UNPKG

fullcalendar

Version:

FullCalendar Vanilla JS package for rendering a calendar

2,030 lines 89.3 kB
import { o as organizeRawLocales, b as buildLocale, i as initEventSources, r as reduceEventSources, c as computeEventSourcesLoading, d as reduceEventSourcesNewTimeZone, a as globalPlugins, p as parseEventSource } from './71d3d952.js';
import { m as memoize, a as memoizeObjArg, N as NowTimerRunner, f as flushSyncWithSizeBatching, g as generateClassName, P as PureComponent, b as buildViewContext, c as getIsHeightAuto, V as ViewContextType, B as BaseComponent, C as ContentContainer } from './2d6c825e.js';
import { p as parseEvents, j as createEmptyEventStore, B as BASE_OPTION_DEFAULTS, k as BASE_OPTION_REFINERS, C as CALENDAR_LISTENER_REFINERS, l as CALENDAR_ONLY_OPTION_REFINERS, m as COMPLEX_OPTION_COMPARATORS, V as VIEW_ONLY_OPTION_REFINERS, n as mapHash, c as createEventUi, w as warn, e as createFormatter, o as parseDateSpan, t as triggerDateSelect, q as triggerDateUnselect, u as EventImpl, v as eventTupleToStore, x as EventSourceImpl, h as hashValuesToArray, y as parseEvent, z as eventApiToStore, a as buildEventApis, A as formatWithOrdinals, D as getElEventRange, F as listenBySelector, G as listenToHoverBySelector, H as isArraysEqual, I as isPropsEqualShallow, J as computeViewBorderless } from './642eba18.js';
import { m as mergeCalendarOptions, i as isMergedPropsEqual, a as mergeViewOptionsMap } from './28b4e459.js';
import { rangeContainsMarker, DateEnv, createDuration, joinDateTimeFormatParts, greatestDurationDenominator, diffWholeDays } from '@full-ui/headless-calendar';
import { jsxs, jsx, Fragment } from 'preact/jsx-runtime';
import { j as joinClassNames } from './c912f986.js';
import { D as DateProfileGenerator } from './54e239b5.js';
import { r as reduceEventStore, a as rezoneEventStoreDates, I as Interaction, p as parseInteractionSettings, i as interactionSettingsStore } from './b00ce53f.js';
import { E as Emitter } from './9e9ef97a.js';
import { c as classNames } from './3b4e987d.js';
import { Component, Fragment as Fragment$1, createElement } from 'preact/compat';

// TODO: easier way to add new hooks? need to update a million things
function refinePluginDef(input) {
    return {
        name: input.name,
        premiumReleaseDate: input.premiumReleaseDate ? new Date(input.premiumReleaseDate) : undefined,
        reducers: input.reducers || [],
        isLoadingFuncs: input.isLoadingFuncs || [],
        contextInit: [].concat(input.contextInit || []),
        eventRefiners: input.eventRefiners || {},
        eventDefMemberAdders: input.eventDefMemberAdders || [],
        eventSourceRefiners: input.eventSourceRefiners || {},
        isDraggableTransformers: input.isDraggableTransformers || [],
        eventDragMutationMassagers: input.eventDragMutationMassagers || [],
        eventDefMutationAppliers: input.eventDefMutationAppliers || [],
        dateSelectionTransformers: input.dateSelectionTransformers || [],
        datePointTransforms: input.datePointTransforms || [],
        dateSpanTransforms: input.dateSpanTransforms || [],
        views: input.views || {},
        viewPropsTransformers: input.viewPropsTransformers || [],
        isPropsValid: input.isPropsValid || null,
        externalDefTransforms: input.externalDefTransforms || [],
        viewContainerAppends: input.viewContainerAppends || [],
        eventDropTransformers: input.eventDropTransformers || [],
        componentInteractions: input.componentInteractions || [],
        calendarInteractions: input.calendarInteractions || [],
        eventSourceDefs: input.eventSourceDefs || [],
        cmdFormatter: input.cmdFormatter,
        recurringTypes: input.recurringTypes || [],
        initialView: input.initialView || '',
        elementDraggingImpl: input.elementDraggingImpl,
        optionChangeHandlers: input.optionChangeHandlers || {},
        scrollerSyncerClass: input.scrollerSyncerClass || null,
        listenerRefiners: input.listenerRefiners || {},
        optionRefiners: input.optionRefiners || {},
        optionDefaults: input.optionDefaults ? [input.optionDefaults] : [],
        propSetHandlers: input.propSetHandlers || {},
    };
}
function buildPluginHooks(pluginDefs, globalDefs) {
    let pluginsByName = {};
    let hooks = {
        premiumReleaseDate: undefined,
        reducers: [],
        isLoadingFuncs: [],
        contextInit: [],
        eventRefiners: {},
        eventDefMemberAdders: [],
        eventSourceRefiners: {},
        isDraggableTransformers: [],
        eventDragMutationMassagers: [],
        eventDefMutationAppliers: [],
        dateSelectionTransformers: [],
        datePointTransforms: [],
        dateSpanTransforms: [],
        views: {},
        viewPropsTransformers: [],
        isPropsValid: null,
        externalDefTransforms: [],
        viewContainerAppends: [],
        eventDropTransformers: [],
        componentInteractions: [],
        calendarInteractions: [],
        eventSourceDefs: [],
        cmdFormatter: null,
        recurringTypes: [],
        initialView: '',
        elementDraggingImpl: null,
        optionChangeHandlers: {},
        scrollerSyncerClass: null,
        listenerRefiners: {},
        optionRefiners: {},
        optionDefaults: [],
        propSetHandlers: {},
    };
    /*
    IDs/names, etc
    */
    function addDefs(defs) {
        for (let unrefinedDef of defs) {
            const { name } = unrefinedDef;
            if (!name) {
                throw new Error('Plugin must specify a name');
            }
            if (!pluginsByName[name]) {
                const def = pluginsByName[name] = refinePluginDef(unrefinedDef);
                hooks = combineHooks(hooks, def);
                addDefs(unrefinedDef.deps || []);
            }
        }
    }
    if (pluginDefs) { // how could this be undefined?
        addDefs(pluginDefs);
    }
    addDefs(globalDefs); // GLOBAL plugins
    return hooks;
}
function buildBuildPluginHooks() {
    let currentOverrideDefs = [];
    let currentGlobalDefs = [];
    let currentHooks;
    return (overrideDefs, globalDefs) => {
        if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) {
            currentHooks = buildPluginHooks(overrideDefs, globalDefs);
        }
        currentOverrideDefs = overrideDefs;
        currentGlobalDefs = globalDefs;
        return currentHooks;
    };
}
function combineHooks(hooks0, hooks1) {
    return {
        premiumReleaseDate: compareOptionalDates(hooks0.premiumReleaseDate, hooks1.premiumReleaseDate),
        reducers: hooks0.reducers.concat(hooks1.reducers),
        isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs),
        contextInit: hooks0.contextInit.concat(hooks1.contextInit),
        eventRefiners: { ...hooks0.eventRefiners, ...hooks1.eventRefiners },
        eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders),
        eventSourceRefiners: { ...hooks0.eventSourceRefiners, ...hooks1.eventSourceRefiners },
        isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers),
        eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers),
        eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers),
        dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers),
        datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms),
        dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms),
        views: mergeViewOptionsMap(hooks0.views, hooks1.views),
        viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers),
        isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid,
        externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms),
        viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends),
        eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers),
        calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions),
        componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions),
        eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs),
        cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter,
        recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes),
        initialView: hooks0.initialView || hooks1.initialView, // put earlier plugins FIRST
        elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl, // "
        optionChangeHandlers: { ...hooks0.optionChangeHandlers, ...hooks1.optionChangeHandlers },
        scrollerSyncerClass: hooks0.scrollerSyncerClass || hooks1.scrollerSyncerClass,
        listenerRefiners: { ...hooks0.listenerRefiners, ...hooks1.listenerRefiners },
        optionRefiners: { ...hooks0.optionRefiners, ...hooks1.optionRefiners },
        optionDefaults: hooks0.optionDefaults.concat(hooks1.optionDefaults),
        propSetHandlers: { ...hooks0.propSetHandlers, ...hooks1.propSetHandlers },
    };
}
function compareOptionalDates(date0, date1) {
    if (date0 === undefined) {
        return date1;
    }
    if (date1 === undefined) {
        return date0;
    }
    return new Date(Math.max(date0.valueOf(), date1.valueOf()));
}

function compileViewDefs(defaultConfigs, overrideConfigs) {
    let hash = {};
    let viewType;
    for (viewType in defaultConfigs) {
        ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs);
    }
    for (viewType in overrideConfigs) {
        ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs);
    }
    return hash;
}
function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) {
    if (hash[viewType]) {
        return hash[viewType];
    }
    let viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs);
    if (viewDef) {
        hash[viewType] = viewDef;
    }
    return viewDef;
}
function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) {
    let defaultConfig = defaultConfigs[viewType];
    let overrideConfig = overrideConfigs[viewType];
    let queryProp = (name) => ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] :
        ((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null));
    let theComponent = queryProp('component');
    let superType = queryProp('superType');
    let superDef = null;
    if (superType) {
        if (superType === viewType) {
            throw new Error('Can\'t have a custom view type that references itself');
        }
        superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs);
    }
    if (!theComponent && superDef) {
        theComponent = superDef.component;
    }
    if (!theComponent) {
        return null; // don't throw a warning, might be settings for a single-unit view
    }
    return {
        type: viewType,
        component: theComponent,
        defaults: mergeCalendarOptions(superDef ? superDef.defaults : {}, defaultConfig ? defaultConfig.rawOptions : {}),
        overrides: mergeCalendarOptions(superDef ? superDef.overrides : {}, overrideConfig ? overrideConfig.rawOptions : {}),
    };
}

