UNPKG

fullcalendar

Version:

FullCalendar Vanilla JS package for rendering a calendar

2,493 lines 95.6 kB
import { NativeDateFormatter, CmdDateFormatter, FuncDateFormatter, addMs, startOfDay, asRoughMs, addDays, createDuration, durationsEqual, joinDateTimeFormatParts, rangeContainsMarker, buildIsoString, rangesIntersect, rangeContainsRange, diffDays, diffDayAndTime, intersectRanges, invertRanges, rangesEqual } from '@full-ui/headless-calendar';
import { c as classNames } from './3b4e987d.js';
import { j as joinClassNames } from './c912f986.js';

function createFormatter(input) {
    if (typeof input === 'object' && input) { // non-null object
        return new NativeDateFormatter(input);
    }
    if (typeof input === 'string') {
        return new CmdDateFormatter(input);
    }
    if (typeof input === 'function') {
        return new FuncDateFormatter(input);
    }
    return null;
}

function warn(...args) {
    console.warn('FullCalendar:', ...args);
}

/* eslint max-classes-per-file: off */
const warnedClassNameOptions = {};
function refineClassName(input, optionName) {
    if (!input || typeof input === 'string') {
        return input;
    }
    warnInvalidClassName(optionName);
    return '';
}
function refineClassNameGenerator(input, optionName) {
    if (typeof input === 'function') {
        return (renderProps) => refineClassName(input(renderProps), optionName);
    }
    return refineClassName(input, optionName);
}
function warnInvalidClassName(optionName) {
    if (!warnedClassNameOptions[optionName]) {
        warn(`Invalid option \`${optionName}\`: expected a className string or a falsy value.`);
        warnedClassNameOptions[optionName] = true;
    }
}

// Stops a mouse/touch event from doing it's native browser action
function preventDefault(ev) {
    ev.preventDefault();
}
// Event Delegation
// ----------------------------------------------------------------------------------------------------------------
function buildDelegationHandler(selector, handler) {
    return (ev) => {
        let matchedChild = ev.target.closest(selector);
        if (matchedChild) {
            handler.call(matchedChild, ev, matchedChild);
        }
    };
}
function listenBySelector(container, eventType, selector, handler) {
    let attachedHandler = buildDelegationHandler(selector, handler);
    container.addEventListener(eventType, attachedHandler);
    return () => {
        container.removeEventListener(eventType, attachedHandler);
    };
}
function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) {
    let currentMatchedChild;
    return listenBySelector(container, 'mouseover', selector, (mouseOverEv, matchedChild) => {
        if (matchedChild !== currentMatchedChild) {
            currentMatchedChild = matchedChild;
            onMouseEnter(mouseOverEv, matchedChild);
            let realOnMouseLeave = (mouseLeaveEv) => {
                currentMatchedChild = null;
                onMouseLeave(mouseLeaveEv, matchedChild);
                matchedChild.removeEventListener('mouseleave', realOnMouseLeave);
            };
            // listen to the next mouseleave, and then unattach
            matchedChild.addEventListener('mouseleave', realOnMouseLeave);
        }
    });
}
// Animation
// ----------------------------------------------------------------------------------------------------------------
const transitionEventNames = [
    'webkitTransitionEnd',
    'otransitionend',
    'oTransitionEnd',
    'msTransitionEnd',
    'transitionend',
];
// triggered only when the next single subsequent transition finishes
function whenTransitionDone(el, callback) {
    let realCallback = (ev) => {
        callback(ev);
        transitionEventNames.forEach((eventName) => {
            el.removeEventListener(eventName, realCallback);
        });
    };
    transitionEventNames.forEach((eventName) => {
        el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes
    });
}
// ARIA workarounds
// ----------------------------------------------------------------------------------------------------------------
function createAriaClickAttrs(handler) {
    return {
        onClick: handler,
        ...createAriaKeyboardAttrs(handler),
    };
}
function createAriaKeyboardAttrs(handler) {
    return {
        tabIndex: 0,
        onKeyDown(ev) {
            if (ev.key === 'Enter' || ev.key === ' ') {
                handler(ev);
                ev.preventDefault(); // if space, don't scroll down page
            }
        },
    };
}

let guidNumber = 0;
function guid() {
    guidNumber += 1;
    return String(guidNumber);
}
/* FullCalendar-specific DOM Utilities
----------------------------------------------------------------------------------------------------------------------*/
// Make the mouse cursor express that an event is not allowed in the current area
function disableCursor() {
    document.body.classList.add(classNames.notAllowed);
}
// Returns the mouse cursor to its original look
function enableCursor() {
    document.body.classList.remove(classNames.notAllowed);
}
/* Selection
----------------------------------------------------------------------------------------------------------------------*/
function preventSelection(el) {
    el.style.userSelect = 'none';
    el.style.webkitUserSelect = 'none';
    el.addEventListener('selectstart', preventDefault);
}
function allowSelection(el) {
    el.style.userSelect = '';
    el.style.webkitUserSelect = '';
    el.removeEventListener('selectstart', preventDefault);
}
/* Context Menu
----------------------------------------------------------------------------------------------------------------------*/
function preventContextMenu(el) {
    el.addEventListener('contextmenu', preventDefault);
}
function allowContextMenu(el) {
    el.removeEventListener('contextmenu', preventDefault);
}
function parseFieldSpecs(input) {
    let specs = [];
    let tokens = [];
    let i;
    let token;
    if (typeof input === 'string') {
        tokens = input.split(/\s*,\s*/);
    }
    else if (typeof input === 'function') {
        tokens = [input];
    }
    else if (Array.isArray(input)) {
        tokens = input;
    }
    for (i = 0; i < tokens.length; i += 1) {
        token = tokens[i];
        if (typeof token === 'string') {
            specs.push(token.charAt(0) === '-' ?
                { field: token.substring(1), order: -1 } :
                { field: token, order: 1 });
        }
        else if (typeof token === 'function') {
            specs.push({ func: token });
        }
    }
    return specs;
}
function compareByFieldSpecs(obj0, obj1, fieldSpecs) {
    let i;
    let cmp;
    for (i = 0; i < fieldSpecs.length; i += 1) {
        cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]);
        if (cmp) {
            return cmp;
        }
    }
    return 0;
}
function compareByFieldSpec(obj0, obj1, fieldSpec) {
    if (fieldSpec.func) {
        return fieldSpec.func(obj0, obj1);
    }
    return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field])
        * (fieldSpec.order || 1);
}
function flexibleCompare(a, b) {
    if (!a && !b) {
        return 0;
    }
    if (b == null) {
        return -1;
    }
    if (a == null) {
        return 1;
    }
    if (typeof a === 'string' || typeof b === 'string') {
        return String(a).localeCompare(String(b));
    }
    return a - b;
}
/* String Utilities
----------------------------------------------------------------------------------------------------------------------*/
function formatWithOrdinals(formatter, args, fallbackText) {
    if (typeof formatter === 'function') {
        return formatter(...args);
    }
    if (typeof formatter === 'string') { // non-blank string
        return args.reduce((str, arg, index) => (str.replace('$' + index, arg || '')), formatter);
    }
    return fallbackText;
}
/* Number Utilities
----------------------------------------------------------------------------------------------------------------------*/
function compareNumbers(a, b) {
    return a - b;
}
function valuesIdentical(a, b) {
    return a === b;
}
function computeViewBorderless(options) {
    const borderless = options.borderless;
    return {
        borderlessX: Boolean(options.borderlessX ?? borderless),
        borderlessTop: Boolean(options.borderlessTop ?? borderless),
        borderlessBottom: Boolean(options.borderlessBottom ?? borderless),
    };
}

const { hasOwnProperty } = Object.prototype;
// Filter / Map
// -------------------------------------------------------------------------------------------------
function filterHash(hash, func) {
    let filtered = {};
    for (let key in hash) {
        if (func(hash[key], key)) {
            filtered[key] = hash[key];
        }
    }
    return filtered;
}
function mapHash(hash, func) {
    let newHash = {};
    for (let key in hash) {
        newHash[key] = func(hash[key], key);
    }
    return newHash;
}
// Conversion
// -------------------------------------------------------------------------------------------------
// Can't use Object.values yet because no es2015 support
// TODO: reassess browser support
// https://caniuse.com/?search=object.values
function hashValuesToArray(obj) {
    let a = [];
    for (let key in obj) {
        a.push(obj[key]);
    }
    return a;
}
// TODO: rename to stringArrayToHash or something
function arrayToHash(a) {
    let hash = {};
    for (let item of a) {
        hash[item] = true;
    }
    return hash;
}
// Equality
// -------------------------------------------------------------------------------------------------
function isMaybePropsEqualDepth1(props0, props1) {
    if (typeof props0 === 'object' && props0 && // non-null object
        typeof props1 === 'object' && props1 // non-null object
    ) {
        return isPropsEqualWithFunc(props0, props1, isPropsEqualShallow);
    }
    return props0 === props1;
}
function isPropsEqualWithFunc(props0, props1, valuesEqual) {
    if (props0 === props1) {
        return true;
    }
    for (let key in props0) {
        if (hasOwnProperty.call(props0, key)) {
            if (!(key in props1)) {
                return false;
            }
        }
    }
    for (let key in props1) {
        if (hasOwnProperty.call(props1, key)) {
            if (!(key in props0) || !valuesEqual(props0[key], props1[key], key)) {
                return false;
            }
        }
    }
    return true;
}
function isMaybePropsEqualShallow(props0, props1) {
    if (typeof props0 === 'object' &&
        typeof props1 === 'object' &&
        props0 && props1 // both non-null objects
    ) {
        return isPropsEqualShallow(props0, props1);
    }
    return props0 === props1;
}
function isPropsEqualShallow(props0, props1) {
    return isPropsEqualWithFunc(props0, props1, valuesIdentical);
}
function isPropsEqualWithMap(props0, props1, equalityFuncMap) {
    return isPropsEqualWithFunc(props0, props1, (val0, val1, key) => {
        const equalityFunc = equalityFuncMap[key];
        const isEqual = equalityFunc
            ? equalityFunc(val0, val1)
            : val0 === val1;
        // if (debugMessage && !isEqual) {
        //   console.log(
        //     debugMessage, key, 'NOT EQUAL', 'rerunning...',
        //     equalityFunc
        //       ? equalityFunc(val0, val1)
        //       : val0 === val1
        //   )
        // }
        return isEqual;
    });
}
/*
Returns array of keys
*/
function getUnequalProps(props0, props1) {
    let keys = [];
    for (let key in props0) {
        if (hasOwnProperty.call(props0, key)) {
            if (!(key in props1)) {
                keys.push(key);
            }
        }
    }
    for (let key in props1) {
        if (hasOwnProperty.call(props1, key)) {
            if (props0[key] !== props1[key]) {
                keys.push(key);
            }
        }
    }
    return keys;
}
// Merge
// -------------------------------------------------------------------------------------------------
function mergeMaybePropsDepth1(props0, props1) {
    if (!props0) {
        return props1;
    }
    return mergePropsWithFunc(props0, props1, mergePropsShallow);
}
function mergePropsWithFunc(props0, props1, mergeValues) {
    const dest = {};
    for (let key in props0) {
        if (hasOwnProperty.call(props0, key)) {
            if (!(key in props1)) {
                dest[key] = props0[key];
            }
        }
    }
    for (let key in props1) {
        if (hasOwnProperty.call(props1, key)) {
            if (!(key in props0)) {
                dest[key] = props1[key];
            }
            else {
                dest[key] = mergeValues(props0[key], props1[key]);
            }
        }
    }
    return dest;
}
function mergePropsShallow(props0, props1) {
    return Object.assign({}, props0, props1);
}

