npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@liturgical-calendar/components-js

v2.11.0

Published

Liturgical calendar components for javascript: an html select populated with liturgical calendars supported by the Liturgical Calendar API; form controls for parameters that are supported by the Liturgical Calendar API; a webcalendar; and liturgy of the d

Readme

@liturgical-calendar/components-js

A reusable ES6/JavaScript component library for the Liturgical Calendar API. Build liturgical calendar interfaces without a build step.

LiturgyOfAnyDay Example

Quick Start

<script type="module">
import {
    ApiClient,
    CalendarSelect,
    LiturgyOfTheDay
} from 'https://cdn.jsdelivr.net/npm/@liturgical-calendar/components-js@latest/+esm';

ApiClient.init().then((apiClient) => {
    const calendarSelect = new CalendarSelect('en-US')
        .class('form-select')
        .allowNull();
    calendarSelect.appendTo('#calendar-container');

    const liturgy = new LiturgyOfTheDay('en-US')
        .class('liturgy-widget')
        .showReadings(true)
        .listenTo(apiClient);
    liturgy.appendTo('#liturgy-container');

    apiClient.listenTo(calendarSelect);
    apiClient.fetchCalendar('en').catch((error) => {
        console.error(`Could not load the calendar: ${error.message}`);
    });
}).catch((error) => {
    console.error(`Could not reach the API at ${error.url}: ${error.message}`);
});
</script>

Components

| Component | Description | | -------------------------------------------- | -------------------------------------------------- | | ApiClient | Manages API communication and data fetching | | ApiBase | One API base: its URL, index and cache | | ApiClientError | Error carrying url, status, statusText, body | | CalendarSelect | Dropdown for selecting liturgical calendars | | RiteSelect | Dropdown for selecting the liturgical rite | | ApiOptions | Form controls for API parameters | | WebCalendar | Full calendar table with customizable display | | LiturgyOfTheDay / LiturgyOfAnyDay | Daily liturgy widgets | | PathBuilder | API URL builder tool | | ReadingsRenderer | Lectionary readings, and the schema vocabulary | | CalendarResourcePicker | Rite + calendar picker, bundled and wired | | DayViewer | Complete "liturgy of any day" page in one mount | | CalendarControls | Rite + calendar + ApiOptions, wired, no renderer | | CalendarViewer | CalendarControls paired with a WebCalendar | | ApiExplorer | CalendarControls paired with a PathBuilder | | Utils | Utility functions for locale detection |

Meta-components

Meta-components bundle a fixed, tested wiring of the library's existing components — including the ordering requirements and silent-failure traps that come with wiring them by hand — behind a single mount call, and expose the wired children publicly for anything a theme bag doesn't cover:

  • CalendarResourcePicker — a RiteSelect and a filtered CalendarSelect bundled into one mount, for picking a national or diocesan calendar resource id.
  • DayViewer — a complete "liturgy of any day" page (rite, calendar, locale and the LiturgyOfAnyDay widget) bundled into one mount, correctly wired so a rite change actually reaches the API request.
  • CalendarControls — a RiteSelect, a CalendarSelect and an ApiOptions bundled into one mount and wired to an ApiClient, with no renderer of its own. CalendarViewer and ApiExplorer are both built on it; a fourth, unbundled consumer renders with FullCalendar instead of WebCalendar, which is why the renderer was kept out of this class rather than folded in. selection and the chainable onSelectionChange( callback ) publish what is selected and which ApiOptions inputs that selection predetermines, so a consumer styling a read-only input no longer hand-wires a change listener.
  • CalendarViewerCalendarControls paired with a WebCalendar, the whole calendar-table example page in one mount call.
  • ApiExplorerCalendarControls paired with a PathBuilder, with fetching turned off, for a page whose only job is building and previewing an API request URL.

All five take a synchronous constructor plus a static async mountInto(), and a theme bag written in HTML roles (select, input, label, wrapper) rather than framework class names — the library ships no framework-specific CSS. A nested apiOptions key extends the same vocabulary to every one of an ApiOptions' ten inputs, so styling a bundled form needs none of the process-wide Input.setGlobal* mutations.

A preset names the standard class set for a framework rather than making every page write it out, and remains overridable per key:

theme: 'bootstrap5';
theme: { preset: 'bootstrap5', riteSelect: { wrapperClass: 'col col-md-2' } }

ThemePreset.BOOTSTRAP_4 and ThemePreset.BOOTSTRAP_5 are the two names; anything else throws, listing them. What each supplies differs, because the frameworks differ:

| preset | select | input | label | | ------------ | -------------- | -------------- | ------------ | | bootstrap5 | form-select | form-control | form-label | | bootstrap4 | form-control | form-control | — none |

bootstrap4 supplies no label because Bootstrap 4 has no .form-label; emitting one would invent a class the framework does not define.

A preset covers controls and never layout — no wrapper class, no grid span, and no class the framework does not itself define — and it does not detect which framework a page loaded. It does style the whole ApiOptions form.

A key written beside a preset replaces that preset's value; class tokens are not merged. So { preset: 'bootstrap5', select: 'form-select-sm' } yields form-select-sm alone, losing form-select. Repeat what you want to keep: select: 'form-select form-select-sm'.

