Developer Guide
This guide documents the internal mechanisms of Kalendus so contributors can diagnose issues, extend features, and keep behavior consistent across views.
Architecture Snapshot
| Layer | Responsibility | Key files |
|---|---|---|
| Reactive controllers | Per-instance state (viewMode, activeDate) and navigation helpers |
src/lib/ViewStateController.ts |
| Entry processing | Validate, expand, and cache entries whenever .entries changes |
src/lms-calendar.ts (willUpdate, _computeEntryCaches) |
| Layout pipeline | Convert expanded entries into visual boxes for each view | src/lib/LayoutCalculator.ts, src/lib/allDayLayout.ts, src/lib/SlotManager.ts |
| Rendering shells | View-specific components (Header, Week, Month, Day, Year) | src/components/*.ts |
| Localization | Message bundle + generated locale templates | src/lib/messages.ts, src/generated/locales/*.ts |
Typical render flow
- Inputs change (usually
.entries,.locale, or CSS width) →willUpdatevalidates entries, sorts timed items, and precomputes derived caches. - Expanded entries are stored once per update, along with: ISO-date buckets (
_expandedByISODate), per-day counts (_entrySumByDay), and layout caches invalidated for day/week views. - View selection uses
ViewStateControllerand user events (switchview,switchdate,jumptoday). - Week context (
computeWeekDisplayContext) reads CSS tokens to decide how many day columns to render and whether the “peek” navigation should be shown. - SlotManager + LayoutCalculator convert expanded entries into positioned
<lms-calendar-entry>elements for each view, reusing memoized layout results whenever possible.
Key Mechanisms
Entry validation & expansion
willUpdatetreats missingtimeas all-day, so consumers can omittimefor all-day blocks._expandEntryMaybesplits multi-day entries into per-day slices and annotatescontinuationmetadata (index,total,has,is)._entrySumByDaykeeps ISO (YYYY-M-D) keys. The Year view, mobile “month-dot” badges, and responsive indicators consume this map directly._expandedByISODateis the authoritative lookup for day/week queries. Always prefer reading from it instead of re-splitting the raw entry array.
Layout pipeline
- Timed entries:
_renderDayEntriesWithSlotManagermaps each entry to aLayoutCalculatorbox. Results are cached per ISO date, so repeated renders only reconstruct DOM nodes when data changes. - All-day entries:
allocateAllDayRowsreceives normalizedAllDayEventrecords (id,days,isMultiDay) to determine row stacking. Cache keys include the condensed-week offsets so each peek window reuses assignments. - SlotManager abstracts slot naming and CSS generation. Prefer
slotManager.generatePositionCSS+slotManager.calculateAccessibilityinstead of duplicating grid math.
Condensed week views
computeWeekDisplayContext(activeDate, firstDayOfWeek, calendarWidth, hostEl) returns:
visibleDates,visibleStartIndex,visibleLength: used by<lms-calendar-week>to render 3-day peeks (or any other configured size).gridColumns: CSS string applied to the week component (var(--time-column-width) repeat(n, 1fr)).isCondensed: instructs navigation handlers to emitpeek-navigateevents, shifting the window left/right without changing the active date.
To customize behavior, override CSS tokens on the host component:
lms-calendar {
--week-day-count: 7; /* desktop columns */
--week-mobile-day-count: 4; /* columns below breakpoint */
--week-mobile-breakpoint: 640px;
}
Localization workflow
- Message keys live in
src/lib/messages.ts. Add new keys there, then runbun run i18n:extract/bun run i18n:buildto updatesrc/generated/locales/*.ts. - Consumers can override UI text by providing new target locales or by translating the generated template files.
- Common a11y strings (
previous,next,events,viewLabel) are part of the message bundle. Prefer retrieving them viagetMessages(locale). - Locale loading gating:
isLocaleLoaded(locale)checks synchronously whether a locale's chunk is available. The calendar'swillUpdatesets_localeReady = falsefor unloaded locales, hiding UI chrome viavisibility: hiddenuntil the chunk loads. A_localeVersioncounter then increments, busting Lit's dirty-check on all child components sogetMessages()returns correct translations on re-render. English is always loaded (zero-cost).
Common Use Cases / Troubleshooting
| Scenario | Recommended steps |
|---|---|
| Add a new locale | 1) Append code to lit-localize.json. 2) Run bun run i18n:extract. 3) Translate strings in src/generated/locales/<code>.ts. 4) Register the template map in src/lib/messages.ts and lit-localize.json. 5) Add sample Storybook locale if needed. |
| Different condensed-window sizes | Override --week-mobile-day-count or --week-day-count. The controller automatically clamps between 1 and 7 days and re-centers around the active date. |
| All-day events overlap incorrectly | Ensure entries without time remain all-day; avoid providing time ranges shorter than 24 hours for all-day blocks. Inspect allocateAllDayRows inputs by logging allDayLayoutEvents. |
| Performance spikes with huge entry sets | Verify that layout caches are reused. When adding new derived data, store per-entry metadata during _expandEntryMaybe to avoid re-hashing in inner loops. |
| Custom analytics on view changes | Listen for switchview, switchdate, peek-navigate, and expand events on <lms-calendar>. Drill targets (e.g., 'day' vs 'month') are included in the expand detail payload. |
Testing Tips
- Unit tests live under
test/unit/. Usebun testfor the full suite orNODE_OPTIONS=--experimental-vm-modules mocha path/to/file.test.tsfor targeted runs. - Week/day layout logic has dedicated tests in
test/unit/lib/allDayLayout.test.ts,test/unit/lib/ViewStateController.test.ts,test/unit/LayoutCalculator.test.ts, andtest/unit/week-row-allocation.test.ts. - When modifying CSS token behavior or condensed-week logic, add/extend tests in
test/unit/lib/weekDisplayContext.test.ts(create this file if missing) to ensure the context math stays stable.
Adding a New Locale
- Add the locale code to
lit-localize.jsontarget locales - Run
bunx lit-localize extractto generate the template file - Translate strings in
src/generated/locales/<locale>.ts - Add a loader entry in
src/lib/messages.ts(localeLoadersmap) - Optionally add a
LUXON_LOCALE_MAPentry inlocalization.tsif the locale code differs from Intl/Luxon conventions
Staying Consistent
- Keep date keys canonical (
YYYY-MM-DD) across caches and map lookups. - Prefer declarative data flow (“compute once, read many”)—controllers and helper modules exist so views stay dumb.
- When introducing new view-specific features, document their CSS tokens and event payloads in
README.mdplus the developer guide so integrators stay informed.