function parseViewConfigs(inputs) {
    return mapHash(inputs, parseViewConfig);
}
function parseViewConfig(input) {
    let rawOptions = typeof input === 'function' ?
        { component: input } :
        input;
    let { component } = rawOptions;
    if (rawOptions.content) {
        component = createViewHookComponent(rawOptions.content);
    }
    else if (component && !(component.prototype instanceof BaseComponent)) {
        // WHY?: people were using `component` property for `content`
        // TODO: converge on one setting name
        component = createViewHookComponent(component);
    }
    return {
        superType: rawOptions.type,
        component: component,
        rawOptions, // includes type and component too :(
    };
}
/*
TODO: converge with ViewContainer
*/
function createViewHookComponent(contentGenerator) {
    return (viewProps) => (jsx(ViewContextType.Consumer, { children: (context) => {
            const { options, viewSpec } = context;
            const renderProps = {
                // the "extra" props, for sliceEvents...
                ...viewProps,
                nextDayThreshold: options.nextDayThreshold,
                // ViewDisplayInfo...
                ...computeViewBorderless(options),
                options: { headerToolbar: options.headerToolbar, footerToolbar: options.footerToolbar },
                isHeightAuto: getIsHeightAuto(options),
                view: context.viewApi,
            };
            return (jsx(ContentContainer, { tag: "div", className: joinClassNames(generateClassName(options.viewClass, renderProps), 
                // WORKAROUND for way calendar's className would get merged into view's className
                generateClassName(viewSpec.optionDefaults.class, renderProps), generateClassName(viewSpec.optionDefaults.className, renderProps), generateClassName(viewSpec.optionOverrides.class, renderProps), generateClassName(viewSpec.optionOverrides.className, renderProps)), renderProps: renderProps, generatorName: undefined, customGenerator: contentGenerator, didMount: options.didMount || options.viewDidMount, willUnmount: options.willUnmount || options.viewWillUnmount }));
        } }));
}

function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides) {
    let defaultConfigs = parseViewConfigs(defaultInputs);
    let overrideConfigs = parseViewConfigs(optionOverrides.views);
    let viewDefs = compileViewDefs(defaultConfigs, overrideConfigs);
    return mapHash(viewDefs, (viewDef) => buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides));
}
function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides) {
    let durationInput = viewDef.overrides.duration ||
        viewDef.defaults.duration ||
        dynamicOptionOverrides.duration ||
        optionOverrides.duration;
    let duration = null;
    let durationUnit = '';
    let singleUnit = '';
    let singleUnitOverrides = {};
    if (durationInput) {
        duration = createDurationCached(durationInput);
        if (duration) { // valid?
            let denom = greatestDurationDenominator(duration);
            durationUnit = denom.unit;
            if (denom.value === 1) {
                singleUnit = durationUnit;
                singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {};
            }
        }
    }
    return {
        type: viewDef.type,
        component: viewDef.component,
        duration,
        durationUnit,
        singleUnit,
        optionDefaults: viewDef.defaults,
        optionOverrides: { ...singleUnitOverrides, ...viewDef.overrides },
    };
}
// hack to get memoization working
let durationInputMap = {};
function createDurationCached(durationInput) {
    let json = JSON.stringify(durationInput);
    let res = durationInputMap[json];
    if (res === undefined) {
        res = createDuration(durationInput);
        durationInputMap[json] = res;
    }
    return res;
}

function reduceViewType(viewType, action) {
    switch (action.type) {
        case 'CHANGE_VIEW_TYPE':
            viewType = action.viewType;
    }
    return viewType;
}

function reduceCurrentDate(currentDate, action) {
    switch (action.type) {
        case 'CHANGE_DATE':
            return action.dateMarker;
        default:
            return currentDate;
    }
}
// should be initialized once and stay constant
// this will change too
function getInitialDate(options, dateEnv, nowManager) {
    let initialDateInput = options.initialDate;
    // compute the initial ambig-timezone date
    if (initialDateInput != null) {
        return dateEnv.createMarker(initialDateInput);
    }
    return nowManager.getDateMarker();
}

function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) {
    switch (action.type) {
        case 'SET_OPTION':
            return { ...dynamicOptionOverrides, [action.optionName]: action.rawOptionValue };
        default:
            return dynamicOptionOverrides;
    }
}

function reduceDateProfile(currentDateProfile, action, currentDate, nowDate, dateProfileGenerator) {
    let dp;
    switch (action.type) {
        case 'CHANGE_VIEW_TYPE':
            return dateProfileGenerator.build(action.dateMarker || currentDate, nowDate);
        case 'CHANGE_DATE':
            return dateProfileGenerator.build(action.dateMarker, nowDate);
        case 'PREV':
            dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate, nowDate);
            if (dp.isValid) {
                return dp;
            }
            break;
        case 'NEXT':
            dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate, nowDate);
            if (dp.isValid) {
                return dp;
            }
            break;
    }
    return currentDateProfile;
}

function reduceDateSelection(currentSelection, action) {
    switch (action.type) {
        case 'UNSELECT_DATES':
            return null;
        case 'SELECT_DATES':
            return action.selection;
        default:
            return currentSelection;
    }
}

function reduceSelectedEvent(currentInstanceId, action) {
    switch (action.type) {
        case 'UNSELECT_EVENT':
            return '';
        case 'SELECT_EVENT':
            return action.eventInstanceId;
        default:
            return currentInstanceId;
    }
}

function reduceEventDrag(currentDrag, action) {
    let newDrag;
    switch (action.type) {
        case 'UNSET_EVENT_DRAG':
            return null;
        case 'SET_EVENT_DRAG':
            newDrag = action.state;
            return {
                affectedEvents: newDrag.affectedEvents,
                mutatedEvents: newDrag.mutatedEvents,
                isEvent: newDrag.isEvent,
            };
        default:
            return currentDrag;
    }
}

function reduceEventResize(currentResize, action) {
    let newResize;
    switch (action.type) {
        case 'UNSET_EVENT_RESIZE':
            return null;
        case 'SET_EVENT_RESIZE':
            newResize = action.state;
            return {
                affectedEvents: newResize.affectedEvents,
                mutatedEvents: newResize.mutatedEvents,
                isEvent: newResize.isEvent,
            };
        default:
            return currentResize;
    }
}

function parseToolbars(calendarOptions, viewSpecs, calendarApi) {
    let header = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, viewSpecs, calendarApi) : null;
    let footer = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, viewSpecs, calendarApi) : null;
    return { header, footer };
}
function parseToolbar(sectionStrHash, calendarOptions, viewSpecs, calendarApi) {
    let isRtl = calendarOptions.direction === 'rtl';
    let viewsWithButtons = [];
    let hasTitle = false;
    function processSectionStr(sectionStr) {
        let sectionRes = parseSection(sectionStr, calendarOptions, viewSpecs, calendarApi);
        viewsWithButtons.push(...sectionRes.viewsWithButtons);
        hasTitle = hasTitle || sectionRes.hasTitle;
        return sectionRes.widgets;
    }
    const sectionWidgets = {
        start: processSectionStr(sectionStrHash[isRtl ? 'right' : 'left'] || sectionStrHash.start || ''),
        center: processSectionStr(sectionStrHash.center || ''),
        end: processSectionStr(sectionStrHash[isRtl ? 'left' : 'right'] || sectionStrHash.end || ''),
    };
    return {
        sectionWidgets,
        viewsWithButtons,
        hasTitle,
    };
}
/*
BAD: querying icons and text here. should be done at render time
*/
function parseSection(sectionStr, calendarOptions, viewSpecs, calendarApi) {
    let calendarButtons = calendarOptions.buttons || {};
    let customElements = calendarOptions.toolbarElements || {};
    let sectionSubstrs = sectionStr ? sectionStr.split(' ') : [];
    let viewsWithButtons = [];
    let hasTitle = false;
    let widgets = sectionSubstrs.map((buttonGroupStr) => (buttonGroupStr.split(',').map((name) => {
        if (name === 'title') {
            hasTitle = true;
            return { name };
        }
        if (customElements[name]) {
            return { name, customElement: customElements[name] };
        }
        let viewSpec;
        let buttonInput = calendarButtons[name] || {};
        let buttonText;
        let buttonHint;
        let buttonClick;
        if ((viewSpec = viewSpecs[name])) {
            viewsWithButtons.push(name);
            const buttonTextKey = viewSpec.optionDefaults.buttonTextKey;
            buttonText = buttonInput.text ||
                (buttonTextKey ? calendarOptions[buttonTextKey] : '') ||
                (viewSpec.singleUnit
                    ? (calendarOptions[viewSpec.singleUnit + 'TextLong'] ||
                        calendarOptions[viewSpec.singleUnit + 'Text'])
                    : '') ||
                name;
            /*
            buttons{}.hint(viewButtonText, viewName)
            viewHint(viewButtonText, viewName)
            */
            buttonHint = formatWithOrdinals(buttonInput.hint || calendarOptions.viewHint, [buttonText, name], // ordinal arguments
            buttonText);
            buttonClick = (ev) => {
                buttonInput?.click?.(ev);
                if (!ev.defaultPrevented) {
                    calendarApi.changeView(name);
                }
            };
        }
        else {
            buttonText = buttonInput.text ||
                calendarOptions[name + 'TextLong'] ||
                calendarOptions[name + 'Text'] ||
                name;
            /*
            buttons{}.hint(currentUnitText, currentUnit)
            prevHint(currentUnitUnitext, currentUnit)
            nextHint -- same
            todayHint -- same
            */
            if (name === 'prevYear') {
                buttonHint = formatWithOrdinals(buttonInput.hint || calendarOptions.prevHint, [calendarOptions.yearText, 'year'], buttonText);
            }
            else if (name === 'nextYear') {
                buttonHint = formatWithOrdinals(buttonInput.hint || calendarOptions.nextHint, [calendarOptions.yearText, 'year'], buttonText);
            }
            else {
                buttonHint = (currentUnit) => {
                    return formatWithOrdinals(buttonInput.hint || calendarOptions[name + 'Hint'], // todayHint/prevHint/nextHint
                    [
                        calendarOptions[currentUnit + 'TextLong'] ||
                            calendarOptions[currentUnit + 'Text'],
                        currentUnit
                    ], buttonText);
                };
            }
            buttonClick = (ev) => {
                buttonInput?.click?.(ev);
                if (!ev.defaultPrevented) {
                    calendarApi[name]?.();
                }
            };
        }
        return {
            name,
            isView: Boolean(viewSpec),
            buttonText,
            buttonHint,
            buttonDisplay: buttonInput.display,
            buttonIconClass: buttonInput.iconClass,
            buttonIconContent: buttonInput.iconContent,
            buttonClick,
            buttonIsPrimary: buttonInput.isPrimary || false,
            buttonClass: buttonInput.class ?? buttonInput.className,
            buttonDidMount: buttonInput.didMount,
            buttonWillUnmount: buttonInput.willUnmount,
        };
    })));
    return { widgets, viewsWithButtons, hasTitle };
}

