Using Kalendus as a Library

This document explains how to consume the lms-calendar web component that ships with @jpahd/kalendus, with emphasis on package structure, runtime contracts, and integration patterns for application developers embedding the calendar inside their own products.

Package overview

Installation

npm install @jpahd/kalendus lit luxon

Kalendus targets modern evergreen browsers with native Custom Elements, Shadow DOM, ResizeObserver, and CSS container queries. For legacy browsers you must supply the relevant polyfills yourself.

Importing and registration

src/lms-calendar.ts registers the element via @customElement('lms-calendar'), so importing the package once per bundle is enough:

// anywhere in your app entry point
import '@jpahd/kalendus';

// later in templates / JSX / HTML
const template = html`<lms-calendar></lms-calendar>`;

When tree-shaking, keep the side-effect import (e.g. add it to the sideEffects allow list if your bundler strips bare CSS imports).

Data contracts

TypeScript definitions ship with the package, but the essential shapes are below for quick reference:

type CalendarDate = { day: number; month: number; year: number };
type CalendarTime = { hour: number; minute: number };
type CalendarTimeInterval = { start: CalendarTime; end: CalendarTime };
type CalendarDateInterval = { start: CalendarDate; end: CalendarDate };

type CalendarEntry = {
    date: CalendarDateInterval;
    time: CalendarTimeInterval; // span 00:00-23:59 for all-day blocks
    heading: string;
    content: string;
    color: string; // any valid CSS color
    isContinuation: boolean; // the component recalculates this
    continuation?: Continuation; // auto-injected for multi-day entries
};

Constraints enforced inside willUpdate:

  1. Entries with invalid date/time ranges (end before start) are dropped using luxon.Interval validation.
  2. Arrays are sorted by start time so you do not need to pre-sort.
  3. Multi-day entries are automatically expanded into per-day slices, and continuation metadata is injected for styling.

<lms-calendar> API surface

Public properties

Property Attribute Type Default Notes
heading heading string undefined Optional text rendered in the header.
activeDate CalendarDate today Getter/setter proxying ViewStateController. Assigning triggers navigation.
entries CalendarEntry[] [] Main data source. Provide a new array when mutating so Lit detects the change.
color color string '#000000' Primary accent used by header buttons and gradients.
locale locale string <html lang> \ en Controls UI strings plus date formatting. All supported codes are listed under Localization.
firstDayOfWeek first-day-of-week 06 1 Changes ISO week alignment; reflected attribute enables declarative authoring.
yearDrillTarget year-drill-target 'day' | 'month' 'month' Determines which view opens after clicking a day in the year overview.
yearDensityMode year-density-mode 'dot' | 'heatmap' | 'count' 'dot' Selects the per-day density visualization in the year overview.
dir dir 'ltr' | 'rtl' | 'auto' 'auto' Text direction; auto-detected from locale (RTL for ar, he, etc.). Override to force LTR/RTL.

Methods & Getters

Reactive updates

Custom DOM events

All events bubble and are composed, so you can listen directly on <lms-calendar> in any framework.

Event Detail payload Fired by Typical use
switchdate { direction: 'next' | 'previous' } Header nav buttons Mirror navigation in your app bar or analytics.
switchview { view: 'day' | 'week' | 'month' | 'year' } Header context buttons Track the active zoom level.
jumptoday { date: CalendarDate } Header “Today” button Reset external filters to today.
expand { date: CalendarDate; drillTarget?: 'day' | 'week' | 'month' } Clicking a day label in month/week grid or any cell in the year overview Switch your surrounding UI (e.g., load day-specific details) when the calendar drills down.
open-menu { heading, content, time, date?, anchorRect } Entry cards Intercept to show a custom panel or cancel the built-in one.
menu-close none Menu close button Hide mirrored overlays when the built-in menu closes.
peek-navigate { date: CalendarDate, direction: 'next' | 'previous' } Condensed week peek arrows Track peek navigation in condensed week mode.
locale-ready { locale: string } After locale chunk loads Coordinate with translated UI; hide skeletons or defer dependent work.
clear-other-selections { exceptEntry: HTMLElement } Entry focus events Internal. Keep multi-surface selections in sync.

Note: The expand event payload varies by source — Month and Week views omit drillTarget, while Year view always includes it. See the Events Reference for full details.

You can stop propagation to replace built-in behavior. For example, intercept open-menu, call event.preventDefault(), and show your own drawer while leaving the rest of the component untouched.

For complete event documentation including payload types, source components, and code examples, see the Events Reference.

Year view integration

Entry ingestion and layout

  1. Validation: willUpdate rejects entries whose date or time ranges are invalid per luxon Interval. Feed valid ISO-like data only.
  2. Day splitting: _expandEntryMaybe uses DateTime.plus({ days: index }) to clone multi-day events. Continuation metadata is added so entry chips can indicate start, middle, or end segments.
  3. All-day detection: Any entry without time, or whose time interval runs 00:00–23:59, is rendered in the dedicated all-day row using allocateAllDayRows logic.
  4. Responsive month mode: if the observed width is <768px, month cells collapse to aggregated dots (displayMode = 'month-dot'). Above that threshold, full entry chips render inside each day.
  5. Week/day stacking: LayoutCalculator and SlotManager determine vertical positioning (minuteHeight = 1 pixel) and horizontal cascading with progressive width reduction and offset. Overlapping events share column slots with opacity adjustments.

Styling and theming

The element exposes 151 CSS custom properties. Common entry points:

Token Purpose
--background-color Base surface color; default Canvas (adapts to OS dark mode).
--primary-color Primary accent used in menu buttons and highlights.
--header-height, --header-text-color Header sizing and typography.
--border-radius-sm/md/lg Rounded corners applied across entries, menu, and context chips.
--time-column-width Width of the schedule gutter in week/day view.
--entry-font-size, --entry-line-height, --entry-padding Entry typography.
--entry-handle-width/color Handle strip on timed entries in week/day view.
--entry-dot-size Dot indicators in compact month rendering.
--shadow-sm/md/lg/hv Box shadows for cards and overlays.

Apply tokens on the host element:

lms-calendar {
    --primary-color: #1976d2;
    --background-color: #fefefe;
    --entry-font-size: 0.85rem;
}

All styles live inside the component’s shadow root, so global CSS will not leak in unless you override the provided tokens.

Localization

Accessibility behavior

Integration patterns

Vanilla JS

const calendar = document.querySelector('lms-calendar');
calendar.entries = buildEntriesFromApi(data);
calendar.addEventListener('switchview', (event) => {
    console.log('View changed to', event.detail.view);
});

React wrapper

import { useEffect, useRef } from 'react';
import '@jpahd/kalendus';

export function Calendar({ entries }: { entries: CalendarEntry[] }) {
    const ref = useRef<HTMLElement>(null);

    useEffect(() => {
        if (ref.current) {
            ref.current.entries = entries;
        }
    }, [entries]);

    return <lms-calendar ref={ref as React.RefObject<any>} heading="Schedule" />;
}

Vue 3 example



For Angular or Lit apps, treat <lms-calendar> as any other custom element; Angular 14+ supports standalone custom elements automatically, and Lit components can include it via unsafeStatic('lms-calendar') if needed.

Performance and state tips

Testing hooks

Troubleshooting

Further Reading