// TODO: new util arrayify?
// Array.prototype.slice.call(
function flatArray(items) {
    const res = [];
    for (const item of items) {
        if (Array.isArray(item)) {
            for (const subItem of item) {
                res.push(subItem);
            }
        }
        else {
            res.push(item);
        }
    }
    return res;
}
function flatMapArray(inputs, mapFunc) {
    const res = [];
    for (let i = 0; i < inputs.length; i += 1) {
        const output = mapFunc(inputs[i], i);
        if (Array.isArray(output)) {
            for (const subOutput of output) {
                res.push(subOutput);
            }
        }
        else {
            res.push(output);
        }
    }
    return res;
}
function removeExact(array, exactItem) {
    let removeCnt = 0;
    let i = 0;
    while (i < array.length) {
        if (array[i] === exactItem) {
            array.splice(i, 1);
            removeCnt += 1;
        }
        else {
            i += 1;
        }
    }
    return removeCnt;
}
function isMaybeArraysEqual(array0, array1) {
    if (Array.isArray(array0) && Array.isArray(array1)) {
        return isArraysEqual(array0, array1);
    }
    return array0 === array1;
}
function isArraysEqual(array0, array1, itemsEqual = valuesIdentical) {
    if (array0 === array1) {
        return true;
    }
    let len = array0.length;
    let i;
    if (len !== array1.length) { // not array? or not same length?
        return false;
    }
    for (i = 0; i < len; i += 1) {
        if (!itemsEqual(array0[i], array1[i])) {
            return false;
        }
    }
    return true;
}

// base options
// ------------
const BASE_OPTION_REFINERS = {
    navLinkDayClick: identity,
    navLinkWeekClick: identity,
    duration: createDuration,
    buttons: identity,
    toolbarElements: identity,
    prevText: String,
    nextText: String,
    prevYearText: String,
    nextYearText: String,
    todayText: String,
    yearText: String,
    monthText: String,
    weekTextLong: String,
    weekTextShort: String,
    dayText: String,
    listText: identity,
    todayHint: identity,
    prevHint: identity,
    nextHint: identity,
    // TODO: make type for hint input
    buttonDisplay: identity,
    buttonGroupClass: refineClassNameGenerator,
    buttonClass: refineClassNameGenerator,
    defaultAllDayEventDuration: createDuration,
    defaultTimedEventDuration: createDuration,
    nextDayThreshold: createDuration,
    scrollTime: createDuration,
    scrollTimeReset: Boolean,
    slotMinTime: createDuration,
    slotMaxTime: createDuration,
    popoverFormat: createFormatter,
    slotDuration: createDuration,
    snapDuration: createDuration,
    headerToolbar: identity,
    footerToolbar: identity,
    forceEventDuration: Boolean,
    // TODO: move to timegrid
    dayLaneClass: refineClassNameGenerator,
    dayLaneInnerClass: refineClassNameGenerator,
    dayLaneDidMount: identity,
    dayLaneWillUnmount: identity,
    initialView: String,
    aspectRatio: Number,
    weekends: Boolean,
    weekNumberCalculation: identity,
    weekNumbers: Boolean,
    weekNumberHeaderClass: refineClassNameGenerator,
    weekNumberHeaderInnerClass: refineClassNameGenerator,
    weekNumberHeaderContent: identity,
    weekNumberHeaderDidMount: identity,
    weekNumberHeaderWillUnmount: identity,
    inlineWeekNumberClass: refineClassNameGenerator,
    inlineWeekNumberContent: identity,
    inlineWeekNumberDidMount: identity,
    inlineWeekNumberWillUnmount: identity,
    editable: Boolean,
    controller: identity,
    nowIndicator: Boolean,
    nowIndicatorSnap: identity,
    nowIndicatorHeaderClass: refineClassNameGenerator,
    nowIndicatorHeaderContent: identity,
    nowIndicatorHeaderDidMount: identity,
    nowIndicatorHeaderWillUnmount: identity,
    nowIndicatorDotClass: refineClassName,
    nowIndicatorLineClass: refineClassNameGenerator,
    nowIndicatorLineContent: identity,
    nowIndicatorLineDidMount: identity,
    nowIndicatorLineWillUnmount: identity,
    showNonCurrentDates: Boolean,
    lazyFetching: Boolean,
    startParam: String,
    endParam: String,
    timeZoneParam: String,
    timeZone: String,
    locales: identity,
    locale: identity,
    dragRevertDuration: Number,
    dragScroll: Boolean,
    allDayMaintainDuration: Boolean,
    unselectAuto: Boolean,
    dropAccept: identity, // TODO: type draggable
    eventOrder: parseFieldSpecs,
    eventOrderStrict: Boolean,
    eventSlicing: Boolean, // default: true
    eventPrintLayout: String,
    longPressDelay: Number,
    eventDragMinDistance: Number,
    expandRows: Boolean,
    height: identity,
    contentHeight: identity,
    direction: String,
    colorScheme: String,
    weekNumberFormat: createFormatter,
    eventResizableFromStart: Boolean,
    displayEventTime: Boolean,
    displayEventEnd: Boolean,
    progressiveEventRendering: Boolean,
    businessHours: identity,
    initialDate: identity,
    now: identity,
    eventDataTransform: identity,
    tableHeaderSticky: identity,
    footerScrollbarSticky: identity,
    defaultAllDay: Boolean,
    eventSourceFailure: identity,
    eventSourceSuccess: identity,
    eventDisplay: String, // TODO: give more specific
    eventStartEditable: Boolean,
    eventDurationEditable: Boolean,
    eventOverlap: identity,
    eventConstraint: identity,
    eventAllow: identity,
    eventColor: String,
    eventContrastColor: String,
    eventDidMount: identity,
    eventWillUnmount: identity,
    eventContent: identity,
    eventClass: refineClassNameGenerator,
    eventInnerClass: refineClassNameGenerator,
    eventTimeClass: refineClassNameGenerator,
    eventTitleClass: refineClassNameGenerator,
    eventBeforeClass: refineClassNameGenerator,
    eventAfterClass: refineClassNameGenerator,
    //
    listItemEventClass: refineClassNameGenerator,
    listItemEventInnerClass: refineClassNameGenerator,
    listItemEventTimeClass: refineClassNameGenerator,
    listItemEventTitleClass: refineClassNameGenerator,
    listItemEventBeforeClass: refineClassNameGenerator,
    listItemEventAfterClass: refineClassNameGenerator,
    //
    blockEventClass: refineClassNameGenerator,
    blockEventInnerClass: refineClassNameGenerator,
    blockEventTimeClass: refineClassNameGenerator,
    blockEventTitleClass: refineClassNameGenerator,
    blockEventBeforeClass: refineClassNameGenerator,
    blockEventAfterClass: refineClassNameGenerator,
    //
    rowEventClass: refineClassNameGenerator,
    rowEventInnerClass: refineClassNameGenerator,
    rowEventTimeClass: refineClassNameGenerator,
    rowEventTitleClass: refineClassNameGenerator,
    rowEventTitleSticky: Boolean,
    rowEventBeforeClass: refineClassNameGenerator,
    rowEventBeforeContent: identity,
    rowEventAfterClass: refineClassNameGenerator,
    rowEventAfterContent: identity,
    //
    columnEventClass: refineClassNameGenerator,
    columnEventInnerClass: refineClassNameGenerator,
    columnEventTimeClass: refineClassNameGenerator,
    columnEventTitleClass: refineClassNameGenerator,
    columnEventTitleSticky: Boolean,
    columnEventBeforeClass: refineClassNameGenerator,
    columnEventAfterClass: refineClassNameGenerator,
    //
    backgroundEventClass: refineClassNameGenerator,
    backgroundEventDidMount: identity,
    backgroundEventWillUnmount: identity,
    backgroundEventContent: identity,
    backgroundEventInnerClass: refineClassNameGenerator,
    backgroundEventTitleClass: refineClassNameGenerator,
    backgroundEventColor: String,
    selectConstraint: identity,
    selectOverlap: identity,
    selectAllow: identity,
    droppable: Boolean,
    unselectCancel: String,
    slotHeaderFormat: identity,
    slotLaneClass: refineClassNameGenerator,
    slotLaneDidMount: identity,
    slotLaneWillUnmount: identity,
    slotHeaderClass: refineClassNameGenerator,
    slotHeaderInnerClass: refineClassNameGenerator,
    slotHeaderContent: identity,
    slotHeaderDidMount: identity,
    slotHeaderWillUnmount: identity,
    slotHeaderAlign: identity,
    slotHeaderSticky: identity,
    slotHeaderRowClass: refineClassName,
    slotHeaderDividerClass: refineClassNameGenerator,
    dayMaxEvents: identity,
    dayMaxEventRows: identity,
    dayMinWidth: Number,
    slotHeaderInterval: createDuration,
    // in core because more-popover needs it
    dayHeaderClass: refineClassNameGenerator,
    dayHeaderInnerClass: refineClassNameGenerator,
    dayHeaderContent: identity,
    dayHeaderDidMount: identity,
    dayHeaderWillUnmount: identity,
    dayHeaderAlign: identity,
    // stickiness for cell-inner-contents laterally. experimental settings
    _dayHeaderSticky: identity,
    dayHeaderRowClass: refineClassName,
    dayHeaderDividerClass: refineClassNameGenerator,
    dayRowClass: refineClassName,
    dayCellDidMount: identity,
    dayCellWillUnmount: identity,
    dayCellClass: refineClassNameGenerator,
    dayCellInnerClass: refineClassNameGenerator,
    dayCellTopContent: identity,
    dayCellTopClass: refineClassNameGenerator,
    dayCellTopInnerClass: refineClassNameGenerator,
    dayCellBottomClass: refineClassNameGenerator,
    allDaySlot: Boolean,
    allDayText: String,
    allDayHeaderClass: refineClassNameGenerator,
    allDayHeaderInnerClass: refineClassNameGenerator,
    allDayHeaderContent: identity,
    allDayHeaderDidMount: identity,
    allDayHeaderWillUnmount: identity,
    timedText: String,
    slotMinWidth: Number,
    slotMinHeight: Number,
    navLinks: Boolean,
    eventTimeFormat: createFormatter,
    rerenderDelay: Number, // TODO: move to vanilla right? nah keep here
    moreLinkText: identity, // this not enforced :( check others too
    moreLinkHint: identity,
    selectMinDistance: Number,
    selectable: Boolean,
    selectLongPressDelay: Number,
    eventLongPressDelay: Number,
    selectMirror: Boolean,
    eventMaxStack: Number,
    eventMinHeight: Number,
    eventMinWidth: Number,
    eventShortHeight: Number,
    slotEventOverlap: Boolean,
    firstDay: Number,
    dayCount: Number,
    dateAlignment: String,
    dateIncrement: createDuration,
    hiddenDays: identity,
    fixedWeekCount: Boolean,
    validRange: identity, // `this` works?
    visibleRange: identity, // `this` works?
    titleFormat: identity,
    eventInteractive: Boolean,
    // only used by list-view, but languages define the value, so we need it in base options
    noEventsText: String,
    viewHint: identity,
    viewChangeHint: String, // for the tab container
    navLinkHint: identity,
    closeHint: String,
    eventsHint: String,
    headingLevel: Number,
    moreLinkClick: identity,
    moreLinkContent: identity,
    moreLinkDidMount: identity,
    moreLinkWillUnmount: identity,
    moreLinkClass: refineClassNameGenerator,
    moreLinkInnerClass: refineClassNameGenerator,
    //
    rowMoreLinkClass: refineClassNameGenerator,
    rowMoreLinkInnerClass: refineClassNameGenerator,
    //
    columnMoreLinkClass: refineClassNameGenerator,
    columnMoreLinkInnerClass: refineClassNameGenerator,
    navLinkClass: refineClassName,
    monthStartFormat: createFormatter,
    dayCellFormat: createFormatter,
    // for connectors
    // (can't be part of plugin system b/c must be provided at runtime)
    handleCustomRendering: identity,
    customRenderingMetaMap: identity,
    popoverClass: refineClassName,
    popoverCloseClass: refineClassName,
    popoverCloseContent: identity,
    dayNarrowWidth: Number,
    borderless: Boolean,
    borderlessX: Boolean,
    borderlessTop: Boolean,
    borderlessBottom: Boolean,
    fillerClass: refineClassNameGenerator,
    headerToolbarClass: refineClassNameGenerator,
    footerToolbarClass: refineClassNameGenerator,
    toolbarClass: refineClassNameGenerator,
    toolbarSectionClass: refineClassNameGenerator,
    toolbarTitleClass: refineClassName,
    tableClass: refineClassNameGenerator,
    tableHeaderClass: refineClassNameGenerator,
    tableBodyClass: refineClassNameGenerator,
    nonBusinessHoursClass: refineClassName,
    highlightClass: refineClassName,
    // daygrid-only
    dayHeaders: Boolean,
    dayHeaderFormat: createFormatter,
    // timegrid-only
    allDayDividerClass: refineClassName,
    // list-only
    listDaysClass: refineClassName, // rename this?
    listDayClass: refineClassNameGenerator,
    //
    listDayFormat: createFalsableFormatter, // defaults specified in list plugins
    listDayAltFormat: createFalsableFormatter, // "
    //
    listDayHeaderDidMount: identity,
    listDayHeaderWillUnmount: identity,
    listDayHeaderClass: refineClassNameGenerator,
    listDayHeaderInnerClass: refineClassNameGenerator,
    listDayHeaderContent: identity,
    //
    listDayBodyClass: refineClassNameGenerator,
    //
    noEventsClass: refineClassNameGenerator,
    noEventsInnerClass: refineClassNameGenerator,
    noEventsContent: identity,
    noEventsDidMount: identity,
    noEventsWillUnmount: identity,
    // noEventsText is defined in base options
    // multimonth-only
    multiMonthMaxColumns: Number,
    //
    singleMonthMinWidth: Number,
    singleMonthTitleFormat: createFormatter,
    singleMonthDidMount: identity,
    singleMonthWillUnmount: identity,
    singleMonthClass: refineClassNameGenerator,
    singleMonthHeaderClass: refineClassNameGenerator,
    singleMonthHeaderInnerClass: refineClassNameGenerator,
};
// do NOT give a type here. need `typeof BASE_OPTION_DEFAULTS` to give real results.
// raw values.
const BASE_OPTION_DEFAULTS = {
    buttonDisplay: 'auto',
    eventDisplay: 'auto',
    defaultTimedEventDuration: '01:00:00',
    defaultAllDayEventDuration: { day: 1 },
    forceEventDuration: false,
    nextDayThreshold: '00:00:00',
    initialView: '',
    aspectRatio: 1.35,
    weekends: true,
    weekNumbers: false,
    weekNumberCalculation: 'local',
    editable: false,
    nowIndicator: false,
    scrollTime: '06:00:00',
    scrollTimeReset: true,
    slotMinTime: '00:00:00',
    slotMaxTime: '24:00:00',
    showNonCurrentDates: true,
    lazyFetching: true,
    startParam: 'start',
    endParam: 'end',
    timeZoneParam: 'timeZone',
    timeZone: 'local', // TODO: throw error if given falsy value?
    locales: [],
    locale: '', // blank values means it will compute based off locales[]
    dragRevertDuration: 500,
    dragScroll: true,
    allDayMaintainDuration: false,
    unselectAuto: true,
    dropAccept: '*',
    eventOrder: 'start,-duration,allDay,title',
    eventSlicing: true,
    eventPrintLayout: 'auto',
    popoverFormat: { month: 'long', day: 'numeric', year: 'numeric' },
    longPressDelay: 1000,
    eventDragMinDistance: 5, // only applies to mouse
    expandRows: false,
    navLinks: false,
    selectable: false,
    eventMinHeight: 15,
    eventMinWidth: 30,
    eventShortHeight: 30,
    monthStartFormat: { month: 'long', day: 'numeric' },
    dayCellFormat: { day: 'numeric', omitTrailing: true },
    headingLevel: 2, // like H2
    outerBorder: true,
    dayNarrowWidth: 80,
    eventOverlap: true,
    slotHeaderAlign: 'start',
    slotHeaderSticky: true,
    dayHeaderAlign: 'start',
    _dayHeaderSticky: true,
    rowEventTitleSticky: true,
    columnEventTitleSticky: true,
    nowIndicatorSnap: 'auto',
    // daygrid-only
    dayHeaders: true,
};
// calendar listeners
// ------------------
const CALENDAR_LISTENER_REFINERS = {
    datesSet: identity,
    eventsSet: identity,
    eventAdd: identity,
    eventChange: identity,
    eventRemove: identity,
    eventClick: identity, // TODO: resource for scheduler????
    eventMouseEnter: identity,
    eventMouseLeave: identity,
    select: identity, // resource for scheduler????
    unselect: identity,
    loading: identity,
    // internal
    _unmount: identity,
    _beforeprint: identity,
    _afterprint: identity,
    _noDateSelect: identity,
    _noEventDrop: identity,
    _noEventResize: identity,
    _timeScrollRequest: identity,
    // interaction-plugin-only
    dateClick: identity,
    eventDragStart: identity,
    eventDragStop: identity,
    eventDrop: identity,
    eventResizeStart: identity,
    eventResizeStop: identity,
    eventResize: identity,
    drop: identity,
    eventReceive: identity,
    eventLeave: identity,
};
// calendar-only options (not for view-specific)
// ---------------------------------------------
const CALENDAR_ONLY_OPTION_REFINERS = {
    class: refineClassNameGenerator,
    className: refineClassNameGenerator,
    viewClass: refineClassNameGenerator,
    viewDidMount: identity,
    viewWillUnmount: identity,
    views: identity,
    plugins: identity,
    initialEvents: identity,
    events: identity,
    eventSources: identity,
};
// view-specific options
// ---------------------
const VIEW_ONLY_OPTION_REFINERS = {
    type: String,
    component: identity,
    class: refineClassNameGenerator,
    className: refineClassNameGenerator,
    content: identity,
    didMount: identity,
    willUnmount: identity,
    // internal only
    buttonTextKey: String,
    dateProfileGeneratorClass: identity,
    usesMinMaxTime: Boolean,
    disallowAmbigTitle: Boolean,
};
const COMPLEX_OPTION_COMPARATORS = {
    // Unfortunately always need 'maybe' to handle undefined inital value, because of CalendarDataManager
    dateIncrement: isMaybePropsEqualShallow,
    headerToolbar: isMaybePropsEqualShallow,
    footerToolbar: isMaybePropsEqualShallow,
    buttons: isMaybePropsEqualDepth1,
    plugins: isMaybeArraysEqual,
    events: isMaybeArraysEqual,
    eventSources: isMaybeArraysEqual,
    ['resources']: isMaybeArraysEqual,
};
// util funcs
// ----------------------------------------------------------------------------------------------------
function refineProps(input, refiners) {
    let refined = {};
    let extra = {};
    for (let propName in refiners) {
        if (propName in input) {
            refined[propName] = refiners[propName](input[propName], propName);
        }
    }
    for (let propName in input) {
        if (!(propName in refiners)) {
            extra[propName] = input[propName];
        }
    }
    return { refined, extra };
}
function identity(raw) {
    return raw;
}
function createFalsableFormatter(input) {
    return input === false ? null : createFormatter(input);
}