// always represents the current view. otherwise, it'd need to change value every time date changes
class ViewImpl {
    constructor(type, getCurrentData, dateEnv) {
        this.type = type;
        this.getCurrentData = getCurrentData;
        this.dateEnv = dateEnv;
    }
    get calendar() {
        return this.getCurrentData().calendarApi;
    }
    get title() {
        return this.getCurrentData().viewTitle;
    }
    get activeStart() {
        return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start);
    }
    get activeEnd() {
        return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end);
    }
    get currentStart() {
        return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start);
    }
    get currentEnd() {
        return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end);
    }
    getOption(name) {
        return this.getCurrentData().options[name]; // are the view-specific options
    }
}

const DEF_DEFAULTS = {
    startTime: '09:00',
    endTime: '17:00',
    daysOfWeek: [1, 2, 3, 4, 5], // monday - friday
    display: 'inverse-background',
    className: '', // TODO: remove
    groupId: '_businessHours', // so multiple defs get grouped
};
/*
TODO: pass around as EventDefHash!!!
*/
function parseBusinessHours(input, context) {
    return parseEvents(refineInputs(input), null, context);
}
function refineInputs(input) {
    let rawDefs;
    if (input === true) {
        rawDefs = [{}]; // will get DEF_DEFAULTS verbatim
    }
    else if (Array.isArray(input)) {
        // if specifying an array, every sub-definition NEEDS a day-of-week
        rawDefs = input.filter((rawDef) => rawDef.daysOfWeek);
    }
    else if (typeof input === 'object' && input) { // non-null object
        rawDefs = [input];
    }
    else { // is probably false
        rawDefs = [];
    }
    rawDefs = rawDefs.map((rawDef) => ({ ...DEF_DEFAULTS, ...rawDef }));
    return rawDefs;
}

// Computes what the title at the top of the calendarApi should be for this view
function buildTitle(dateProfile, viewOptions, dateEnv) {
    let range;
    // for views that span a large unit of time, show the proper interval, ignoring stray days before and after
    if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) {
        range = dateProfile.currentRange;
    }
    else { // for day units or smaller, use the actual day range
        range = dateProfile.activeRange;
    }
    let parts;
    const options = { isEndExclusive: dateProfile.isRangeAllDay };
    if (viewOptions.titleFormat) {
        parts = dateEnv.formatRangeToParts(range.start, range.end, createFormatter(viewOptions.titleFormat), options);
    }
    else {
        parts = dateEnv.formatRangeToParts(range.start, range.end, createFormatter(buildTitleFormat(dateProfile, viewOptions.disallowAmbigTitle, 'long')), options);
        if (hasTwoMonths(parts)) {
            parts = dateEnv.formatRangeToParts(range.start, range.end, createFormatter(buildTitleFormat(dateProfile, viewOptions.disallowAmbigTitle, 'short')), options);
        }
    }
    return joinDateTimeFormatParts(parts);
}
// Generates the format string that should be used to generate the title for the current date range.
// Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`.
function buildTitleFormat(dateProfile, disallowAmbigTitle, monthFormat) {
    const { currentRangeUnit } = dateProfile;
    if (currentRangeUnit === 'year') {
        return { year: 'numeric' };
    }
    if (currentRangeUnit === 'month') {
        return { year: 'numeric', month: monthFormat };
    }
    if (!disallowAmbigTitle) {
        const days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end);
        if (days !== null && days > 1) {
            return {
                year: 'numeric',
                month: monthFormat,
            };
        }
    }
    // one day. longer, like "September 9 2014"
    return { year: 'numeric', month: 'long', day: 'numeric' };
}
function hasTwoMonths(parts) {
    let hasStartMonth = false;
    let hasEndMonth = false;
    for (const part of parts) {
        if (part.type === 'month') {
            if (part.source === 'startRange')
                hasStartMonth = true;
            if (part.source === 'endRange')
                hasEndMonth = true;
        }
    }
    return hasStartMonth && hasEndMonth;
}

/*
TODO: test switching timezones when NO timezone plugin
*/
class CalendarNowManager {
    constructor() {
        this.resetListeners = new Set();
    }
    handleInput(dateEnv, // will change if timezone setup changed
    nowInput) {
        const oldDateEnv = this.dateEnv;
        if (dateEnv !== oldDateEnv) {
            if (typeof nowInput === 'function') {
                this.nowFn = nowInput;
            }
            else if (!oldDateEnv) { // first time?
                // inputs that express an exact instant (ISO with offset, Date, epoch ms) keep their
                // exact epoch (marker round-trips are ambiguous during DST folds). civil inputs
                // resolve deterministically to the first occurrence.
                this.nowAnchorDate = nowInput
                    ? resolveInputToDate(nowInput, dateEnv)
                    : new Date();
                this.nowAnchorQueried = Date.now();
            }
            this.dateEnv = dateEnv;
            // not first time? fire reset handlers
            if (oldDateEnv) {
                for (const resetListener of this.resetListeners.values()) {
                    resetListener();
                }
            }
        }
    }
    getDateMarker() {
        return this.dateEnv.timestampToMarker(this.getEpochMs());
    }
    /*
    The exact instant of "now". Unlike a DateMarker, unambiguous during DST transitions.
    When `now` was supplied as a function returning a civil time, resolves deterministically.
    */
    getEpochMs() {
        return this.nowAnchorDate
            ? this.nowAnchorDate.valueOf() + (Date.now() - this.nowAnchorQueried)
            : resolveInputToDate(this.nowFn(), this.dateEnv).valueOf();
    }
    addResetListener(handler) {
        this.resetListeners.add(handler);
    }
    removeResetListener(handler) {
        this.resetListeners.delete(handler);
    }
}
/*
Resolves a date input to an exact-instant Date. Prefers the instant the input itself
expressed (unambiguous during DST folds); falls back to first-occurrence resolution.
*/
function resolveInputToDate(input, dateEnv) {
    const meta = dateEnv.createMarkerMeta(input);
    return meta.instantMs != null
        ? new Date(meta.instantMs)
        : dateEnv.toDate(meta.marker);
}

