Quick start
A month calendar rendered to the DOM, without a framework.
import type { Calendar_DayGridLayout } from '@calendar-tsx/core';
import { createCalendar, calendarViews, calendarFeatures, dayGridView, timeGridView } from '@calendar-tsx/core';
import '@calendar-tsx/themes/default.css';
import { Temporal } from 'temporal-polyfill';
type Booking = { title: string };
const features = calendarFeatures({});
const views = calendarViews({
features: features,
views: {
month: dayGridView({ period: 'month', maxEventsPerDay: 3 }),
week: timeGridView({ days: 7 }),
},
});
const calendar = createCalendar({
timeZone: 'America/New_York',
views,
features,
view: 'month',
date: Temporal.Now.plainDateISO(),
events: [{ id: 'standup', kind: 'allDay', start: Temporal.Now.plainDateISO(), data: { title: 'Standup' } }],
// `view` and `date` are controlled: apply the next ones yourself
onNavigate: next => calendar['~update']({ ...calendar.options, view: next.view, date: next.date }),
});
// the attributes and custom properties the themes read
const part = (tag: string, name: string, attributes: Record<string, string> = {}) => {
const element = document.createElement(tag);
element.dataset['scope'] = 'calendar';
element.dataset['part'] = name;
for (const [key, value] of Object.entries(attributes)) element.setAttribute(key, value);
return element;
};
const renderDayGrid = (layout: Calendar_DayGridLayout<Booking>) => {
const root = part('div', 'day-grid-root', { role: 'region', 'aria-label': 'Calendar' });
for (const section of layout.sections) {
const grid = part('div', 'day-grid-section', { role: 'grid' });
grid.style.setProperty('--cal-columns', String(Math.max(1, section.columnCount)));
for (const week of section.rows) {
const row = part('div', 'day-grid-row', { role: 'row' });
row.style.setProperty('--cal-rows', String(week.stackCount));
for (const day of week.days) {
const cell = part('div', 'day-grid-day', { role: 'gridcell' });
if (day.isToday) cell.dataset['today'] = '';
if (day.isOutside) cell.dataset['outside'] = '';
cell.textContent = String(day.date.day);
row.append(cell);
}
for (const segment of week.segments) {
const bar = part('button', 'day-grid-row-event', { type: 'button' });
bar.style.setProperty('--cal-col-start', String(segment.colStart));
bar.style.setProperty('--cal-col-span', String(segment.colSpan));
bar.style.setProperty('--cal-row', String(segment.row));
bar.textContent = segment.event.data.title;
row.append(bar);
}
grid.append(row);
}
root.append(grid);
}
return root;
};
const container = document.querySelector('#calendar')!;
const render = () => {
const { layout } = calendar.store.state;
if (layout.kind === 'dayGrid') container.replaceChildren(renderDayGrid(layout));
};
calendar['~mount'](); // starts the clock
calendar.store.subscribe(render);
render();
document.querySelector('#next')!.addEventListener('click', () => calendar.api.next());What it does
createCalendarderives the state from the options.viewanddateare controlled, soonNavigatepasses the next ones back in through~update.~mountstarts the clock; the returned function stops it.store.subscribere-renders when the state changes.store.state.layoutis the model for the active view.- The renderer writes
data-scope,data-part, state attributes and--cal-*custom properties, the contract the themes read, so the default theme lays it out.
Let the types infer
Don't pass type arguments such as createCalendar<Booking>(…): TypeScript then stops inferring the rest, and view
keys widen to string. Type events instead, for example as Calendar_EventInput<Booking>[].