/*
The canonical way to construct an EventInstanceRange. Pass undefined for an edge's
instant when the input didn't express one or the edge's marker was derived civilly —
the property is omitted entirely (never stored as an undefined-valued key).
*/
function buildEventInstanceRange(start, end, instantStartMs, instantEndMs) {
    const range = { start, end };
    if (instantStartMs != null) {
        range.instantStartMs = instantStartMs;
    }
    if (instantEndMs != null) {
        range.instantEndMs = instantEndMs;
    }
    return range;
}
// Resolves an edge to its real instant, choosing the deterministic first occurrence when
// the marker is ambiguous and the edge does not carry an exact instant.
function resolveEdgeInstantMs(marker, instantMs, dateEnv) {
    return instantMs ?? dateEnv.toDate(marker).valueOf();
}
/*
Builds every outward-facing representation of a range edge from one policy. Exact timed
edges use their true instant and its canonical wall-clock marker; civil/all-day edges keep
the supplied marker and DateEnv conversion.
*/
function buildRangeEdgeOutput(marker, instantMs, dateEnv, omitTime) {
    const canonicalMarker = instantMs != null ? dateEnv.timestampToMarker(instantMs) : marker;
    const timeZoneOffset = instantMs != null
        ? Math.round((canonicalMarker.valueOf() - instantMs) / 60000)
        : dateEnv.offsetForMarker(marker);
    if (!omitTime && instantMs != null) {
        return {
            marker: canonicalMarker,
            date: new Date(instantMs),
            dateStr: buildIsoString(canonicalMarker, timeZoneOffset),
        };
    }
    return {
        marker: canonicalMarker,
        date: dateEnv.toDate(marker),
        dateStr: omitTime
            ? dateEnv.formatIso(marker, { omitTime })
            : buildIsoString(marker, timeZoneOffset),
    };
}
// the exact epoch instant of a range's start: the stored instant when present, otherwise
// deterministic first-occurrence resolution of the civil marker
function getRangeInstantStartMs(range, dateEnv) {
    return resolveEdgeInstantMs(range.start, range.instantStartMs, dateEnv);
}
function getRangeInstantEndMs(range, dateEnv) {
    return resolveEdgeInstantMs(range.end, range.instantEndMs, dateEnv);
}
// The canonical civil end: recomputed from the exact instant for fold-compressed ranges.
function canonicalRangeEndMarker(range, dateEnv) {
    return range.instantEndMs != null ? dateEnv.timestampToMarker(range.instantEndMs) : range.end;
}
function rangeHasInstants(range) {
    return range.instantStartMs != null || range.instantEndMs != null;
}
/*
Like rangesIntersect, but exact: when either range carries stored instants, real instants
are compared (a fold-compressed end's representational marker would otherwise falsely
collide with the other occurrence's civil times). Pure-civil pairs compare markers, as before.
*/
function instanceRangesIntersect(range0, range1, dateEnv) {
    if (rangeHasInstants(range0) || rangeHasInstants(range1)) {
        return getRangeInstantStartMs(range0, dateEnv) < getRangeInstantEndMs(range1, dateEnv) &&
            getRangeInstantEndMs(range0, dateEnv) > getRangeInstantStartMs(range1, dateEnv);
    }
    return rangesIntersect(range0, range1);
}
/*
Like rangeContainsRange, but exact in the same way as instanceRangesIntersect.
The outer range may be open-ended.
*/
function instanceRangeContainsRange(outerRange, innerRange, dateEnv) {
    if (rangeHasInstants(outerRange) || rangeHasInstants(innerRange)) {
        const outerStartMs = outerRange.start != null ?
            resolveEdgeInstantMs(outerRange.start, outerRange.instantStartMs, dateEnv) :
            -Infinity;
        const outerEndMs = outerRange.end != null ?
            resolveEdgeInstantMs(outerRange.end, outerRange.instantEndMs, dateEnv) :
            Infinity;
        return outerStartMs <= getRangeInstantStartMs(innerRange, dateEnv) &&
            outerEndMs >= getRangeInstantEndMs(innerRange, dateEnv);
    }
    return rangeContainsRange(outerRange, innerRange);
}
/*
Adds a positive duration to a start edge, deriving an END edge.
When the edge carries an exact instant and the duration is pure clock-time, the addition
happens in instant space (exact real elapsed time) and the result carries an instant too.
If a fall-back fold keeps the resulting canonical marker from advancing past the edge's own
marker, the result uses the edge's UTC-offset reading instead (the sanctioned
instant/marker disagreement — see EventInstanceRange). Calendar-unit durations and
instant-less edges use civil arithmetic (no instant).
*/
function addDurationToEdge(edge, duration, dateEnv) {
    if (edge.instantMs != null && !duration.years && !duration.months && !duration.days) {
        const durMs = asRoughMs(duration);
        const instantMs = edge.instantMs + durMs;
        const marker = dateEnv.timestampToMarker(instantMs);
        return marker > edge.marker
            ? { marker, instantMs }
            : { marker: addMs(edge.marker, durMs), instantMs };
    }
    return { marker: dateEnv.add(edge.marker, duration) };
}
/*
Builds a civilly-ordered EventInstanceRange from two edges, or null when the range is
invalid: pure-civil edges are invalid unless the end marker is after the start marker;
when either edge carries an exact instant, REAL-time order is the truth instead.
A real range that a DST fall-back fold civilly compresses (instants ordered, markers not)
is not discarded: its end marker is re-expressed in the start's UTC-offset reading
(start marker + real duration), keeping the exact end instant.
*/
function buildValidInstanceRange(start, end, dateEnv) {
    if (start.instantMs == null && end.instantMs == null) {
        return end.marker > start.marker
            ? buildEventInstanceRange(start.marker, end.marker)
            : null;
    }
    const startMs = resolveEdgeInstantMs(start.marker, start.instantMs, dateEnv);
    const endMs = resolveEdgeInstantMs(end.marker, end.instantMs, dateEnv);
    if (endMs <= startMs) {
        return null;
    }
    return buildEventInstanceRange(start.marker, end.marker > start.marker
        ? end.marker
        : addMs(start.marker, endMs - startMs), // fold-compressed: start's offset reading
    start.instantMs, end.instantMs);
}
function createEventInstance(defId, range) {
    return {
        instanceId: guid(),
        defId,
        range,
    };
}