class CalendarDataManager {
    constructor(config) {
        this.computeCurrentViewData = memoize(this._computeCurrentViewData);
        this.organizeRawLocales = memoize(organizeRawLocales);
        this.buildLocale = memoize(buildLocale);
        this.buildPluginHooks = buildBuildPluginHooks();
        this.buildDateEnv = memoize(buildDateEnv);
        this.parseToolbars = memoize(parseToolbars);
        this.buildViewSpecs = memoize(buildViewSpecs);
        this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator);
        this.buildViewApi = memoize(buildViewApi);
        this.buildViewUiProps = memoizeObjArg(buildViewUiProps);
        this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqualShallow);
        this.buildEventUiBases = memoize(buildEventUiBases);
        this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours);
        this.buildToolbarProps = memoize(buildToolbarProps);
        this.buildTitle = memoize(buildTitle);
        this.nowManager = new CalendarNowManager();
        this.isDrainingActionQueue = false;
        this.actionQueue = [];
        this.optionOverrides = {};
        // used by CalendarApiImpl
        this.emitter = new Emitter();
        this.currentCalendarOptionsRefiners = {};
        this.currentCalendarOptionsInput = {};
        this.currentCalendarOptionsRefined = {};
        this.currentViewOptionsInput = {};
        this.currentViewOptionsRefined = {};
        this.optionsForRefining = [];
        this.optionsForHandling = [];
        this.getCurrentData = () => this.data;
        this.handleNowChange = () => {
            this.dispatch({ type: 'UPDATE_NOW' });
        };
        this.dispatch = (action) => {
            this.actionQueue.push(action);
            if (!this.isDrainingActionQueue) {
                this.drainActionQueue();
            }
        };
        this.config = config;
        this.nowManager = new CalendarNowManager();
        this.nowTimer = new NowTimerRunner(this.handleNowChange);
    }
    destroy() {
        this.nowTimer.destroy();
    }
    /*
    Will NOT trigger onDataChange unless there were other actions in the queue
    */
    update(optionOverrides) {
        this.optionOverrides = optionOverrides;
        this.actionQueue.push({ type: 'IDLE' }); // ensure reducer gets called
        this.drainActionQueue();
        return this.data;
    }
    /*
    WILL trigger onDataChange
    */
    resetOptions(optionOverrides, changedOptionNames) {
        if (changedOptionNames === undefined) {
            this.optionOverrides = optionOverrides;
        }
        else {
            this.optionOverrides = { ...this.optionOverrides, ...optionOverrides };
            this.optionsForRefining.push(...changedOptionNames);
        }
        this.dispatch({ type: 'RESET_OPTIONS' });
    }
    drainActionQueue() {
        let calendarContext;
        let { state, data } = this;
        const isInit = !state;
        const { actionQueue } = this;
        const actionsComplete = []; // non-idle
        this.isDrainingActionQueue = true;
        while (actionQueue.length) {
            const action = actionQueue.shift();
            ({ state, data, calendarContext } = this.reduce(state, data, action));
            this.state = state;
            this.data = data;
            if (action.type !== 'IDLE') {
                actionsComplete.push(action);
            }
        }
        this.isDrainingActionQueue = false;
        if (isInit) {
            const controllerOption = calendarContext.options.controller;
            if (controllerOption) {
                controllerOption._setApi(this.config.calendarApi);
            }
        }
        if (!isInit && actionsComplete.length) {
            const { onDataChange } = this.config;
            if (onDataChange) {
                onDataChange(this.data, actionsComplete);
            }
        }
    }
    reduce(prevState, prevData, action) {
        let { config } = this;
        let isInit = !prevState;
        // === Compute options and view data ===
        let dynamicOptionOverrides = isInit
            ? {}
            : reduceDynamicOptionOverrides(prevState.dynamicOptionOverrides, action);
        let optionsData = this.computeOptionsData(this.optionOverrides, dynamicOptionOverrides, config.calendarApi);
        let currentViewType = isInit
            ? (optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView)
            : reduceViewType(prevState.currentViewType, action);
        let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, this.optionOverrides, dynamicOptionOverrides);
        // === Wire things up ===
        config.calendarApi.currentDataManager = this;
        this.emitter.setThisContext(config.calendarApi);
        this.emitter.setOptions(currentViewData.options);
        // === Build calendarContext ===
        let calendarContext = {
            nowManager: this.nowManager,
            dateEnv: optionsData.dateEnv,
            options: optionsData.calendarOptions,
            pluginHooks: optionsData.pluginHooks,
            calendarApi: config.calendarApi,
            dispatch: this.dispatch,
            emitter: this.emitter,
            getCurrentData: this.getCurrentData,
        };
        // === Update now timer ===
        let { nowDate } = this.nowTimer.update({
            unit: 'day',
            unitValue: 1,
            nowIndicatorSnap: 'auto',
            nowManager: this.nowManager,
            dateEnv: optionsData.dateEnv,
        });
        // === Compute currentDate ===
        let currentDate = isInit
            ? getInitialDate(optionsData.calendarOptions, optionsData.dateEnv, this.nowManager)
            : reduceCurrentDate(prevState.currentDate, action);
        // === Compute dateProfile ===
        let dateProfile;
        if (isInit) {
            dateProfile = currentViewData.dateProfileGenerator.build(currentDate, nowDate);
        }
        else {
            dateProfile = prevState.dateProfile;
            // Check for generator change
            if (prevData && prevData.dateProfileGenerator !== currentViewData.dateProfileGenerator) {
                dateProfile = currentViewData.dateProfileGenerator.build(currentDate, nowDate);
            }
            dateProfile = reduceDateProfile(dateProfile, action, currentDate, nowDate, currentViewData.dateProfileGenerator);
        }
        // === Adjust currentDate if out of range ===
        if ((action && (action.type === 'PREV' || action.type === 'NEXT')) ||
            !rangeContainsMarker(dateProfile.activeRange, currentDate)) {
            currentDate = dateProfile.currentRange.start;
        }
        // === Compute eventSources, eventStore ===
        let eventSources = isInit
            ? initEventSources(optionsData.calendarOptions, dateProfile, calendarContext)
            : reduceEventSources(prevState.eventSources, action, dateProfile, calendarContext);
        let eventStore = isInit
            ? createEmptyEventStore()
            : reduceEventStore(prevState.eventStore, action, eventSources, dateProfile, calendarContext);
        // === Compute renderableEventStore ===
        let isEventsLoading = computeEventSourcesLoading(eventSources);
        let renderableEventStore = isInit
            ? createEmptyEventStore()
            : (isEventsLoading && !currentViewData.options.progressiveEventRendering)
                ? (prevState.renderableEventStore || eventStore)
                : eventStore;
        // === UI computation ===
        let { eventUiSingleBase, selectionConfig } = this.buildViewUiProps(calendarContext);
        let eventUiBySource = this.buildEventUiBySource(eventSources);
        let eventUiBases = isInit
            ? {}
            : this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource);
        // === Build new state ===
        let newState = {
            dynamicOptionOverrides,
            currentViewType,
            currentDate,
            dateProfile,
            eventSources,
            eventStore,
            renderableEventStore,
            selectionConfig,
            eventUiBases,
            businessHours: this.parseContextBusinessHours(calendarContext),
            dateSelection: isInit ? null : reduceDateSelection(prevState.dateSelection, action),
            eventSelection: isInit ? '' : reduceSelectedEvent(prevState.eventSelection, action),
            eventDrag: isInit ? null : reduceEventDrag(prevState.eventDrag, action),
            eventResize: isInit ? null : reduceEventResize(prevState.eventResize, action),
            nowDate,
        };
        // === Plugin reducers ===
        let contextAndState = { ...calendarContext, ...newState };
        for (let reducer of optionsData.pluginHooks.reducers) {
            Object.assign(newState, reducer(prevState, action, contextAndState));
        }
        // === Loading state emission ===
        let wasLoading = prevState ? computeIsLoading(prevState, calendarContext) : false;
        let isLoading = computeIsLoading(newState, calendarContext);
        if (!wasLoading && isLoading) {
            this.emitter.trigger('loading', true);
        }
        else if (wasLoading && !isLoading) {
            this.emitter.trigger('loading', false);
        }
        // === Build CalendarData ===
        let viewTitle = this.buildTitle(dateProfile, currentViewData.options, optionsData.dateEnv);
        let toolbarProps = this.buildToolbarProps(currentViewData.viewSpec, dateProfile, currentViewData.dateProfileGenerator, currentDate, nowDate, viewTitle);
        let newData = {
            viewTitle,
            nowManager: this.nowManager,
            calendarApi: config.calendarApi,
            dispatch: this.dispatch,
            emitter: this.emitter,
            getCurrentData: this.getCurrentData,
            toolbarProps,
            ...optionsData,
            ...currentViewData,
            ...newState,
        };
        // === Handle option changes ===
        let changeHandlers = optionsData.pluginHooks.optionChangeHandlers;
        let prevCalendarOptions = prevData && prevData.calendarOptions;
        let newCalendarOptions = optionsData.calendarOptions;
        if (prevCalendarOptions && prevCalendarOptions !== newCalendarOptions) {
            if (prevCalendarOptions.timeZone !== newCalendarOptions.timeZone) {
                // HACK
                newState.eventSources = newData.eventSources = reduceEventSourcesNewTimeZone(newData.eventSources, dateProfile, newData);
                newState.eventStore = newData.eventStore = rezoneEventStoreDates(newData.eventStore, prevData.dateEnv, newData.dateEnv);
                newState.renderableEventStore = newData.renderableEventStore = rezoneEventStoreDates(newData.renderableEventStore, prevData.dateEnv, newData.dateEnv);
            }
            for (let optionName in changeHandlers) {
                if (this.optionsForHandling.indexOf(optionName) !== -1 ||
                    prevCalendarOptions[optionName] !== newCalendarOptions[optionName]) {
                    changeHandlers[optionName](newCalendarOptions[optionName], newData);
                }
            }
        }
        this.optionsForHandling = [];
        return { state: newState, data: newData, calendarContext };
    }
    computeOptionsData(optionOverrides, dynamicOptionOverrides, calendarApi) {
        // TODO: blacklist options that are handled by optionChangeHandlers
        if (!this.optionsForRefining.length &&
            optionOverrides === this.stableOptionOverrides &&
            dynamicOptionOverrides === this.stableDynamicOptionOverrides) {
            return this.stableCalendarOptionsData;
        }
        let { refinedOptions, pluginHooks, localeDefaults, availableLocaleData, } = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides);
        let dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekTextLong, refinedOptions.weekTextShort, pluginHooks, availableLocaleData);
        let viewSpecs = this.buildViewSpecs(pluginHooks.views, this.stableOptionOverrides, this.stableDynamicOptionOverrides);
        let toolbarConfig = this.parseToolbars(refinedOptions, viewSpecs, calendarApi);
        return this.stableCalendarOptionsData = {
            calendarOptions: refinedOptions,
            pluginHooks,
            dateEnv,
            viewSpecs,
            toolbarConfig,
            localeDefaults,
            availableRawLocales: availableLocaleData.map,
        };
    }
    // always called from behind a memoizer
    processRawCalendarOptions(optionOverrides, dynamicOptionOverrides) {
        let { locales, locale } = mergeCalendarOptions(BASE_OPTION_DEFAULTS, optionOverrides, dynamicOptionOverrides);
        let availableLocaleData = this.organizeRawLocales(locales);
        let availableRawLocales = availableLocaleData.map;
        let localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options;
        let pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins);
        let refiners = this.currentCalendarOptionsRefiners = {
            ...BASE_OPTION_REFINERS,
            ...CALENDAR_LISTENER_REFINERS,
            ...CALENDAR_ONLY_OPTION_REFINERS,
            ...pluginHooks.listenerRefiners,
            ...pluginHooks.optionRefiners,
        };
        let raw = mergeCalendarOptions(BASE_OPTION_DEFAULTS, ...pluginHooks.optionDefaults, localeDefaults, filterKnownOptions(mergeCalendarOptions(optionOverrides, dynamicOptionOverrides), refiners));
        let refined = {};
        let currentRaw = this.currentCalendarOptionsInput;
        let currentRefined = this.currentCalendarOptionsRefined;
        let anyChanges = false;
        for (let optionName in raw) {
            if (this.optionsForRefining.indexOf(optionName) === -1 && (raw[optionName] === currentRaw[optionName] || (COMPLEX_OPTION_COMPARATORS[optionName] &&
                (optionName in currentRaw) &&
                COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName])) || isMergedPropsEqual(currentRaw[optionName], raw[optionName]))) {
                refined[optionName] = currentRefined[optionName];
            }
            else if (refiners[optionName]) {
                refined[optionName] = refiners[optionName](raw[optionName], optionName);
                anyChanges = true;
            }
        }
        if (anyChanges) {
            this.currentCalendarOptionsInput = raw;
            this.currentCalendarOptionsRefined = refined;
            this.stableOptionOverrides = optionOverrides;
            this.stableDynamicOptionOverrides = dynamicOptionOverrides;
        }
        this.optionsForHandling.push(...this.optionsForRefining);
        this.optionsForRefining = [];
        return {
            rawOptions: this.currentCalendarOptionsInput,
            refinedOptions: this.currentCalendarOptionsRefined,
            pluginHooks,
            availableLocaleData,
            localeDefaults,
        };
    }
    _computeCurrentViewData(viewType, optionsData, optionOverrides, dynamicOptionOverrides) {
        let viewSpec = optionsData.viewSpecs[viewType];
        if (!viewSpec) {
            throw new Error(`viewType "${viewType}" is not available. Please make sure you've loaded all neccessary plugins`);
        }
        let { refinedOptions } = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides);
        this.nowManager.handleInput(optionsData.dateEnv, refinedOptions.now);
        let dateProfileGenerator = this.buildDateProfileGenerator({
            dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass,
            duration: viewSpec.duration,
            durationUnit: viewSpec.durationUnit,
            usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime,
            dateEnv: optionsData.dateEnv,
            calendarApi: this.config.calendarApi,
            slotMinTime: refinedOptions.slotMinTime,
            slotMaxTime: refinedOptions.slotMaxTime,
            showNonCurrentDates: refinedOptions.showNonCurrentDates,
            dayCount: refinedOptions.dayCount,
            dateAlignment: refinedOptions.dateAlignment,
            dateIncrement: refinedOptions.dateIncrement,
            hiddenDays: refinedOptions.hiddenDays,
            weekends: refinedOptions.weekends,
            validRangeInput: refinedOptions.validRange,
            visibleRangeInput: refinedOptions.visibleRange,
            fixedWeekCount: refinedOptions.fixedWeekCount,
        });
        let viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv);
        return { viewSpec, options: refinedOptions, dateProfileGenerator, viewApi };
    }
    processRawViewOptions(viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) {
        let refiners = {
            ...BASE_OPTION_REFINERS,
            ...CALENDAR_LISTENER_REFINERS,
            ...CALENDAR_ONLY_OPTION_REFINERS,
            ...VIEW_ONLY_OPTION_REFINERS,
            ...pluginHooks.listenerRefiners,
            ...pluginHooks.optionRefiners,
        };
        let raw = mergeCalendarOptions(BASE_OPTION_DEFAULTS, ...pluginHooks.optionDefaults, viewSpec.optionDefaults, localeDefaults, filterKnownOptions(mergeCalendarOptions(optionOverrides, viewSpec.optionOverrides, dynamicOptionOverrides), refiners));
        let refined = {};
        let currentRaw = this.currentViewOptionsInput;
        let currentRefined = this.currentViewOptionsRefined;
        let anyChanges = false;
        for (let optionName in raw) {
            if (raw[optionName] === currentRaw[optionName] || (COMPLEX_OPTION_COMPARATORS[optionName] &&
                COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], currentRaw[optionName])) || isMergedPropsEqual(currentRaw[optionName], raw[optionName])) {
                refined[optionName] = currentRefined[optionName];
            }
            else {
                if (raw[optionName] === this.currentCalendarOptionsInput[optionName] ||
                    (COMPLEX_OPTION_COMPARATORS[optionName] &&
                        COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], this.currentCalendarOptionsInput[optionName]))) {
                    if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop
                        refined[optionName] = this.currentCalendarOptionsRefined[optionName];
                    }
                }
                else if (refiners[optionName]) {
                    refined[optionName] = refiners[optionName](raw[optionName], optionName);
                }
                anyChanges = true;
            }
        }
        if (anyChanges) {
            this.currentViewOptionsInput = raw;
            this.currentViewOptionsRefined = refined;
        }
        return {
            rawOptions: this.currentViewOptionsInput,
            refinedOptions: this.currentViewOptionsRefined,
        };
    }
}
function buildDateEnv(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekTextLong, weekTextShort, pluginHooks, availableLocaleData) {
    let locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map);
    return new DateEnv({
        calendarSystem: 'gregory', // TODO: make this a setting
        timeZone,
        locale,
        weekNumberCalculation,
        firstDay,
        weekTextLong,
        weekTextShort,
        cmdFormatter: pluginHooks.cmdFormatter,
    });
}
function buildDateProfileGenerator(props) {
    let DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator;
    return new DateProfileGeneratorClass(props);
}
function buildViewApi(type, getCurrentData, dateEnv) {
    return new ViewImpl(type, getCurrentData, dateEnv);
}
function buildEventUiBySource(eventSources) {
    return mapHash(eventSources, (eventSource) => eventSource.ui);
}
/*
The result of this is processed by compileEventUi
*/
function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) {
    let eventUiBases = {
        '': eventUiSingleBase, // fallback
    };
    for (let defId in eventDefs) {
        let def = eventDefs[defId];
        if (def.sourceId && eventUiBySource[def.sourceId]) {
            eventUiBases[defId] = eventUiBySource[def.sourceId];
        }
    }
    return eventUiBases;
}
function buildViewUiProps(calendarContext) {
    const { options } = calendarContext;
    return {
        eventUiSingleBase: createEventUi({
            display: options.eventDisplay,
            editable: options.editable, // without "event" at start
            startEditable: options.eventStartEditable,
            durationEditable: options.eventDurationEditable,
            constraint: options.eventConstraint,
            overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined,
            allow: options.eventAllow,
            // color: options.eventColor, // StandardEvent/BgEvent will handle this
            // contrastColor: options.eventContrastColor, // StandardEvent/BgEvent will handle this
            // className: options.eventClass // render hook will handle this
        }, calendarContext),
        selectionConfig: createEventUi({
            constraint: options.selectConstraint,
            overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined,
            allow: options.selectAllow,
        }, calendarContext),
    };
}
function computeIsLoading(state, context) {
    for (let isLoadingFunc of context.pluginHooks.isLoadingFuncs) {
        if (isLoadingFunc(state)) {
            return true;
        }
    }
    return false;
}
function parseContextBusinessHours(calendarContext) {
    return parseBusinessHours(calendarContext.options.businessHours, calendarContext);
}
const warnedUnknownOptions = {};
function filterKnownOptions(options, optionRefiners) {
    const knownOptions = {};
    for (const optionName in options) {
        if (optionRefiners[optionName]) {
            knownOptions[optionName] = options[optionName];
        }
        else if (!warnedUnknownOptions[optionName]) {
            warn(`Unknown option \`${optionName}\`.`);
            warnedUnknownOptions[optionName] = true;
        }
    }
    return knownOptions;
}
function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, nowDate, title) {
    // don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid
    let todayInfo = dateProfileGenerator.build(nowDate, nowDate, undefined, /* forceToValid = */ false);
    let prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, nowDate, /* forceToValid = */ false);
    let nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, nowDate, /* forceToValid = */ false);
    return {
        title,
        selectedButton: viewSpec.type,
        navUnit: viewSpec.singleUnit,
        isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, nowDate),
        isPrevEnabled: prevInfo.isValid,
        isNextEnabled: nextInfo.isValid,
    };
}

