@fullcalendar/web-component
Version:
Custom Element for FullCalendar
274 lines (266 loc) • 13.3 kB
JavaScript
import { jsx, jsxs, Fragment } from 'preact/jsx-runtime';
import { j as joinClassNames } from './423b7bc6.js';
import { joinDateTimeFormatParts, formatDayString, intersectRanges, startOfDay, addDays } from '@full-ui/headless-calendar';
import { B as BaseComponent, y as findWeekdayText, z as findDayNumberText, v as buildNavLinkAttrs, C as ContentContainer, u as renderText, W as WEEKDAY_ONLY_FORMAT, F as FULL_DATE_FORMAT, x as StandardEvent, a as memoize, t as buildDateStr, g as generateClassName, o as DateComponent, d as getIsHeightAuto, h as ViewContainer, S as Scroller, n as NowTimer, q as getDateMeta } from './fa7e597f.js';
import { F as classNames, c as createFormatter, an as getEventKey, al as getEventRangeMeta, s as sliceEventStore, ak as sortEventSegs } from './69b11357.js';
class ListDayHeaderInner extends BaseComponent {
render() {
const { props, context } = this;
const { options } = context;
const textParts = context.dateEnv.formatToParts(props.dayDate, props.dayFormat);
const text = joinDateTimeFormatParts(textParts);
const hasNavLink = options.navLinks;
const renderProps = {
...props.dateMeta,
view: context.viewApi,
text,
textParts,
get weekdayText() { return findWeekdayText(textParts); },
get dayNumberText() { return findDayNumberText(textParts); },
hasNavLink,
level: props.level,
};
const navLinkAttrs = hasNavLink
? buildNavLinkAttrs(this.context, props.dayDate, undefined, text, this.props.isTabbable)
: {};
return (jsx(ContentContainer, { tag: "div", attrs: navLinkAttrs, renderProps: renderProps, generatorName: "listDayHeaderContent", customGenerator: options.listDayHeaderContent, defaultGenerator: renderText, classNameGenerator: options.listDayHeaderInnerClass }));
}
}
class ListDayHeader extends BaseComponent {
render() {
let { options, viewApi, viewSpec } = this.context;
let { dayDate, dateMeta } = this.props;
let stickyHeaderDates = !this.props.forPrint;
const listDayFormat = options.listDayFormat ?? createDefaultListDayFormat(viewSpec);
const listDayAltFormat = options.listDayAltFormat ?? createDefaultListDaySideFormat(viewSpec);
let renderProps = {
...dateMeta,
view: viewApi,
};
return (jsx(ContentContainer, { tag: "div", attrs: {
'data-date': formatDayString(dayDate),
...(dateMeta.isToday ? { 'aria-current': 'date' } : {}),
}, className: stickyHeaderDates ? classNames.stickyT : '', renderProps: renderProps, generatorName: undefined, classNameGenerator: options.listDayHeaderClass, didMount: options.listDayHeaderDidMount, willUnmount: options.listDayHeaderWillUnmount, children: () => (jsxs(Fragment, { children: [Boolean(listDayFormat) && (jsx(ListDayHeaderInner, { dayDate: dayDate, dayFormat: listDayFormat, isTabbable: true, dateMeta: dateMeta, level: 0 })), Boolean(listDayAltFormat) && (jsx(ListDayHeaderInner, { dayDate: dayDate, dayFormat: listDayAltFormat, isTabbable: false, dateMeta: dateMeta, level: 1 }))] })) }));
}
}
function createDefaultListDayFormat({ durationUnit, singleUnit }) {
if (singleUnit === 'day') {
return WEEKDAY_ONLY_FORMAT;
}
else if (durationUnit === 'day' || singleUnit === 'week') {
return WEEKDAY_ONLY_FORMAT;
}
else {
return FULL_DATE_FORMAT;
}
}
function createDefaultListDaySideFormat({ durationUnit, singleUnit }) {
if (singleUnit === 'day') ;
else if (durationUnit === 'day' || singleUnit === 'week') {
return FULL_DATE_FORMAT;
}
else {
return WEEKDAY_ONLY_FORMAT;
}
}
const DEFAULT_TIME_FORMAT = createFormatter({
hour: 'numeric',
minute: '2-digit',
meridiem: 'short',
});
class ListEvent extends BaseComponent {
render() {
let { props, context } = this;
let { eventRange } = props;
const { displayEventTime } = context.options;
let forcedTimeText = (displayEventTime !== false) && (eventRange.def.allDay || (!props.isStart && !props.isEnd))
? context.options.allDayText
: undefined;
return (jsx(StandardEvent, { ...props, attrs: {
role: 'listitem',
}, forcedTimeText: forcedTimeText, defaultTimeFormat: DEFAULT_TIME_FORMAT, disableDragging: true, disableResizing: true, disableZindexes // because conflicts with sticky list headers
: true, display: 'list-item' }));
}
}
class ListDay extends BaseComponent {
constructor() {
super(...arguments);
// memo
this.getDateMeta = memoize(getDateMeta);
this.sortEventSegs = memoize(sortEventSegs);
}
render() {
const { props, context } = this;
const { nowDate, todayRange } = props;
const { options } = context;
const dateMeta = this.getDateMeta(props.dayDate, context.dateEnv, undefined, todayRange);
const segs = this.sortEventSegs(props.segs, options.eventOrder);
const fullDateStr = buildDateStr(this.context, props.dayDate);
const listDayData = {
...dateMeta,
isFirst: props.isFirst,
isLast: props.isLast,
view: context.viewApi,
};
const listDayEventsData = {
...dateMeta,
view: context.viewApi,
};
return (jsxs("div", { role: 'listitem', "aria-label": fullDateStr, className: generateClassName(options.listDayClass, listDayData), children: [jsx(ListDayHeader, { dayDate: props.dayDate, dateMeta: dateMeta, forPrint: props.forPrint }), jsx("div", { role: 'list', "aria-label": options.eventsHint, className: joinClassNames(generateClassName(options.listDayBodyClass, listDayEventsData), classNames.flexCol), children: segs.map((seg, index) => {
const key = getEventKey(seg);
const isFirst = index === 0;
const isLast = index === segs.length - 1;
return (jsx(ListEvent, { eventRange: seg.eventRange, slicedStart: seg.slicedStart, slicedEnd: seg.slicedEnd, isStart: seg.isStart, isEnd: seg.isEnd, isFirst: isFirst, isLast: isLast, isDragging: false, isResizing: false, isMirror: false, isSelected: false, ...getEventRangeMeta(seg.eventRange, todayRange, nowDate) }, key));
}) })] }));
}
}
/*
Responsible for the scroller, and forwarding event-related actions into the "grid".
*/
class ListView extends DateComponent {
constructor() {
super(...arguments);
// memo
this.computeDateVars = memoize(computeDateVars);
this.eventStoreToSegs = memoize(this._eventStoreToSegs);
this.setRootEl = (rootEl) => {
if (rootEl) {
this.context.registerInteractiveComponent(this, {
el: rootEl,
disableHits: true, // HACK to not do date-clicking/selecting
});
}
else {
this.context.unregisterInteractiveComponent(this);
}
};
}
render() {
let { props, context } = this;
let { options } = context;
let { dayDates, dayRanges } = this.computeDateVars(props.dateProfile);
let eventSegs = this.eventStoreToSegs(props.eventStore, props.eventUiBases, dayRanges);
let verticalScrolling = !props.forPrint && !getIsHeightAuto(options);
return (jsx(ViewContainer, { viewSpec: context.viewSpec, className: joinClassNames(props.className, classNames.flexCol), elRef: this.setRootEl, children: eventSegs.length ? (jsx(Scroller // TODO: don't need heavyweight component
, { vertical: verticalScrolling, className: joinClassNames(classNames.flexCol, verticalScrolling ? classNames.liquid : ''), children: this.renderSegList(eventSegs, dayDates) })) : this.renderEmptyMessage() }));
}
renderEmptyMessage() {
let { options, viewApi } = this.context;
let renderProps = {
text: options.noEventsText,
view: viewApi,
};
return (jsx(ContentContainer, { tag: "div", attrs: {
role: 'status', // does a polite announcement
}, renderProps: renderProps, generatorName: "noEventsContent", customGenerator: options.noEventsContent, defaultGenerator: renderNoEventsInner, classNameGenerator: options.noEventsClass, className: classNames.grow, didMount: options.noEventsDidMount, willUnmount: options.noEventsWillUnmount, children: (InnerContent) => (jsx(InnerContent, { tag: "div", className: generateClassName(options.noEventsInnerClass, renderProps) })) }));
}
renderSegList(allSegs, dayDates) {
let { options } = this.context;
let segsByDay = groupSegsByDay(allSegs); // sparse array
return (jsx("div", { role: "list", "aria-labelledby": this.props.labelId, "aria-label": this.props.labelStr, className: joinClassNames(classNames.flexCol, joinClassNames(options.listDaysClass)), children: jsx(NowTimer, { unit: "day", children: (nowDate, todayRange) => {
const dayNodes = [];
const populatedDayCount = segsByDay.reduce((count, daySegs) => count + (daySegs ? 1 : 0), 0);
let populatedDayIndex = 0;
for (let dayIndex = 0; dayIndex < segsByDay.length; dayIndex += 1) {
let daySegs = segsByDay[dayIndex];
if (daySegs) { // sparse array, so might be undefined
const dayDate = dayDates[dayIndex];
const key = formatDayString(dayDate);
const isFirst = populatedDayIndex === 0;
const isLast = populatedDayIndex === populatedDayCount - 1;
dayNodes.push(jsx(ListDay, { dayDate: dayDate, nowDate: nowDate, todayRange: todayRange, segs: daySegs, isFirst: isFirst, isLast: isLast, forPrint: this.props.forPrint }, key));
populatedDayIndex += 1;
}
}
return (jsx(Fragment, { children: dayNodes }));
} }) }));
}
_eventStoreToSegs(eventStore, eventUiBases, dayRanges) {
return this.eventRangesToSegs(sliceEventStore(eventStore, eventUiBases,
// HACKY to reference internal state...
this.props.dateProfile.activeRange, this.context.options.nextDayThreshold).fg, dayRanges);
}
eventRangesToSegs(fullDayEventRanges, dayRanges) {
let segs = [];
for (let fullDayEventRange of fullDayEventRanges) {
segs.push(...this.eventRangeToSegs(fullDayEventRange, dayRanges));
}
return segs;
}
eventRangeToSegs(fullDayEventRange, dayRanges) {
let fullDayRange = fullDayEventRange.range;
let dayIndex;
let segs = [];
for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex += 1) {
const slicedFullDayRange = intersectRanges(fullDayRange, dayRanges[dayIndex]);
if (slicedFullDayRange) {
segs.push({
eventRange: fullDayEventRange,
slicedStart: slicedFullDayRange.start,
slicedEnd: slicedFullDayRange.end,
isStart: fullDayEventRange.isStart && fullDayRange.start.valueOf() === slicedFullDayRange.start.valueOf(),
isEnd: fullDayEventRange.isEnd && fullDayRange.end.valueOf() === slicedFullDayRange.end.valueOf(),
dayIndex,
});
}
}
return segs;
}
}
function renderNoEventsInner(renderProps) {
return renderProps.text;
}
function computeDateVars(dateProfile) {
let dayStart = startOfDay(dateProfile.renderRange.start);
let viewEnd = dateProfile.renderRange.end;
let dayDates = [];
let dayRanges = [];
while (dayStart < viewEnd) {
dayDates.push(dayStart);
dayRanges.push({
start: dayStart,
end: addDays(dayStart, 1),
});
dayStart = addDays(dayStart, 1);
}
return { dayDates, dayRanges };
}
// Returns a sparse array of arrays, segs grouped by their dayIndex
function groupSegsByDay(segs) {
let segsByDay = []; // sparse array
let i;
let seg;
for (i = 0; i < segs.length; i += 1) {
seg = segs[i];
(segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = []))
.push(seg);
}
return segsByDay;
}
var listPlugin = {
name: 'list',
views: {
list: {
component: ListView,
buttonTextKey: 'listText', // what to lookup in locale files
disallowAmbigTitle: true,
},
listDay: {
type: 'list',
duration: { days: 1 },
},
listWeek: {
type: 'list',
duration: { weeks: 1 },
},
listMonth: {
type: 'list',
duration: { month: 1 },
},
listYear: {
type: 'list',
duration: { year: 1 },
},
},
};
export { listPlugin as l };