/* Date stuff that doesn't belong in datelib core
----------------------------------------------------------------------------------------------------------------------*/
// given a timed range, computes an all-day range that has the same exact duration,
// but whose start time is aligned with the start of the day.
function computeAlignedDayRange(timedRange) {
    let dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1;
    let start = startOfDay(timedRange.start);
    let end = addDays(start, dayCnt);
    return { start, end };
}
// given a timed range, computes an all-day range based on how for the end date bleeds into the next day
// TODO: give nextDayThreshold a default arg
function computeVisibleDayRange(timedRange, nextDayThreshold = createDuration(0)) {
    let startDay = null;
    let endDay = null;
    if (timedRange.end) {
        endDay = startOfDay(timedRange.end);
        let endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay`
        // If the end time is actually inclusively part of the next day and is equal to or
        // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`.
        // Otherwise, leaving it as inclusive will cause it to exclude `endDay`.
        if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) {
            endDay = addDays(endDay, 1);
        }
    }
    if (timedRange.start) {
        startDay = startOfDay(timedRange.start); // the beginning of the day the range starts
        // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day.
        if (endDay && endDay <= startDay) {
            endDay = addDays(startDay, 1);
        }
    }
    return { start: startDay, end: endDay };
}
function diffDates(date0, date1, dateEnv, largeUnit) {
    if (largeUnit === 'year') {
        return createDuration(dateEnv.diffWholeYears(date0, date1), 'year');
    }
    if (largeUnit === 'month') {
        return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month');
    }
    return diffDayAndTime(date0, date1); // returns a duration
}

function parseRecurring(refined, defaultAllDay, dateEnv, recurringTypes) {
    for (let i = 0; i < recurringTypes.length; i += 1) {
        let parsed = recurringTypes[i].parse(refined, dateEnv);
        if (parsed) {
            let { allDay } = refined;
            if (allDay == null) {
                allDay = defaultAllDay;
                if (allDay == null) {
                    allDay = parsed.allDayGuess;
                    if (allDay == null) {
                        allDay = false;
                    }
                }
            }
            return {
                allDay,
                duration: parsed.duration,
                typeData: parsed.typeData,
                typeId: i,
            };
        }
    }
    return null;
}
function expandRecurring(eventStore, framingRange, context) {
    let { dateEnv, pluginHooks, options } = context;
    let { defs, instances } = eventStore;
    // remove existing recurring instances
    // TODO: bad. always expand events as a second step
    instances = filterHash(instances, (instance) => !defs[instance.defId].recurringDef);
    for (let defId in defs) {
        let def = defs[defId];
        if (def.recurringDef) {
            let { duration } = def.recurringDef;
            if (!duration) {
                duration = def.allDay ?
                    options.defaultAllDayEventDuration :
                    options.defaultTimedEventDuration;
            }
            let starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes);
            for (let start of starts) {
                let instance = createEventInstance(defId, {
                    start,
                    end: dateEnv.add(start, duration),
                });
                instances[instance.instanceId] = instance;
            }
        }
    }
    return { defs, instances };
}
/*
Event MUST have a recurringDef
*/
function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) {
    let typeDef = recurringTypes[eventDef.recurringDef.typeId];
    let markers = typeDef.expand(eventDef.recurringDef.typeData, {
        start: dateEnv.subtract(framingRange.start, duration), // for when event starts before framing range and goes into
        end: framingRange.end,
    }, dateEnv);
    // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to
    if (eventDef.allDay) {
        markers = markers.map(startOfDay);
    }
    return markers;
}

function parseEvents(rawEvents, eventSource, context, allowOpenRange, defIdMap, instanceIdMap) {
    let eventStore = createEmptyEventStore();
    let eventRefiners = buildEventRefiners(context);
    for (let rawEvent of rawEvents) {
        let tuple = parseEvent(rawEvent, eventSource, context, allowOpenRange, eventRefiners, defIdMap, instanceIdMap);
        if (tuple) {
            eventTupleToStore(tuple, eventStore);
        }
    }
    return eventStore;
}
function eventTupleToStore(tuple, eventStore = createEmptyEventStore()) {
    eventStore.defs[tuple.def.defId] = tuple.def;
    if (tuple.instance) {
        eventStore.instances[tuple.instance.instanceId] = tuple.instance;
    }
    return eventStore;
}
// retrieves events that have the same groupId as the instance specified by `instanceId`
// or they are the same as the instance.
// why might instanceId not be in the store? an event from another calendar?
function getRelevantEvents(eventStore, instanceId) {
    let instance = eventStore.instances[instanceId];
    if (instance) {
        let def = eventStore.defs[instance.defId];
        // get events/instances with same group
        let newStore = filterEventStoreDefs(eventStore, (lookDef) => isEventDefsGrouped(def, lookDef));
        // add the original
        // TODO: wish we could use eventTupleToStore or something like it
        newStore.defs[def.defId] = def;
        newStore.instances[instance.instanceId] = instance;
        return newStore;
    }
    return createEmptyEventStore();
}
function isEventDefsGrouped(def0, def1) {
    return Boolean(def0.groupId && def0.groupId === def1.groupId);
}
function createEmptyEventStore() {
    return { defs: {}, instances: {} };
}
function mergeEventStores(store0, store1) {
    return {
        defs: { ...store0.defs, ...store1.defs },
        instances: { ...store0.instances, ...store1.instances },
    };
}
function filterEventStoreDefs(eventStore, filterFunc) {
    let defs = filterHash(eventStore.defs, filterFunc);
    let instances = filterHash(eventStore.instances, (instance) => (defs[instance.defId] // still exists?
    ));
    return { defs, instances };
}
function excludeSubEventStore(master, sub) {
    let { defs, instances } = master;
    let filteredDefs = {};
    let filteredInstances = {};
    for (let defId in defs) {
        if (!sub.defs[defId]) { // not explicitly excluded
            filteredDefs[defId] = defs[defId];
        }
    }
    for (let instanceId in instances) {
        if (!sub.instances[instanceId] && // not explicitly excluded
            filteredDefs[instances[instanceId].defId] // def wasn't filtered away
        ) {
            filteredInstances[instanceId] = instances[instanceId];
        }
    }
    return {
        defs: filteredDefs,
        instances: filteredInstances,
    };
}

function normalizeConstraint(input, context) {
    if (Array.isArray(input)) {
        return parseEvents(input, null, context, true); // allowOpenRange=true
    }
    if (typeof input === 'object' && input) { // non-null object
        return parseEvents([input], null, context, true); // allowOpenRange=true
    }
    if (input != null) {
        return String(input);
    }
    return null;
}

// TODO: better called "EventSettings" or "EventConfig"
// TODO: move this file into structs
// TODO: separate constraint/overlap/allow, because selection uses only that, not other props
const EVENT_UI_REFINERS = {
    display: String,
    editable: Boolean,
    startEditable: Boolean,
    durationEditable: Boolean,
    constraint: identity, // Identity<ConstraintInput>, // circular reference. ts dies. event->constraint->event
    overlap: identity,
    allow: identity,
    class: refineClassName,
    className: refineClassName,
    color: String,
    contrastColor: String,
};
const EMPTY_EVENT_UI = {
    display: null,
    startEditable: null,
    durationEditable: null,
    constraints: [],
    overlap: null,
    allows: [],
    color: '',
    contrastColor: '',
    className: '',
};
function createEventUi(refined, context) {
    let constraint = normalizeConstraint(refined.constraint, context);
    return {
        display: refined.display || null,
        startEditable: refined.startEditable != null ? refined.startEditable : refined.editable,
        durationEditable: refined.durationEditable != null ? refined.durationEditable : refined.editable,
        constraints: constraint != null ? [constraint] : [],
        overlap: refined.overlap != null ? refined.overlap : null,
        allows: refined.allow != null ? [refined.allow] : [],
        color: refined.color || '',
        contrastColor: refined.contrastColor || '',
        className: (refined.class ?? refined.className) || '',
    };
}
// TODO: prevent against problems with <2 args!
function combineEventUis(uis) {
    return uis.reduce(combineTwoEventUis, EMPTY_EVENT_UI);
}
function combineTwoEventUis(item0, item1) {
    return {
        display: item1.display != null ? item1.display : item0.display,
        startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable,
        durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable,
        constraints: item0.constraints.concat(item1.constraints),
        overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap,
        allows: item0.allows.concat(item1.allows),
        color: item1.color || item0.color,
        contrastColor: item1.contrastColor || item0.contrastColor,
        className: joinClassNames(item0.className, item1.className),
    };
}