class CalendarApiImpl {
    getCurrentData() {
        return this.currentDataManager.getCurrentData();
    }
    dispatch(action) {
        this.currentDataManager.dispatch(action);
    }
    get view() { return this.getCurrentData().viewApi; }
    batchRendering(callback) {
        callback();
    }
    // Options
    // -----------------------------------------------------------------------------------------------------------------
    setOption(name, val) {
        this.dispatch({
            type: 'SET_OPTION',
            optionName: name,
            rawOptionValue: val,
        });
    }
    getOption(name) {
        return this.currentDataManager.currentCalendarOptionsInput[name];
    }
    getAvailableLocaleCodes() {
        return Object.keys(this.getCurrentData().availableRawLocales);
    }
    // Trigger
    // -----------------------------------------------------------------------------------------------------------------
    on(handlerName, handler) {
        let { currentDataManager } = this;
        if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) {
            currentDataManager.emitter.on(handlerName, handler);
        }
        else {
            warn(`Unknown listener \`${handlerName}\`.`);
        }
    }
    off(handlerName, handler) {
        this.currentDataManager.emitter.off(handlerName, handler);
    }
    // not meant for public use
    trigger(handlerName, ...args) {
        this.currentDataManager.emitter.trigger(handlerName, ...args);
    }
    // View
    // -----------------------------------------------------------------------------------------------------------------
    changeView(viewType, dateOrRange) {
        this.batchRendering(() => {
            this.unselect();
            if (dateOrRange) {
                if (dateOrRange.start && dateOrRange.end) { // a range
                    this.dispatch({
                        type: 'CHANGE_VIEW_TYPE',
                        viewType,
                    });
                    this.dispatch({
                        type: 'SET_OPTION',
                        optionName: 'visibleRange',
                        rawOptionValue: dateOrRange,
                    });
                }
                else {
                    let { dateEnv } = this.getCurrentData();
                    this.dispatch({
                        type: 'CHANGE_VIEW_TYPE',
                        viewType,
                        dateMarker: dateEnv.createMarker(dateOrRange),
                    });
                }
            }
            else {
                this.dispatch({
                    type: 'CHANGE_VIEW_TYPE',
                    viewType,
                });
            }
        });
    }
    // Forces navigation to a view for the given date.
    // `viewType` can be a specific view name or a generic one like "week" or "day".
    // needs to change
    zoomTo(dateMarker, viewType) {
        let state = this.getCurrentData();
        let spec;
        viewType = viewType || 'day'; // day is default zoom
        spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType);
        this.unselect();
        if (spec) {
            this.dispatch({
                type: 'CHANGE_VIEW_TYPE',
                viewType: spec.type,
                dateMarker,
            });
        }
        else {
            this.dispatch({
                type: 'CHANGE_DATE',
                dateMarker,
            });
        }
    }
    // Given a duration singular unit, like "week" or "day", finds a matching view spec.
    // Preference is given to views that have corresponding buttons.
    getUnitViewSpec(unit) {
        let { viewSpecs, toolbarConfig } = this.getCurrentData();
        let viewTypes = [].concat(toolbarConfig.header ? toolbarConfig.header.viewsWithButtons : [], toolbarConfig.footer ? toolbarConfig.footer.viewsWithButtons : []);
        let i;
        let spec;
        for (let viewType in viewSpecs) {
            viewTypes.push(viewType);
        }
        for (i = 0; i < viewTypes.length; i += 1) {
            spec = viewSpecs[viewTypes[i]];
            if (spec) {
                if (spec.singleUnit === unit) {
                    return spec;
                }
            }
        }
        return null;
    }
    // Current Date
    // -----------------------------------------------------------------------------------------------------------------
    prev() {
        this.unselect();
        this.dispatch({ type: 'PREV' });
    }
    next() {
        this.unselect();
        this.dispatch({ type: 'NEXT' });
    }
    prevYear() {
        let state = this.getCurrentData();
        this.unselect();
        this.dispatch({
            type: 'CHANGE_DATE',
            dateMarker: state.dateEnv.addYears(state.currentDate, -1),
        });
    }
    nextYear() {
        let state = this.getCurrentData();
        this.unselect();
        this.dispatch({
            type: 'CHANGE_DATE',
            dateMarker: state.dateEnv.addYears(state.currentDate, 1),
        });
    }
    today() {
        let state = this.getCurrentData();
        this.unselect();
        this.dispatch({
            type: 'CHANGE_DATE',
            dateMarker: state.nowManager.getDateMarker(),
        });
    }
    gotoDate(zonedDateInput) {
        let state = this.getCurrentData();
        this.unselect();
        this.dispatch({
            type: 'CHANGE_DATE',
            dateMarker: state.dateEnv.createMarker(zonedDateInput),
        });
    }
    incrementDate(deltaInput) {
        let state = this.getCurrentData();
        let delta = createDuration(deltaInput);
        if (delta) { // else, warn about invalid input?
            this.unselect();
            this.dispatch({
                type: 'CHANGE_DATE',
                dateMarker: state.dateEnv.add(state.currentDate, delta),
            });
        }
    }
    getDate() {
        let state = this.getCurrentData();
        return state.dateEnv.toDate(state.currentDate);
    }
    // Date Formatting Utils
    // -----------------------------------------------------------------------------------------------------------------
    formatDate(d, formatter) {
        let { dateEnv } = this.getCurrentData();
        let dateMeta = dateEnv.createMarkerMeta(d);
        return joinDateTimeFormatParts(dateEnv.formatToParts(dateMeta.marker, createFormatter(formatter), { instantMs: dateMeta.instantMs }));
    }
    // `settings` is for formatter AND isEndExclusive
    formatRange(d0, d1, settings) {
        let { dateEnv } = this.getCurrentData();
        let startMeta = dateEnv.createMarkerMeta(d0);
        let endMeta = dateEnv.createMarkerMeta(d1);
        return joinDateTimeFormatParts(dateEnv.formatRangeToParts(startMeta.marker, endMeta.marker, createFormatter(settings), {
            isEndExclusive: settings.isEndExclusive,
            startInstantMs: startMeta.instantMs,
            endInstantMs: endMeta.instantMs,
        }));
    }
    formatIso(d, omitTime) {
        let { dateEnv } = this.getCurrentData();
        return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime });
    }
    // Date Selection / Event Selection / DayClick
    // -----------------------------------------------------------------------------------------------------------------
    select(dateOrObj, endDate) {
        let selectionInput;
        if (endDate == null) {
            if (dateOrObj.start != null) {
                selectionInput = dateOrObj;
            }
            else {
                selectionInput = {
                    start: dateOrObj,
                    end: null,
                };
            }
        }
        else {
            selectionInput = {
                start: dateOrObj,
                end: endDate,
            };
        }
        let state = this.getCurrentData();
        let selection = parseDateSpan(selectionInput, state.dateEnv, createDuration({ days: 1 }));
        if (selection) { // throw parse error otherwise?
            this.dispatch({ type: 'SELECT_DATES', selection });
            triggerDateSelect(selection, null, state);
        }
    }
    unselect(pev) {
        let state = this.getCurrentData();
        if (state.dateSelection) {
            this.dispatch({ type: 'UNSELECT_DATES' });
            triggerDateUnselect(pev, state);
        }
    }
    // Public Events API
    // -----------------------------------------------------------------------------------------------------------------
    addEvent(eventInput, sourceInput) {
        if (eventInput instanceof EventImpl) {
            let def = eventInput._def;
            let instance = eventInput._instance;
            let currentData = this.getCurrentData();
            // not already present? don't want to add an old snapshot
            if (!currentData.eventStore.defs[def.defId]) {
                this.dispatch({
                    type: 'ADD_EVENTS',
                    eventStore: eventTupleToStore({ def, instance }), // TODO: better util for two args?
                });
                this.triggerEventAdd(eventInput);
            }
            return eventInput;
        }
        let state = this.getCurrentData();
        let eventSource;
        if (sourceInput instanceof EventSourceImpl) {
            eventSource = sourceInput.internalEventSource;
        }
        else if (typeof sourceInput === 'boolean') {
            if (sourceInput) { // true. part of the first event source
                [eventSource] = hashValuesToArray(state.eventSources);
            }
        }
        else if (sourceInput != null) { // an ID. accepts a number too
            let sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function
            if (!sourceApi) {
                warn(`Unknown event source ID \`${sourceInput}\`.`); // TODO: test
                return null;
            }
            eventSource = sourceApi.internalEventSource;
        }
        let tuple = parseEvent(eventInput, eventSource, state, false);
        if (tuple) {
            let newEventApi = new EventImpl(state, tuple.def, tuple.def.recurringDef ? null : tuple.instance);
            this.dispatch({
                type: 'ADD_EVENTS',
                eventStore: eventTupleToStore(tuple),
            });
            this.triggerEventAdd(newEventApi);
            return newEventApi;
        }
        return null;
    }
    triggerEventAdd(eventApi) {
        let { emitter } = this.getCurrentData();
        emitter.trigger('eventAdd', {
            event: eventApi,
            relatedEvents: [],
            revert: () => {
                this.dispatch({
                    type: 'REMOVE_EVENTS',
                    eventStore: eventApiToStore(eventApi),
                });
            },
        });
    }
    // TODO: optimize
    getEventById(id) {
        let state = this.getCurrentData();
        let { defs, instances } = state.eventStore;
        id = String(id);
        for (let defId in defs) {
            let def = defs[defId];
            if (def.publicId === id) {
                if (def.recurringDef) {
                    return new EventImpl(state, def, null);
                }
                for (let instanceId in instances) {
                    let instance = instances[instanceId];
                    if (instance.defId === def.defId) {
                        return new EventImpl(state, def, instance);
                    }
                }
            }
        }
        return null;
    }
    getEvents() {
        let currentData = this.getCurrentData();
        return buildEventApis(currentData.eventStore, currentData);
    }
    removeAllEvents() {
        this.dispatch({ type: 'REMOVE_ALL_EVENTS' });
    }
    // Public Event Sources API
    // -----------------------------------------------------------------------------------------------------------------
    getEventSources() {
        let state = this.getCurrentData();
        let sourceHash = state.eventSources;
        let sourceApis = [];
        for (let internalId in sourceHash) {
            sourceApis.push(new EventSourceImpl(state, sourceHash[internalId]));
        }
        return sourceApis;
    }
    getEventSourceById(id) {
        let state = this.getCurrentData();
        let sourceHash = state.eventSources;
        id = String(id);
        for (let sourceId in sourceHash) {
            if (sourceHash[sourceId].publicId === id) {
                return new EventSourceImpl(state, sourceHash[sourceId]);
            }
        }
        return null;
    }
    addEventSource(sourceInput) {
        let state = this.getCurrentData();
        if (sourceInput instanceof EventSourceImpl) {
            // not already present? don't want to add an old snapshot
            if (!state.eventSources[sourceInput.internalEventSource.sourceId]) {
                this.dispatch({
                    type: 'ADD_EVENT_SOURCES',
                    sources: [sourceInput.internalEventSource],
                });
            }
            return sourceInput;
        }
        let eventSource = parseEventSource(sourceInput, state);
        if (eventSource) { // TODO: error otherwise?
            this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] });
            return new EventSourceImpl(state, eventSource);
        }
        return null;
    }
    removeAllEventSources() {
        this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' });
    }
    refetchEvents() {
        this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true });
    }
    // Scroll
    // -----------------------------------------------------------------------------------------------------------------
    scrollToTime(timeInput) {
        let time = createDuration(timeInput);
        if (time) {
            this.trigger('_timeScrollRequest', time);
        }
    }
    // Button State
    // -----------------------------------------------------------------------------------------------------------------
    getButtonState() {
        const currentData = this.getCurrentData();
        const { toolbarProps } = currentData;
        const options = currentData.calendarOptions;
        const buttonConfigs = options.buttons || {};
        const viewSpecs = currentData.viewSpecs;
        const currentUnit = currentData.viewSpec.singleUnit;
        const currentHintOrdinal = [
            currentUnit ? getSingleUnitText(currentUnit, options) : '',
            currentUnit,
        ];
        const buttonState = {
            today: {
                text: options.todayText,
                hint: formatWithOrdinals(options.todayHint, currentHintOrdinal, options.todayText),
                isDisabled: !toolbarProps.isTodayEnabled,
            },
            prev: {
                text: options.prevText,
                hint: formatWithOrdinals(options.prevHint, currentHintOrdinal, options.prevText),
                isDisabled: !toolbarProps.isPrevEnabled,
            },
            next: {
                text: options.nextText,
                hint: formatWithOrdinals(options.nextHint, currentHintOrdinal, options.nextText),
                isDisabled: !toolbarProps.isNextEnabled,
            },
            prevYear: {
                text: options.prevYearText,
                hint: formatWithOrdinals(options.prevHint, [options.yearText, 'year'], options.prevYearText),
                isDisabled: false,
            },
            nextYear: {
                text: options.prevYearText,
                hint: formatWithOrdinals(options.nextHint, [options.yearText, 'year'], options.nextYearText),
                isDisabled: false,
            },
        };
        for (const viewSpecName in viewSpecs) {
            const viewSpec = viewSpecs[viewSpecName];
            const { singleUnit } = viewSpec;
            const buttonTextKey = viewSpec.optionDefaults.buttonTextKey;
            const buttonText = buttonConfigs[viewSpecName]?.text ||
                (buttonTextKey ? options[buttonTextKey] : '') ||
                (singleUnit ? getSingleUnitText(singleUnit, options) : '') ||
                viewSpecName;
            const buttonHint = formatWithOrdinals(options.viewHint, [buttonText, viewSpecName], // ordinal arguments
            buttonText);
            buttonState[viewSpecName] = {
                text: buttonText,
                hint: buttonHint,
            };
        }
        return buttonState;
    }
}
function getSingleUnitText(singleUnit, options) {
    return options[singleUnit + 'TextLong'] || options[singleUnit + 'Text'];
}