See the meta-components documentation for the full contract, including the theme bag's resolution rules, the presets, and the reject/resolve behaviour of mountInto().

Using two API bases on one page

Each ApiClient is bound to an ApiBase — one object per API base URL, owning that base's calendar index and its response cache. Passing a client to a component binds the component to that base:

const dev = await ApiClient.init('http://localhost:8000');
const prod = await ApiClient.init('https://litcal.johnromanodorazio.com/api/dev');

const devSelect = new CalendarSelect({ locale: 'en', apiClient: dev });
const prodSelect = new CalendarSelect({ locale: 'en', apiClient: prod });

Omitting apiClient binds to the first base initialized, so single-base pages need no change. Once more than one base is registered, an unbound component warns and names the base it chose.

PathBuilder takes no apiClient: it reads the base of the ApiOptions and CalendarSelect handed to it, and throws if those two disagree. CalendarSelect.linkToNationsSelect() throws on the same mismatch.

ApiClient.init() returns a new client on every call, including for a base already registered — only the metadata and cache are shared. That is what allows two clients on one API to hold different rites:

import { ApiClient, Rite } from '@liturgical-calendar/components-js';

const BASE = 'https://litcal.johnromanodorazio.com/api/dev';

const roman = await ApiClient.init(BASE);
const ambrosian = await ApiClient.init(BASE);
ambrosian.rite(Rite.AMBROSIAN);

See examples/CompareBases/ for a complete two-pane page, and the ApiClient documentation for error handling and caching.

Documentation

Examples

The examples/ folder contains complete working examples:

| Example | Description | | --------------------------------------- | ---------------------------------------------- | | LiturgyOfTheDay | Today's liturgy with calendar/locale selection | | LiturgyOfAnyDay | Browse any date with lectionary readings | | WebCalendar | Full calendar table with display options | | PathBuilder | Interactive API URL builder | | RiteSelectChain | Rite to nation to diocese chain | | RiteSelectPathBuilder | The rite as an API path segment | | RiteSelectWebCalendar | A rendered Ambrosian calendar | | CompareBases | Two API bases side by side on one page |

To run examples:

  1. Start the Liturgical Calendar API on localhost:8000
  2. Serve the project: python3 -m http.server 8090
  3. Open http://localhost:8090/examples/LiturgyOfTheDay/

Exports

export {
    // Components
    ApiClient,
    ApiClientError,
    ApiBase,
    CalendarSelect,
    RiteSelect,
    ApiOptions,
    WebCalendar,
    LiturgyOfTheDay,
    LiturgyOfAnyDay,
    PathBuilder,
    ReadingsRenderer,
    CalendarResourcePicker,
    DayViewer,
    CalendarControls,
    CalendarViewer,
    ApiExplorer,
    Input,
    Utils,

    // Enums
    Grouping,
    ColorAs,
    Column,
    ColumnOrder,
    DateFormat,
    GradeDisplay,
    ApiOptionsFilter,
    CalendarSelectFilter,
    YearType,
    LatinInterface,
    Rite,
    RiteProperties,

    // Metadata
    VERSION
}

Version

VERSION is this package's own version as a string, so a running page can report which build it is on:

import { VERSION } from '@liturgical-calendar/components-js';

console.debug(`components-js ${VERSION}`);

This matters when the library is resolved more than one way. A page that loads it from a symlinked local build in development and a pinned CDN tag in production can silently run two different versions, and a pinned importmap is not evidence of what actually loaded: jsDelivr rebuilds +esm bundles, and a stale browser cache can serve an old module from a URL that reads current. VERSION is what the loaded module itself says, so it answers the question the URL cannot.

It is typed string rather than a string literal, so a consumer's version-floor comparison type-checks instead of raising TS2367.

Key Features

  • No build step required - Use directly from CDN with ES6 imports
  • Chainable configuration - Fluent API for all components
  • Automatic caching - Reduces redundant API requests
  • Locale support - 13 languages supported; every component takes a locale as either a string or an Intl.Locale, and treats null and undefined alike as "not supplied"
  • Screen-reader announcements - WebCalendar and LiturgyOfAnyDay replace all of their content when a select changes, and each announces a short summary of what it now shows through a visually-hidden aria-live="polite" region, so the change is not silent. The first render is not announced, and announceUpdates(false) turns the region off for a page that already owns a live region of its own
  • Bootstrap compatible - Easy integration with Bootstrap 5
  • TypeScript definitions - Full type support in dist/index.d.ts

Browser Support

Requires <script type="module">, and a browser with ES2022 support: Chrome/Edge 94+, Firefox 93+, Safari 15.4+. On Node.js the floor is 16.11+ (18+ recommended).

ES6 module support alone is not enough. The published code uses ES2022 runtime APIs — Object.hasOwn() and Error's cause option — as well as static # private class fields. A compiler target alone cannot transpile a runtime API away, and the published build ships no polyfills, so an older engine fails at run time on the artifact as shipped. Consuming the package through your own toolchain lifts that: transpile the syntax and polyfill the two APIs (core-js does both) and the floor is whatever your build targets.

Development

yarn install          # Install dependencies
yarn compile          # Compile TypeScript
yarn test             # Run tests
yarn storybook        # Launch Storybook

See Storybook documentation for detailed setup.

License

ISC