const EVENT_NON_DATE_REFINERS = {
    id: String,
    groupId: String,
    title: String,
    url: String,
    interactive: Boolean,
};
const EVENT_DATE_REFINERS = {
    start: identity,
    end: identity,
    date: identity,
    allDay: Boolean,
};
const EVENT_REFINERS = {
    ...EVENT_NON_DATE_REFINERS,
    ...EVENT_DATE_REFINERS,
    extendedProps: identity,
};
function parseEvent(raw, eventSource, context, allowOpenRange, refiners = buildEventRefiners(context), defIdMap, instanceIdMap) {
    let { refined, extra } = refineEventDef(raw, context, refiners);
    let defaultAllDay = computeIsDefaultAllDay(eventSource, context);
    let recurringRes = parseRecurring(refined, defaultAllDay, context.dateEnv, context.pluginHooks.recurringTypes);
    if (recurringRes) {
        let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', recurringRes.allDay, Boolean(recurringRes.duration), context, defIdMap);
        def.recurringDef = {
            typeId: recurringRes.typeId,
            typeData: recurringRes.typeData,
            duration: recurringRes.duration,
        };
        return { def, instance: null };
    }
    let singleRes = parseSingle(refined, defaultAllDay, context, allowOpenRange);
    if (singleRes) {
        let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', singleRes.allDay, singleRes.hasEnd, context, defIdMap);
        let instance = createEventInstance(def.defId, singleRes.range);
        if (instanceIdMap && def.publicId && instanceIdMap[def.publicId]) {
            instance.instanceId = instanceIdMap[def.publicId];
        }
        return { def, instance };
    }
    return null;
}
function refineEventDef(raw, context, refiners = buildEventRefiners(context)) {
    return refineProps(raw, refiners);
}
function buildEventRefiners(context) {
    return { ...EVENT_UI_REFINERS, ...EVENT_REFINERS, ...context.pluginHooks.eventRefiners };
}
/*
Will NOT populate extendedProps with the leftover properties.
Will NOT populate date-related props.
*/
function parseEventDef(refined, extra, sourceId, allDay, hasEnd, context, defIdMap) {
    let def = {
        title: refined.title || '',
        groupId: refined.groupId || '',
        publicId: refined.id || '',
        url: refined.url || '',
        recurringDef: null,
        defId: ((defIdMap && refined.id) ? defIdMap[refined.id] : '') || guid(),
        sourceId,
        allDay,
        hasEnd,
        interactive: refined.interactive,
        ui: createEventUi(refined, context),
        extendedProps: {
            ...(refined.extendedProps || {}),
            ...extra,
        },
    };
    for (let memberAdder of context.pluginHooks.eventDefMemberAdders) {
        Object.assign(def, memberAdder(refined));
    }
    // help out EventImpl from having user modify props
    Object.freeze(def.ui.className); // might be simple string, but freeze still works
    Object.freeze(def.extendedProps);
    return def;
}
function parseSingle(refined, defaultAllDay, context, allowOpenRange) {
    let { allDay } = refined;
    let startMeta;
    let startMarker = null;
    let hasEnd = false;
    let endMeta;
    let endMarker = null;
    let startInput = refined.start != null ? refined.start : refined.date;
    startMeta = context.dateEnv.createMarkerMeta(startInput);
    if (startMeta) {
        startMarker = startMeta.marker;
    }
    else if (!allowOpenRange) {
        return null;
    }
    if (refined.end != null) {
        endMeta = context.dateEnv.createMarkerMeta(refined.end);
    }
    if (allDay == null) {
        if (defaultAllDay != null) {
            allDay = defaultAllDay;
        }
        else {
            // fall back to the date props LAST
            allDay = (!startMeta || startMeta.isTimeUnspecified) &&
                (!endMeta || endMeta.isTimeUnspecified);
        }
    }
    if (allDay && startMarker) {
        startMarker = startOfDay(startMarker);
    }
    // exact instants are stamped when the input expressed them (ISO with offset, Date,
    // epoch ms) and the marker was used as-is. allDay truncation invalidates instants.
    let startInstantMs = (!allDay && startMeta) ? startMeta.instantMs : undefined;
    let range = null;
    if (endMeta) {
        endMarker = allDay ? startOfDay(endMeta.marker) : endMeta.marker;
        if (!startMarker) { // open start (allowOpenRange)
            range = buildEventInstanceRange(startMarker, endMarker, undefined, allDay ? undefined : endMeta.instantMs);
        }
        else if (allDay) {
            if (endMarker > startMarker) {
                range = buildEventInstanceRange(startMarker, endMarker);
            }
        }
        else {
            // rejects ends invalid in real time; real ranges that a DST fall-back fold civilly
            // compresses keep their exact end, re-expressed in the start's offset reading
            range = buildValidInstanceRange({ marker: startMarker, instantMs: startInstantMs }, { marker: endMarker, instantMs: endMeta.instantMs }, context.dateEnv);
        }
    }
    if (range) {
        hasEnd = true;
    }
    else if (allowOpenRange) {
        range = buildEventInstanceRange(startMarker, null, startInstantMs);
    }
    else {
        hasEnd = context.options.forceEventDuration || false;
        // exact starts get an exact derived end (real elapsed duration), so the end can't
        // re-resolve to the wrong side of a DST fold
        let endEdge = addDurationToEdge({ marker: startMarker, instantMs: startInstantMs }, allDay ?
            context.options.defaultAllDayEventDuration :
            context.options.defaultTimedEventDuration, context.dateEnv);
        range = buildEventInstanceRange(startMarker, endEdge.marker, startInstantMs, endEdge.instantMs);
    }
    return { allDay, hasEnd, range };
}
function computeIsDefaultAllDay(eventSource, context) {
    let res = null;
    if (eventSource) {
        res = eventSource.defaultAllDay;
    }
    if (res == null) {
        res = context.options.defaultAllDay;
    }
    return res;
}

const STANDARD_PROPS = {
    start: identity,
    end: identity,
    allDay: Boolean,
};
function parseDateSpan(raw, dateEnv, defaultDuration) {
    let span = parseOpenDateSpan(raw, dateEnv);
    if (!span) {
        return null;
    }
    let { range } = span;
    if (!range.start) {
        return null;
    }
    if (!range.end) {
        if (defaultDuration == null) {
            return null;
        }
        const endEdge = addDurationToEdge({ marker: range.start, instantMs: span.instantStartMs }, defaultDuration, dateEnv);
        range.end = endEdge.marker;
        if (endEdge.instantMs != null) {
            span.instantEndMs = endEdge.instantMs;
        }
    }
    return span;
}
/*
TODO: somehow combine with parseRange?
Will return null if the start/end props were present but parsed invalidly.
*/
function parseOpenDateSpan(raw, dateEnv) {
    let { refined: standardProps, extra } = refineProps(raw, STANDARD_PROPS);
    let startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null;
    let endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null;
    let { allDay } = standardProps;
    if (allDay == null) {
        allDay = (startMeta && startMeta.isTimeUnspecified) &&
            (!endMeta || endMeta.isTimeUnspecified);
    }
    let range = {
        start: startMeta ? startMeta.marker : null,
        end: endMeta ? endMeta.marker : null,
    };
    if (!allDay && startMeta && endMeta && (startMeta.instantMs != null || endMeta.instantMs != null)) {
        const validRange = buildValidInstanceRange({ marker: startMeta.marker, instantMs: startMeta.instantMs }, { marker: endMeta.marker, instantMs: endMeta.instantMs }, dateEnv);
        if (!validRange && startMeta.instantMs != null && endMeta.instantMs != null) {
            return null;
        }
        if (validRange) {
            range = { start: validRange.start, end: validRange.end };
        }
    }
    const span = {
        range,
        allDay,
        ...extra,
    };
    if (allDay) {
        delete span.instantStartMs;
        delete span.instantEndMs;
    }
    else {
        if (startMeta?.instantMs != null) {
            span.instantStartMs = startMeta.instantMs;
        }
        if (endMeta?.instantMs != null) {
            span.instantEndMs = endMeta.instantMs;
        }
    }
    return span;
}
function isDateSpansEqual(span0, span1) {
    return rangesEqual(span0.range, span1.range) &&
        span0.allDay === span1.allDay &&
        isSpanPropsEqual(span0, span1);
}
// the NON-DATE-RELATED props
function isSpanPropsEqual(span0, span1) {
    for (let propName in span1) {
        if (propName !== 'range' && propName !== 'allDay') {
            if (span0[propName] !== span1[propName]) {
                return false;
            }
        }
    }
    // are there any props that span0 has that span1 DOESN'T have?
    // both have range/allDay, so no need to special-case.
    for (let propName in span0) {
        if (!(propName in span1)) {
            return false;
        }
    }
    return true;
}
function buildDateSpanApi(span, dateEnv) {
    return {
        ...buildRangeApi(span.range, dateEnv, span.allDay, span),
        allDay: span.allDay,
    };
}
function buildRangeApiWithTimeZone(range, dateEnv, omitTime) {
    return {
        ...buildRangeApi(range, dateEnv, omitTime),
        timeZone: dateEnv.timeZone,
    };
}
function buildRangeApi(range, dateEnv, omitTime, rangeMeta) {
    // exact instants may ride on a span (rangeMeta) or on the range itself
    const instantStartMs = rangeMeta?.instantStartMs ?? range.instantStartMs;
    const instantEndMs = rangeMeta?.instantEndMs ?? range.instantEndMs;
    const start = buildRangeEdgeOutput(range.start, instantStartMs, dateEnv, omitTime);
    const end = buildRangeEdgeOutput(range.end, instantEndMs, dateEnv, omitTime);
    return {
        start: start.date,
        end: end.date,
        startStr: start.dateStr,
        endStr: end.dateStr,
    };
}
function getDateSpanInstantStartMs(dateSpan, dateEnv) {
    return resolveEdgeInstantMs(dateSpan.range.start, dateSpan.instantStartMs, dateEnv);
}
function getDateSpanInstantEndMs(dateSpan, dateEnv) {
    return resolveEdgeInstantMs(dateSpan.range.end, dateSpan.instantEndMs, dateEnv);
}
function fabricateEventRange(dateSpan, eventUiBases, context) {
    let res = refineEventDef({ editable: false }, context);
    let def = parseEventDef(res.refined, res.extra, '', // sourceId
    dateSpan.allDay, true, // hasEnd
    context);
    return {
        def,
        ui: compileEventUi(def, eventUiBases),
        instance: createEventInstance(def.defId, dateSpan.range),
        range: dateSpan.range,
        isStart: true,
        isEnd: true,
    };
}