class CalendarMediaRoot extends Component {
    constructor() {
        super(...arguments);
        this.state = {
            forPrint: false,
        };
        this.handleBeforePrint = () => {
            // The synchronous commit mounts print-only DOM during this beforeprint
            // task. Watchers registering during the bracket measure immediately, and
            // their layout recomputations settle in one batched drain before the
            // native event returns.
            flushSyncWithSizeBatching(() => {
                this.setState({ forPrint: true });
            });
        };
        this.handleAfterPrint = () => {
            // No synchronous commit needed: nothing else listens to _afterprint, and
            // the ordinary microtask-batched re-render restores the screen DOM before
            // the next paint. Screen watchers keep their async-first measurement.
            this.setState({ forPrint: false });
        };
    }
    render() {
        return this.props?.children(this.state.forPrint);
    }
    componentDidMount() {
        const { props } = this;
        const { emitter } = props;
        emitter.on('_beforeprint', this.handleBeforePrint);
        emitter.on('_afterprint', this.handleAfterPrint);
    }
    componentWillUnmount() {
        const { props } = this;
        const { emitter } = props;
        emitter.off('_beforeprint', this.handleBeforePrint);
        emitter.off('_afterprint', this.handleAfterPrint);
    }
}
function computeRootClassName(options, forPrint) {
    let borderlessX = options.borderlessX ?? options.borderless;
    let borderlessTop = options.borderlessTop ?? options.borderless;
    let borderlessBottom = options.borderlessBottom ?? options.borderless;
    const calendarDisplayData = {
        borderlessX: Boolean(borderlessX),
        borderlessTop: Boolean(borderlessTop),
        borderlessBottom: Boolean(borderlessBottom),
    };
    return joinClassNames(generateClassName(options.class, calendarDisplayData), generateClassName(options.className, calendarDisplayData), classNames.borderBoxRoot, classNames.isolate, classNames.flexCol, forPrint ? classNames.calendarPrintRoot : classNames.calendarScreenRoot);
}