function triggerDateSelect(selection, pev, context) {
    context.emitter.trigger('select', {
        ...buildDateSpanApiWithContext(selection, context),
        jsEvent: pev ? pev.origEvent : null, // Is this always a mouse event? See #4655
        view: context.viewApi || context.calendarApi.view,
    });
}
function triggerDateUnselect(pev, context) {
    context.emitter.trigger('unselect', {
        jsEvent: pev ? pev.origEvent : null, // Is this always a mouse event? See #4655
        view: context.viewApi || context.calendarApi.view,
    });
}
function buildDateSpanApiWithContext(dateSpan, context) {
    let props = {};
    for (let transform of context.pluginHooks.dateSpanTransforms) {
        Object.assign(props, transform(dateSpan, context));
    }
    Object.assign(props, buildDateSpanApi(dateSpan, context.dateEnv));
    return props;
}
// Given an event's allDay status and start date, return what its fallback end date should be.
// TODO: rename to computeDefaultEventEnd
function getDefaultEventEnd(allDay, marker, context) {
    let { dateEnv, options } = context;
    let end = marker;
    if (allDay) {
        end = startOfDay(end);
        end = dateEnv.add(end, options.defaultAllDayEventDuration);
    }
    else {
        end = dateEnv.add(end, options.defaultTimedEventDuration);
    }
    return end;
}
// like getDefaultEventEnd, but edge-based: an exact start yields an exact derived end
// (real elapsed duration), so the end can't re-resolve to the wrong side of a DST fold
function getDefaultEventEndEdge(allDay, start, context) {
    if (allDay) {
        return { marker: getDefaultEventEnd(true, start.marker, context) };
    }
    return addDurationToEdge(start, context.options.defaultTimedEventDuration, context.dateEnv);
}

// applies the mutation to ALL defs/instances within the event store
function applyMutationToEventStore(eventStore, eventConfigBase, mutation, context) {
    let eventConfigs = compileEventUis(eventStore.defs, eventConfigBase);
    let dest = createEmptyEventStore();
    for (let defId in eventStore.defs) {
        let def = eventStore.defs[defId];
        dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context);
    }
    for (let instanceId in eventStore.instances) {
        let instance = eventStore.instances[instanceId];
        let def = dest.defs[instance.defId]; // important to grab the newly modified def
        dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context);
    }
    return dest;
}
function applyMutationToEventDef(eventDef, eventConfig, mutation, context) {
    let standardProps = mutation.standardProps || {};
    // if hasEnd has not been specified, guess a good value based on deltas.
    // if duration will change, there's no way the default duration will persist,
    // and thus, we need to mark the event as having a real end
    if (standardProps.hasEnd == null &&
        eventConfig.durationEditable &&
        (mutation.startDelta || mutation.endDelta)) {
        standardProps.hasEnd = true; // TODO: is this mutation okay?
    }
    let copy = {
        ...eventDef,
        ...standardProps,
        ui: { ...eventDef.ui, ...standardProps.ui }, // the only prop we want to recursively overlay
    };
    if (mutation.extendedProps) {
        copy.extendedProps = { ...copy.extendedProps, ...mutation.extendedProps };
    }
    for (let applier of context.pluginHooks.eventDefMutationAppliers) {
        applier(copy, mutation, context);
    }
    if (!copy.hasEnd && context.options.forceEventDuration) {
        copy.hasEnd = true;
    }
    return copy;
}
function applyMutationToEventInstance(eventInstance, eventDef, // must first be modified by applyMutationToEventDef
eventConfig, mutation, context) {
    let forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true;
    let clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false;
    let copy = { ...eventInstance };
    if (forceAllDay) {
        copy.range = computeAlignedDayRange(copy.range); // fresh plain range: instants stripped
    }
    if (mutation.datesDelta && eventConfig.startEditable) {
        copy.range = buildInstanceRange(addDeltaToRangeEdge(copy.range.start, copy.range.instantStartMs, mutation.datesDelta, mutation.instantDatesDeltaMs, context), addDeltaToRangeEdge(copy.range.end, copy.range.instantEndMs, mutation.datesDelta, mutation.instantDatesDeltaMs, context));
    }
    if (mutation.startDelta && eventConfig.durationEditable) {
        copy.range = buildInstanceRange(addDeltaToRangeEdge(copy.range.start, copy.range.instantStartMs, mutation.startDelta, mutation.instantStartDeltaMs, context), { marker: copy.range.end, instantMs: copy.range.instantEndMs });
    }
    if (mutation.endDelta && eventConfig.durationEditable) {
        copy.range = buildInstanceRange({ marker: copy.range.start, instantMs: copy.range.instantStartMs }, addDeltaToRangeEdge(copy.range.end, copy.range.instantEndMs, mutation.endDelta, mutation.instantEndDeltaMs, context));
    }
    if (clearEnd) {
        const startEdge = { marker: copy.range.start, instantMs: copy.range.instantStartMs };
        copy.range = buildInstanceRange(startEdge, getDefaultEventEndEdge(eventDef.allDay, startEdge, context));
    }
    // in case event was all-day but the supplied deltas were not
    // better util for this?
    if (eventDef.allDay) {
        copy.range = {
            start: startOfDay(copy.range.start),
            end: startOfDay(copy.range.end),
        };
    }
    // handle invalid durations. a timed range can be invalid civilly or in real time.
    // a real range that a DST fall-back fold civilly compresses is re-expressed (end in the
    // start's offset reading) rather than repaired away
    if (eventDef.allDay) {
        if (copy.range.end <= copy.range.start) {
            const startEdge = { marker: copy.range.start };
            copy.range = buildInstanceRange(startEdge, getDefaultEventEndEdge(true, startEdge, context));
        }
    }
    else {
        const startEdge = { marker: copy.range.start, instantMs: copy.range.instantStartMs };
        copy.range = buildValidInstanceRange(startEdge, { marker: copy.range.end, instantMs: copy.range.instantEndMs }, context.dateEnv) ?? buildInstanceRange(startEdge, getDefaultEventEndEdge(false, startEdge, context));
    }
    return copy;
}
/*
When instantDeltaMs is given, moves the edge by an exact instant amount, basing off the
edge's stored instant when present (preserves identity through DST fall-back doubled times).
The resulting marker is always a real local time in the current timeZone; ambiguous civil
times resolve deterministically. Civil (Duration-only) deltas invalidate any stored instant.
*/
function addDeltaToRangeEdge(marker, instantMs, delta, instantDeltaMs, context) {
    if (instantDeltaMs != null) {
        const newInstantMs = resolveEdgeInstantMs(marker, instantMs, context.dateEnv) + instantDeltaMs;
        return {
            marker: context.dateEnv.timestampToMarker(newInstantMs),
            instantMs: newInstantMs,
        };
    }
    // civil deltas apply to the edge's canonical civil form — a fold-compressed end's
    // stored marker is representational and must not enter civil arithmetic
    return {
        marker: context.dateEnv.add(instantMs != null ? context.dateEnv.timestampToMarker(instantMs) : marker, delta),
    };
}
function buildInstanceRange(start, end) {
    return buildEventInstanceRange(start.marker, end.marker, start.instantMs, end.instantMs);
}

class EventSourceImpl {
    constructor(context, internalEventSource) {
        this.context = context;
        this.internalEventSource = internalEventSource;
    }
    remove() {
        this.context.dispatch({
            type: 'REMOVE_EVENT_SOURCE',
            sourceId: this.internalEventSource.sourceId,
        });
    }
    refetch() {
        this.context.dispatch({
            type: 'FETCH_EVENT_SOURCES',
            sourceIds: [this.internalEventSource.sourceId],
            isRefetch: true,
        });
    }
    get id() {
        return this.internalEventSource.publicId;
    }
    get url() {
        return this.internalEventSource.meta.url;
    }
    get format() {
        return this.internalEventSource.meta.format; // TODO: bad. not guaranteed
    }
}

class EventImpl {
    // instance will be null if expressing a recurring event that has no current instances,
    // OR if trying to validate an incoming external event that has no dates assigned
    constructor(context, def, instance) {
        this._context = context;
        this._def = def;
        this._instance = instance || null;
    }
    /*
    TODO: make event struct more responsible for this
    */
    setProp(name, val) {
        if (name in EVENT_DATE_REFINERS) {
            warn(`Cannot set date-related event property \`${name}\`. Use a method instead.`);
            // TODO: make proper aliasing system?
        }
        else if (name === 'id') {
            val = EVENT_NON_DATE_REFINERS[name](val);
            this.mutate({
                standardProps: { publicId: val }, // hardcoded internal name
            });
        }
        else if (name in EVENT_NON_DATE_REFINERS) {
            val = EVENT_NON_DATE_REFINERS[name](val);
            this.mutate({
                standardProps: { [name]: val },
            });
        }
        else if (name in EVENT_UI_REFINERS) {
            let ui = EVENT_UI_REFINERS[name](val);
            if (name === 'editable') {
                ui = { startEditable: val, durationEditable: val };
            }
            else {
                ui = { [name]: val };
            }
            this.mutate({
                standardProps: { ui },
            });
        }
        else {
            warn(`Cannot set event property \`${name}\`. Use setExtendedProp instead.`);
        }
    }
    setExtendedProp(name, val) {
        this.mutate({
            extendedProps: { [name]: val },
        });
    }
    setStart(startInput, options = {}) {
        let { dateEnv } = this._context;
        let startMeta = dateEnv.createMarkerMeta(startInput);
        if (startMeta && this._instance) { // TODO: warning if parsed bad
            let instanceRange = this._instance.range;
            let startDelta = diffDates(instanceRange.start, startMeta.marker, dateEnv, options.granularity); // what if parsed bad!?
            let instantDeltaMs = computeInstantDeltaMs(startMeta, getRangeInstantStartMs(instanceRange, dateEnv), options.granularity);
            if (options.maintainDuration) {
                this.mutate({ datesDelta: startDelta, instantDatesDeltaMs: instantDeltaMs });
            }
            else {
                this.mutate({ startDelta, instantStartDeltaMs: instantDeltaMs });
            }
        }
    }
    setEnd(endInput, options = {}) {
        let { dateEnv } = this._context;
        let endMeta = null;
        if (endInput != null) {
            endMeta = dateEnv.createMarkerMeta(endInput);
            if (!endMeta) {
                return; // TODO: warning if parsed bad
            }
        }
        if (this._instance) {
            if (endMeta) {
                let instanceRange = this._instance.range;
                // canonical base: matches what a civil delta will be applied to
                let endDelta = diffDates(canonicalRangeEndMarker(instanceRange, dateEnv), endMeta.marker, dateEnv, options.granularity);
                let instantDeltaMs = computeInstantDeltaMs(endMeta, getRangeInstantEndMs(instanceRange, dateEnv), options.granularity);
                this.mutate({ endDelta, instantEndDeltaMs: instantDeltaMs });
            }
            else {
                this.mutate({ standardProps: { hasEnd: false } });
            }
        }
    }
    setDates(startInput, endInput, options = {}) {
        let { dateEnv } = this._context;
        let standardProps = { allDay: options.allDay };
        let startMeta = dateEnv.createMarkerMeta(startInput);
        let endMeta = null;
        if (!startMeta) {
            return; // TODO: warning if parsed bad
        }
        if (endInput != null) {
            endMeta = dateEnv.createMarkerMeta(endInput);
            if (!endMeta) { // TODO: warning if parsed bad
                return;
            }
        }
        if (this._instance) {
            let instanceRange = this._instance.range;
            // converting to all-day is civil-only: exact instants don't survive day-alignment
            let skipInstants = options.allDay === true;
            let instantStartDeltaMs = skipInstants ? undefined : computeInstantDeltaMs(startMeta, getRangeInstantStartMs(instanceRange, dateEnv), options.granularity);
            let instantEndDeltaMs = (skipInstants || !endMeta) ? undefined : computeInstantDeltaMs(endMeta, getRangeInstantEndMs(instanceRange, dateEnv), options.granularity);
            // when computing the diff for an event being converted to all-day,
            // compute diff off of the all-day values the way event-mutation does.
            if (options.allDay === true) {
                instanceRange = computeAlignedDayRange(instanceRange);
            }
            // canonical bases: match what civil deltas will be applied to
            let startDelta = diffDates(instanceRange.start, startMeta.marker, dateEnv, options.granularity);
            if (endMeta) {
                let endDelta = diffDates(canonicalRangeEndMarker(instanceRange, dateEnv), endMeta.marker, dateEnv, options.granularity);
                if (durationsEqual(startDelta, endDelta) && instantStartDeltaMs === instantEndDeltaMs) {
                    this.mutate({ datesDelta: startDelta, instantDatesDeltaMs: instantStartDeltaMs, standardProps });
                }
                else {
                    this.mutate({ startDelta, endDelta, instantStartDeltaMs, instantEndDeltaMs, standardProps });
                }
            }
            else { // means "clear the end"
                standardProps.hasEnd = false;
                this.mutate({ datesDelta: startDelta, instantDatesDeltaMs: instantStartDeltaMs, standardProps });
            }
        }
    }
    moveStart(deltaInput) {
        let delta = createDuration(deltaInput);
        if (delta) { // TODO: warning if parsed bad
            this.mutate({ startDelta: delta });
        }
    }
    moveEnd(deltaInput) {
        let delta = createDuration(deltaInput);
        if (delta) { // TODO: warning if parsed bad
            this.mutate({ endDelta: delta });
        }
    }
    moveDates(deltaInput) {
        let delta = createDuration(deltaInput);
        if (delta) { // TODO: warning if parsed bad
            this.mutate({ datesDelta: delta });
        }
    }
    setAllDay(allDay, options = {}) {
        let standardProps = { allDay };
        let { maintainDuration } = options;
        if (maintainDuration == null) {
            maintainDuration = this._context.options.allDayMaintainDuration;
        }
        if (this._def.allDay !== allDay) {
            standardProps.hasEnd = maintainDuration;
        }
        this.mutate({ standardProps });
    }
    formatRange(formatInput) {
        let { dateEnv } = this._context;
        let instance = this._instance;
        let formatter = createFormatter(formatInput);
        let start = buildRangeEdgeOutput(instance.range.start, instance.range.instantStartMs, dateEnv);
        if (this._def.hasEnd) {
            let end = buildRangeEdgeOutput(instance.range.end, instance.range.instantEndMs, dateEnv);
            return joinDateTimeFormatParts(dateEnv.formatRangeToParts(start.marker, end.marker, formatter, {
                startInstantMs: start.date.valueOf(),
                endInstantMs: end.date.valueOf(),
            }));
        }
        return joinDateTimeFormatParts(dateEnv.formatToParts(start.marker, formatter, {
            instantMs: start.date.valueOf(),
        }));
    }
    mutate(mutation) {
        let instance = this._instance;
        if (instance) {
            let def = this._def;
            let context = this._context;
            let { eventStore } = context.getCurrentData();
            let relevantEvents = getRelevantEvents(eventStore, instance.instanceId);
            let eventConfigBase = {
                '': {
                    display: '',
                    startEditable: true,
                    durationEditable: true,
                    constraints: [],
                    overlap: null,
                    allows: [],
                    color: '',
                    contrastColor: '',
                    className: '',
                },
            };
            relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context);
            let oldEvent = new EventImpl(context, def, instance); // snapshot
            this._def = relevantEvents.defs[def.defId];
            this._instance = relevantEvents.instances[instance.instanceId];
            context.dispatch({
                type: 'MERGE_EVENTS',
                eventStore: relevantEvents,
            });
            context.emitter.trigger('eventChange', {
                oldEvent,
                event: this,
                relatedEvents: buildEventApis(relevantEvents, context, instance),
                revert() {
                    context.dispatch({
                        type: 'RESET_EVENTS',
                        eventStore, // the ORIGINAL store
                    });
                },
            });
        }
    }
    remove() {
        let context = this._context;
        let asStore = eventApiToStore(this);
        context.dispatch({
            type: 'REMOVE_EVENTS',
            eventStore: asStore,
        });
        context.emitter.trigger('eventRemove', {
            event: this,
            relatedEvents: [],
            revert() {
                context.dispatch({
                    type: 'MERGE_EVENTS',
                    eventStore: asStore,
                });
            },
        });
    }
    get source() {
        let { sourceId } = this._def;
        if (sourceId) {
            return new EventSourceImpl(this._context, this._context.getCurrentData().eventSources[sourceId]);
        }
        return null;
    }
    get start() {
        let instance = this._instance;
        if (instance) {
            return buildRangeEdgeOutput(instance.range.start, instance.range.instantStartMs, this._context.dateEnv, this._def.allDay).date;
        }
        return null;
    }
    get end() {
        let instance = this._instance;
        if (instance && this._def.hasEnd) {
            return buildRangeEdgeOutput(instance.range.end, instance.range.instantEndMs, this._context.dateEnv, this._def.allDay).date;
        }
        return null;
    }
    get startStr() {
        let instance = this._instance;
        if (instance) {
            return buildRangeEdgeOutput(instance.range.start, instance.range.instantStartMs, this._context.dateEnv, this._def.allDay).dateStr;
        }
        return '';
    }
    get endStr() {
        let instance = this._instance;
        if (instance && this._def.hasEnd) {
            return buildRangeEdgeOutput(instance.range.end, instance.range.instantEndMs, this._context.dateEnv, this._def.allDay).dateStr;
        }
        return '';
    }
    // computable props that all access the def
    // TODO: find a TypeScript-compatible way to do this at scale
    get id() { return this._def.publicId; }
    get groupId() { return this._def.groupId; }
    get allDay() { return this._def.allDay; }
    get title() { return this._def.title; }
    get url() { return this._def.url; }
    get display() { return this._def.ui.display || 'auto'; } // bad. just normalize the type earlier
    get startEditable() { return this._def.ui.startEditable; }
    get durationEditable() { return this._def.ui.durationEditable; }
    get constraint() { return this._def.ui.constraints[0] || null; }
    get overlap() { return this._def.ui.overlap; }
    get allow() { return this._def.ui.allows[0] || null; }
    get color() { return this._def.ui.color; }
    get contrastColor() { return this._def.ui.contrastColor; }
    // NOTE: user can't modify these because Object.freeze was called in event-def parsing
    get className() { return this._def.ui.className; }
    get extendedProps() { return this._def.extendedProps; }
    toPlainObject(settings = {}) {
        let def = this._def;
        let { ui } = def;
        let { startStr, endStr } = this;
        let res = {
            allDay: def.allDay,
        };
        if (def.title) {
            res.title = def.title;
        }
        if (startStr) {
            res.start = startStr;
        }
        if (endStr) {
            res.end = endStr;
        }
        if (def.publicId) {
            res.id = def.publicId;
        }
        if (def.groupId) {
            res.groupId = def.groupId;
        }
        if (def.url) {
            res.url = def.url;
        }
        if (ui.display && ui.display !== 'auto') {
            res.display = ui.display;
        }
        // TODO: what about recurring-event properties???
        // TODO: include startEditable/durationEditable/constraint/overlap/allow
        if (ui.color) {
            res.color = ui.color;
        }
        if (ui.contrastColor) {
            res.contrastColor = ui.contrastColor;
        }
        if (ui.className) {
            res.className = ui.className;
        }
        if (Object.keys(def.extendedProps).length) {
            if (settings.collapseExtendedProps) {
                Object.assign(res, def.extendedProps);
            }
            else {
                res.extendedProps = def.extendedProps;
            }
        }
        return res;
    }
    toJSON() {
        return this.toPlainObject();
    }
}
/*
The exact-ms counterpart of a civil delta, present when the input expressed an exact
instant (ISO with offset, Date, epoch ms). Undefined means the mutation applies only the
civil delta. Skipped when a granularity is requested (whole-unit semantics).
*/
function computeInstantDeltaMs(meta, fromInstantMs, granularity) {
    return (meta.instantMs != null && !granularity)
        ? meta.instantMs - fromInstantMs
        : undefined;
}
function eventApiToStore(eventApi) {
    let def = eventApi._def;
    let instance = eventApi._instance;
    return {
        defs: { [def.defId]: def },
        instances: instance
            ? { [instance.instanceId]: instance }
            : {},
    };
}
function buildEventApis(eventStore, context, excludeInstance) {
    let { defs, instances } = eventStore;
    let eventApis = [];
    let excludeInstanceId = excludeInstance ? excludeInstance.instanceId : '';
    for (let id in instances) {
        let instance = instances[id];
        let def = defs[instance.defId];
        if (instance.instanceId !== excludeInstanceId) {
            eventApis.push(new EventImpl(context, def, instance));
        }
    }
    return eventApis;
}