class ButtonIcon extends BaseComponent {
    render() {
        const { contentGenerator, className } = this.props;
        if (contentGenerator) {
            // TODO: somehow give className to the svg?
            return (jsx(ContentContainer, { tag: 'span', style: { display: 'contents' }, attrs: { 'aria-hidden': true }, renderProps: {}, generatorName: undefined, customGenerator: contentGenerator }));
        }
        if (className !== undefined) {
            return (jsx("span", { "aria-hidden": true, className: className }));
        }
    }
}

class ToolbarSection extends BaseComponent {
    render() {
        let { props } = this;
        let { options } = this.context;
        let children = props.widgetGroups.map((widgetGroup) => this.renderWidgetGroup(widgetGroup));
        return createElement('div', {
            className: generateClassName(options.toolbarSectionClass, { name: props.name }),
        }, ...children);
    }
    renderWidgetGroup(widgetGroup) {
        let { props, context } = this;
        let { options } = context;
        let children = [];
        let isOnlyButtons = true;
        let isOnlyView = true;
        for (const widget of widgetGroup) {
            const { name, isView } = widget;
            if (name === 'title') {
                isOnlyButtons = false;
            }
            else if (!isView) {
                isOnlyView = false;
            }
        }
        for (let widget of widgetGroup) {
            let { name, customElement, buttonHint } = widget;
            if (name === 'title') {
                children.push(jsx("div", { role: 'heading', "aria-level": options.headingLevel, id: props.titleId, className: joinClassNames(options.toolbarTitleClass), children: props.title }));
            }
            else if (customElement) {
                children.push(jsx(ContentContainer, { tag: 'span', style: { display: 'contents' }, renderProps: {}, generatorName: undefined, customGenerator: customElement }));
            }
            else {
                let isSelected = name === props.selectedButton;
                let isDisabled = (!props.isTodayEnabled && name === 'today') ||
                    (!props.isPrevEnabled && name === 'prev') ||
                    (!props.isNextEnabled && name === 'next');
                let buttonDisplay = widget.buttonDisplay ?? options.buttonDisplay;
                if (buttonDisplay === 'auto') {
                    buttonDisplay = (widget.buttonIconContent || widget.buttonIconClass)
                        ? 'icon'
                        : 'text';
                }
                let iconNode;
                if (buttonDisplay !== 'text') {
                    iconNode = (jsx(ButtonIcon, { className: widget.buttonIconClass, contentGenerator: widget.buttonIconContent }));
                }
                let inGroup = widgetGroup.length > 1 && isOnlyButtons;
                let buttonGroup = inGroup ? { hasSelection: isOnlyView } : null;
                let renderProps = {
                    name,
                    text: widget.buttonText,
                    isPrimary: widget.buttonIsPrimary,
                    isSelected,
                    isDisabled,
                    isIconOnly: buttonDisplay === 'icon',
                    buttonGroup,
                };
                children.push(jsx(ContentContainer, { tag: 'button', attrs: {
                        type: 'button',
                        disabled: isDisabled,
                        ...((isOnlyButtons && isOnlyView)
                            ? { 'role': 'tab', 'aria-selected': isSelected }
                            : { 'aria-pressed': isSelected }),
                        'aria-label': typeof buttonHint === 'function'
                            ? buttonHint(props.navUnit)
                            : buttonHint,
                        onClick: widget.buttonClick,
                    }, className: joinClassNames(generateClassName(options.buttonClass, renderProps), !isDisabled && classNames.cursorPointer, inGroup && joinClassNames(isSelected ? classNames.z1 : classNames.z0, classNames.focusZ2)), renderProps: renderProps, generatorName: undefined, classNameGenerator: widget.buttonClass, didMount: widget.buttonDidMount, willUnmount: widget.buttonWillUnmount, children: () => (buttonDisplay === 'text'
                        ? widget.buttonText
                        : buttonDisplay === 'icon'
                            ? iconNode
                            : buttonDisplay === 'icon-text'
                                ? (jsxs(Fragment, { children: [iconNode, widget.buttonText] }))
                                : (jsxs(Fragment, { children: [widget.buttonText, iconNode] })) // text-icon
                    ) }));
            }
        }
        if (children.length > 1) {
            return createElement('div', {
                role: (isOnlyButtons && isOnlyView) ? 'tablist' : undefined,
                'aria-label': (isOnlyButtons && isOnlyView) ? options.viewChangeHint : undefined,
                className: joinClassNames(generateClassName(options.buttonGroupClass, { hasSelection: isOnlyView }), classNames.isolate),
            }, ...children);
        }
        return children[0];
    }
}

class Toolbar extends BaseComponent {
    render() {
        let { props } = this;
        let options = this.context.options;
        let { sectionWidgets } = props.model;
        const { borderlessX, borderlessTop, borderlessBottom } = computeViewBorderless(options);
        const toolbarClassOption = props.isHeader ? options.headerToolbarClass : options.footerToolbarClass;
        return (jsxs("div", { className: joinClassNames(generateClassName(toolbarClassOption, { borderlessX, borderlessTop, borderlessBottom }), generateClassName(options.toolbarClass, { borderlessX, borderlessTop, borderlessBottom })), children: [this.renderSection('start', sectionWidgets.start), this.renderSection('center', sectionWidgets.center), this.renderSection('end', sectionWidgets.end)] }));
    }
    renderSection(name, widgetGroups) {
        let { props } = this;
        return (jsx(ToolbarSection, { name: name, widgetGroups: widgetGroups, title: props.title, titleId: props.titleId, navUnit: props.navUnit, selectedButton: props.selectedButton, isTodayEnabled: props.isTodayEnabled, isPrevEnabled: props.isPrevEnabled, isNextEnabled: props.isNextEnabled }, name));
    }
}

/*
Detects when the user clicks on an event within a DateComponent
*/
class EventClicking extends Interaction {
    constructor(settings) {
        super(settings);
        this.handleSegClick = (ev, segEl) => {
            let { component } = this;
            let { context } = component;
            let eventRange = getElEventRange(segEl);
            if (eventRange && // might be the <div> surrounding the more link
                component.isValidSegDownEl(ev.target)) {
                context.emitter.trigger('eventClick', {
                    el: segEl,
                    event: new EventImpl(component.context, eventRange.def, eventRange.instance),
                    jsEvent: ev, // Is this always a mouse event? See #4655
                    view: context.viewApi,
                });
            }
        };
        this.destroy = listenBySelector(settings.el, 'click', `.${classNames.internalEvent}`, // on both fg and bg events
        this.handleSegClick);
    }
}

/*
Triggers events and adds/removes core classNames when the user's pointer
enters/leaves event-elements of a component.
*/
class EventHovering extends Interaction {
    constructor(settings) {
        super(settings);
        // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it
        this.handleEventElRemove = (el) => {
            if (el === this.currentSegEl) {
                this.handleSegLeave(null, this.currentSegEl);
            }
        };
        this.handleSegEnter = (ev, segEl) => {
            if (getElEventRange(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper
                this.currentSegEl = segEl;
                this.triggerEvent('eventMouseEnter', ev, segEl);
            }
        };
        this.handleSegLeave = (ev, segEl) => {
            if (this.currentSegEl) {
                this.currentSegEl = null;
                this.triggerEvent('eventMouseLeave', ev, segEl);
            }
        };
        this.removeHoverListeners = listenToHoverBySelector(settings.el, `.${classNames.internalEvent}`, // on both fg and bg events
        this.handleSegEnter, this.handleSegLeave);
    }
    destroy() {
        this.removeHoverListeners();
    }
    triggerEvent(publicEvName, ev, segEl) {
        let { component } = this;
        let { context } = component;
        let eventRange = getElEventRange(segEl);
        if (!ev || component.isValidSegDownEl(ev.target)) {
            context.emitter.trigger(publicEvName, {
                el: segEl,
                event: new EventImpl(context, eventRange.def, eventRange.instance),
                jsEvent: ev, // Is this always a mouse event? See #4655
                view: context.viewApi,
            });
        }
    }
}

class CalendarInner extends PureComponent {
    constructor() {
        super(...arguments);
        this.buildViewContext = memoize(buildViewContext);
        this.buildViewPropTransformers = memoize(buildViewPropTransformers);
        this.interactionsStore = {};
        this.calendarInteractions = [];
        this.registerInteractiveComponent = (component, settingsInput) => {
            let settings = parseInteractionSettings(component, settingsInput);
            let DEFAULT_INTERACTIONS = [
                EventClicking,
                EventHovering,
            ];
            let interactionClasses = DEFAULT_INTERACTIONS;
            if (!settingsInput.disableHits) {
                interactionClasses = interactionClasses.concat(this.props.pluginHooks.componentInteractions);
            }
            let interactions = interactionClasses.map((TheInteractionClass) => new TheInteractionClass(settings));
            this.interactionsStore[component.uid] = interactions;
            interactionSettingsStore[component.uid] = settings;
        };
        this.unregisterInteractiveComponent = (component) => {
            let listeners = this.interactionsStore[component.uid];
            if (listeners) {
                for (let listener of listeners) {
                    listener.destroy();
                }
                delete this.interactionsStore[component.uid];
            }
            delete interactionSettingsStore[component.uid];
        };
    }
    get viewTitleId() {
        return this.props.baseId + 'title';
    }
    render() {
        const { props } = this;
        let { toolbarConfig, options } = props;
        let viewHeight;
        let viewHeightLiquid = false;
        let viewAspectRatio;
        if (props.forPrint || getIsHeightAuto(options)) ;
        else if (options.height != null) {
            viewHeightLiquid = true;
        }
        else if (options.contentHeight != null) {
            viewHeight = options.contentHeight;
        }
        else {
            viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall
        }
        let viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.nowManager, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, props.baseId, this.registerInteractiveComponent, this.unregisterInteractiveComponent);
        return (jsxs(ViewContextType.Provider, { value: viewContext, children: [toolbarConfig.header && (jsx(Toolbar, { model: toolbarConfig.header, isHeader: true, titleId: this.viewTitleId, ...props.toolbarProps })), jsxs("div", { className: joinClassNames(classNames.flexCol, classNames.rel, 
                    // prevents browsers' "scroll anchoring behavior", which cause scroll thrashing
                    // when clicking "Next" for month-view, because rows would flex-grow while other rows
                    // temporarily removed. This behavior probably universally unhelpful for our uses,
                    // esp with virtualization, but maybe in future put on more specific row-based parents
                    classNames.overflowAnchorNone, 
                    // workaround for Safari pushing content area extremely wide after returning from
                    // print-view. probably a good idea regardless, to circumvent 'auto' dimentions
                    classNames.minHeight0, viewHeightLiquid && classNames.liquid), style: {
                        height: viewHeight,
                        aspectRatio: viewAspectRatio != null ? String(viewAspectRatio) : undefined,
                    }, children: [this.renderView(joinClassNames((viewHeightLiquid || viewHeight) && classNames.liquid, viewAspectRatio != null && classNames.fill, classNames.internalView)), this.buildAppendContent()] }), toolbarConfig.footer && (jsx(Toolbar, { model: toolbarConfig.footer, isHeader: false, ...props.toolbarProps }))] }));
    }
    renderView(className) {
        const { props } = this;
        const { pluginHooks, viewSpec, toolbarConfig, toolbarProps } = props;
        let viewProps = {
            className,
            dateProfile: props.dateProfile,
            businessHours: props.businessHours,
            eventStore: props.renderableEventStore, // !
            eventUiBases: props.eventUiBases,
            dateSelection: props.dateSelection,
            eventSelection: props.eventSelection,
            eventDrag: props.eventDrag,
            eventResize: props.eventResize,
            forPrint: props.forPrint,
            labelId: toolbarConfig.header && toolbarConfig.header.hasTitle ? this.viewTitleId : undefined,
            labelStr: toolbarConfig.header && toolbarConfig.header.hasTitle ? undefined : toolbarProps.title,
        };
        let transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers);
        let contentProps = {
            ...props,
            toolbarProps,
            forPrint: props.forPrint,
        };
        for (let transformer of transformers) {
            Object.assign(viewProps, transformer.transform(viewProps, contentProps));
        }
        let ViewComponent = viewSpec.component;
        return (jsx(ViewComponent, { ...viewProps }));
    }
    buildAppendContent() {
        const { props } = this;
        return (jsx(Fragment, { children: props.pluginHooks.viewContainerAppends.map((buildAppendContent, i) => (jsx(Fragment$1, { children: buildAppendContent(props) }, i))) }));
    }
    // BE AWARE React StrictMode might execute this twice
    componentDidMount() {
        const { props } = this;
        this.calendarInteractions = props.pluginHooks.calendarInteractions
            .map((CalendarInteractionClass) => new CalendarInteractionClass(props));
        let { propSetHandlers } = props.pluginHooks;
        for (let propName in propSetHandlers) {
            propSetHandlers[propName](props[propName], props);
        }
        // call contextInit
        for (let callback of props.pluginHooks.contextInit) {
            callback(props);
        }
    }
    componentDidUpdate(prevProps) {
        const { props } = this;
        let { propSetHandlers } = props.pluginHooks;
        for (let propName in propSetHandlers) {
            if (props[propName] !== prevProps[propName]) {
                propSetHandlers[propName](props[propName], props);
            }
        }
    }
    // BE AWARE React StrictMode might execute this twice
    componentWillUnmount() {
        const { props } = this;
        for (let interaction of this.calendarInteractions) {
            interaction.destroy();
        }
        this.calendarInteractions = [];
        // will likely undo what was done by contextInit
        props.emitter.trigger('_unmount');
    }
}
function buildViewPropTransformers(theClasses) {
    return theClasses.map((TheClass) => new TheClass());
}

export { CalendarApiImpl as C, CalendarMediaRoot as a, CalendarInner as b, computeRootClassName as c, CalendarDataManager as d, parseBusinessHours as p };