/*
Specifying nextDayThreshold signals that all-day ranges should be sliced.
*/
function sliceEventStore(eventStore, eventUiBases, framingRange, nextDayThreshold) {
    let inverseBgByGroupId = {};
    let inverseBgByDefId = {};
    let defByGroupId = {};
    let bgRanges = [];
    let fgRanges = [];
    let eventUis = compileEventUis(eventStore.defs, eventUiBases);
    for (let defId in eventStore.defs) {
        let def = eventStore.defs[defId];
        let ui = eventUis[def.defId];
        if (ui.display === 'inverse-background') {
            if (def.groupId) {
                inverseBgByGroupId[def.groupId] = [];
                if (!defByGroupId[def.groupId]) {
                    defByGroupId[def.groupId] = def;
                }
            }
            else {
                inverseBgByDefId[defId] = [];
            }
        }
    }
    for (let instanceId in eventStore.instances) {
        let instance = eventStore.instances[instanceId];
        let def = eventStore.defs[instance.defId];
        let ui = eventUis[def.defId];
        let origRange = instance.range;
        let normalRange = (!def.allDay && nextDayThreshold) ?
            computeVisibleDayRange(origRange, nextDayThreshold) :
            origRange;
        let slicedRange = intersectRanges(normalRange, framingRange);
        if (slicedRange) {
            if (ui.display === 'inverse-background') {
                if (def.groupId) {
                    inverseBgByGroupId[def.groupId].push(slicedRange);
                }
                else {
                    inverseBgByDefId[instance.defId].push(slicedRange);
                }
            }
            else if (ui.display !== 'none') {
                (ui.display === 'background' ? bgRanges : fgRanges).push({
                    def,
                    ui,
                    instance,
                    range: buildSlicedEventRange(origRange, normalRange, slicedRange),
                    isStart: normalRange.start && normalRange.start.valueOf() === slicedRange.start.valueOf(),
                    isEnd: normalRange.end && normalRange.end.valueOf() === slicedRange.end.valueOf(),
                });
            }
        }
    }
    for (let groupId in inverseBgByGroupId) { // BY GROUP
        let ranges = inverseBgByGroupId[groupId];
        let invertedRanges = invertRanges(ranges, framingRange);
        for (let invertedRange of invertedRanges) {
            let def = defByGroupId[groupId];
            let ui = eventUis[def.defId];
            bgRanges.push({
                def,
                ui,
                instance: null,
                range: invertedRange,
                isStart: false,
                isEnd: false,
            });
        }
    }
    for (let defId in inverseBgByDefId) {
        let ranges = inverseBgByDefId[defId];
        let invertedRanges = invertRanges(ranges, framingRange);
        for (let invertedRange of invertedRanges) {
            bgRanges.push({
                def: eventStore.defs[defId],
                ui: eventUis[defId],
                instance: null,
                range: invertedRange,
                isStart: false,
                isEnd: false,
            });
        }
    }
    return { bg: bgRanges, fg: fgRanges };
}
/*
Carries instant fields from the original event range onto the sliced range, per-edge, only
for edges that were not clipped by the framing range. Only applies when the range was not
day-normalized (normalRange === origRange, i.e. timed events not subject to nextDayThreshold).
*/
function buildSlicedEventRange(origRange, normalRange, slicedRange) {
    if (normalRange !== origRange) {
        return slicedRange;
    }
    return buildEventInstanceRange(slicedRange.start, slicedRange.end, slicedRange.start.valueOf() === origRange.start.valueOf() ? origRange.instantStartMs : undefined, slicedRange.end.valueOf() === origRange.end.valueOf() ? origRange.instantEndMs : undefined);
}
function hasBgRendering(def) {
    return def.ui.display === 'background' || def.ui.display === 'inverse-background';
}
function setElEventRange(el, eventRange) {
    el.fcEventRange = eventRange;
}
function getElEventRange(el) {
    return el.fcEventRange ||
        el.parentNode.fcEventRange || // for the harness
        null;
}
// event ui computation
function compileEventUis(eventDefs, eventUiBases) {
    return mapHash(eventDefs, (eventDef) => compileEventUi(eventDef, eventUiBases));
}
/*
I wish we didn't need to deal with inheritance of all properties all together
I wish you could resolve just eventDisplay first, then the others
*/
function compileEventUi(eventDef, eventUiBases) {
    const uis = [];
    const fallbackBase = eventUiBases[''];
    const defBase = eventUiBases[eventDef.defId];
    if (fallbackBase) {
        uis.push(fallbackBase);
    }
    if (defBase) {
        uis.push(defBase);
    }
    uis.push(eventDef.ui);
    return combineEventUis(uis);
}
function sortEventSegs(segs, eventOrderSpecs) {
    let objs = segs.map(buildSegCompareObj);
    objs.sort((obj0, obj1) => compareByFieldSpecs(obj0, obj1, eventOrderSpecs)); // !!!
    return objs.map((c) => c._seg);
}
// returns a object with all primitive props that can be compared
function buildSegCompareObj(seg) {
    let { eventRange } = seg;
    let eventDef = eventRange.def;
    let range = eventRange.instance ? eventRange.instance.range : eventRange.range;
    let start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events
    let end = range.end ? range.end.valueOf() : 0; // "
    return {
        ...eventDef.extendedProps,
        ...eventDef,
        id: eventDef.publicId,
        start,
        end,
        duration: end - start,
        allDay: Number(eventDef.allDay),
        _seg: seg, // for later retrieval
    };
}
function computeEventRangeDraggable(eventRange, context) {
    let { pluginHooks } = context;
    let transformers = pluginHooks.isDraggableTransformers;
    let { def, ui } = eventRange;
    let val = ui.startEditable;
    for (let transformer of transformers) {
        val = transformer(val, def, ui, context);
    }
    return val;
}
/*
slicedStart/slicedEnd are optionally supplied to signal where breaks occur in view-specific segment
a better approach is to always slice with dates and always supply this argument,
however, daygrid only slices by row/col
*/
function buildEventRangeTimeText(timeFormat, eventRange, // timed/whole-day span
slicedStart, // view-sliced timed/whole-day span
slicedEnd, // view-sliced timed/whole-day span
isStart, isEnd, context, defaultDisplayEventTime = true, defaultDisplayEventEnd = true) {
    const { dateEnv, options } = context;
    const { def } = eventRange;
    const { range } = eventRange.instance;
    const canonicalStart = buildRangeEdgeOutput(range.start, range.instantStartMs, dateEnv);
    const canonicalEnd = buildRangeEdgeOutput(range.end, range.instantEndMs, dateEnv);
    let { displayEventTime, displayEventEnd } = options;
    if (displayEventTime == null) {
        displayEventTime = defaultDisplayEventTime !== false;
    }
    if (displayEventEnd == null) {
        displayEventEnd = defaultDisplayEventEnd !== false;
    }
    const startDate = (!isStart &&
        slicedStart &&
        // if seg is the first seg, but start-date cut-off by slotMinTime, (technically isStart=false)
        // we still want to display the original start-time
        startOfDay(slicedStart).valueOf() !== startOfDay(canonicalStart.marker).valueOf())
        ? slicedStart
        : canonicalStart.marker;
    const endDate = (!isEnd &&
        slicedEnd &&
        // See above HACK, but for end-time
        startOfDay(addMs(slicedEnd, -1)).valueOf() !== startOfDay(addMs(canonicalEnd.marker, -1)).valueOf())
        ? slicedEnd
        : canonicalEnd.marker;
    const startInstantMs = startDate === canonicalStart.marker ? canonicalStart.date.valueOf() : undefined;
    const endInstantMs = endDate === canonicalEnd.marker ? canonicalEnd.date.valueOf() : undefined;
    if (displayEventTime && !def.allDay) {
        if (displayEventEnd && (isStart || isEnd) && def.hasEnd) {
            // TODO: put this functionality in @full-ui/headless-calendar ?
            const rangeParts = dateEnv.formatRangeToParts(startDate, endDate, timeFormat, {
                startInstantMs,
                endInstantMs,
            });
            const multiDaySeparator = detectMultiDayTimes(rangeParts);
            // NOTE: produces strings like '12:00pm - 1:00pm', without condensing dayPeriod,
            // but that's okay since it's technically a different dayPeriod on a different day
            if (multiDaySeparator != null) {
                return joinDateTimeFormatParts(dateEnv.formatToParts(startDate, timeFormat, {
                    instantMs: startInstantMs,
                })) +
                    multiDaySeparator +
                    joinDateTimeFormatParts(dateEnv.formatToParts(endDate, timeFormat, {
                        instantMs: endInstantMs,
                    }));
            }
            return joinDateTimeFormatParts(rangeParts);
        }
        if (isStart) {
            return joinDateTimeFormatParts(dateEnv.formatToParts(startDate, timeFormat, {
                instantMs: startInstantMs,
            }));
        }
    }
    return '';
}
const dateUnits = new Set(['year', 'month', 'day']); // TODO: DRY
function detectMultiDayTimes(parts) {
    let sharedPart;
    let hasDatePart = false;
    for (const part of parts) {
        if (part.source === 'shared') {
            sharedPart = part;
        }
        if (dateUnits.has(part.type)) {
            hasDatePart = true;
        }
    }
    return hasDatePart ? sharedPart.value : undefined;
}
function getEventRangeMeta(eventRange, todayRange, nowDate, nowMs) {
    let segRange = eventRange.range;
    return {
        isPast: segRange.instantEndMs != null && nowMs != null
            ? segRange.instantEndMs <= nowMs
            : segRange.end <= (nowDate || todayRange.start),
        isFuture: segRange.instantStartMs != null && nowMs != null
            ? segRange.instantStartMs >= nowMs
            : segRange.start >= (nowDate || todayRange.end),
        isToday: todayRange && rangeContainsMarker(todayRange, segRange.start),
    };
}
function buildEventRangeKey(eventRange) {
    return eventRange.instance
        ? eventRange.instance.instanceId
        : `${eventRange.def.defId}:${eventRange.range.start.toISOString()}`;
    // inverse-background events don't have specific instances. TODO: better solution
}
function getEventTagAndAttrs(eventRange, context) {
    let { def, instance } = eventRange;
    let { url } = def;
    if (url) {
        return ['a', { href: url }, true];
    }
    let { emitter, options } = context;
    let { eventInteractive } = options;
    if (eventInteractive == null) {
        eventInteractive = def.interactive;
        if (eventInteractive == null) {
            eventInteractive = Boolean(emitter.hasHandlers('eventClick'));
        }
    }
    let attrs;
    // mock what happens in EventClicking
    if (eventInteractive) {
        // only attach keyboard-related handlers because click handler is already done in EventClicking
        attrs = createAriaKeyboardAttrs((ev) => {
            emitter.trigger('eventClick', {
                el: ev.target,
                event: new EventImpl(context, def, instance),
                jsEvent: ev,
                view: context.viewApi,
            });
        });
        attrs = { role: 'button', ...attrs };
    }
    return ['div', attrs, eventInteractive];
}

export { getRangeInstantEndMs as $, formatWithOrdinals as A, BASE_OPTION_DEFAULTS as B, CALENDAR_LISTENER_REFINERS as C, getElEventRange as D, EVENT_UI_REFINERS as E, listenBySelector as F, listenToHoverBySelector as G, isArraysEqual as H, isPropsEqualShallow as I, computeViewBorderless as J, isPropsEqualWithMap as K, createAriaClickAttrs as L, setElEventRange as M, buildEventRangeTimeText as N, getEventTagAndAttrs as O, computeEventRangeDraggable as P, refineClassName as Q, mergeMaybePropsDepth1 as R, getUnequalProps as S, computeVisibleDayRange as T, filterEventStoreDefs as U, VIEW_ONLY_OPTION_REFINERS as V, excludeSubEventStore as W, expandRecurring as X, mergeEventStores as Y, buildValidInstanceRange as Z, buildEventInstanceRange as _, buildEventApis as a, getRangeInstantStartMs as a0, enableCursor as a1, disableCursor as a2, getDateSpanInstantStartMs as a3, getDateSpanInstantEndMs as a4, compareNumbers as a5, getRelevantEvents as a6, applyMutationToEventStore as a7, addDeltaToRangeEdge as a8, preventSelection as a9, addDurationToEdge as aA, getDefaultEventEndEdge as aB, createEventInstance as aC, preventContextMenu as aa, allowSelection as ab, allowContextMenu as ac, isDateSpansEqual as ad, buildRangeEdgeOutput as ae, diffDates as af, whenTransitionDone as ag, sortEventSegs as ah, getEventRangeMeta as ai, compileEventUis as aj, instanceRangesIntersect as ak, buildDateSpanApiWithContext as al, instanceRangeContainsRange as am, fabricateEventRange as an, flatArray as ao, buildEventRangeKey as ap, flatMapArray as aq, combineEventUis as ar, hasBgRendering as as, compareByFieldSpecs as at, flexibleCompare as au, parseFieldSpecs as av, refineClassNameGenerator as aw, removeExact as ax, refineEventDef as ay, parseEventDef as az, buildRangeApiWithTimeZone as b, createEventUi as c, arrayToHash as d, createFormatter as e, filterHash as f, guid as g, hashValuesToArray as h, identity as i, createEmptyEventStore as j, BASE_OPTION_REFINERS as k, CALENDAR_ONLY_OPTION_REFINERS as l, COMPLEX_OPTION_COMPARATORS as m, mapHash as n, parseDateSpan as o, parseEvents as p, triggerDateUnselect as q, refineProps as r, sliceEventStore as s, triggerDateSelect as t, EventImpl as u, eventTupleToStore as v, warn as w, EventSourceImpl as x, parseEvent as y, eventApiToStore as z };