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

@lekoala/date-picker

v0.3.0

Published

Native-first date picker and inline calendar primitives

Readme

@lekoala/date-picker

Small, native-first date picker and inline calendar primitives.

v0.2: the interaction contract, event names, public styling tokens and the component API are frozen — see docs/DECISIONS.md. Internal DOM and class names are .dp-* implementation details and stay private.

This project fills the gap between a normal editable date field and a full scheduling calendar. A plain date input remains the right answer for simple CMS/editing forms. This component becomes useful when a date has context: availability, disabled days, annotations, remote state, start/end relationships, or a mini calendar that navigates another view.

The two primitives

<date-calendar>

An inline calendar with real month/year navigation, keyboard support and three distinct pieces of state:

  • display — the month being rendered (YYYY-MM);
  • focusedDate — the roving keyboard target (YYYY-MM-DD);
  • value — the selected date (YYYY-MM-DD, optional).

display and focusedDate are coupled during navigation/display: changing the displayed month re-clamps the focused day into that month, because the roving-tabbable grid cell must stay visible. value and activation stay independent of both.

User activation is exposed separately as dateactivate. This is what makes the same calendar useful as a selector and as a mini navigator.

<date-calendar value="2026-09-10" fixed-weeks></date-calendar>

For a navigator that should not own a selection:

<date-calendar id="mini" selection="none" fixed-weeks show-week-numbers></date-calendar>
<script type="module">
  const mini = document.querySelector("#mini");
  mini.addEventListener("dateactivate", (event) => {
    agenda.gotoDate(event.detail.date);
  });
</script>

<date-picker>

A composition around a real editable text input. The user sees and edits a short locale-aware numeric date while form submission stays canonical ISO.

Use <date-picker> when choosing a date benefits from calendar context. For a simple editable date field, a normal input with formatting and validation is the right tool instead.

<date-picker value="2026-09-10" locale="fr-BE">
  <input name="appointment_date" required>
</date-picker>

With JavaScript enabled the input displays 10/09/2026; the hidden submitted value remains 2026-09-10. The visible input keeps the label, focus, required state and validation UI.

By default the calendar opens when the field receives focus — without stealing the keyboard focus, so typing works immediately. open-on-focus="false" makes the calendar opt-in (trigger button or ArrowDown). Under readonly the value stays editable-by-calendar-off but is still submitted; under disabled the field is also unsubmitted.

Start/end ranges

Two product shapes cover ranges.

One shared calendar surface — two editable fields wired to a single picker popup:

<date-picker range>
  <input data-range-start name="arrival" aria-label="Arrival">
  <input data-range-end name="departure" aria-label="Departure">
</date-picker>

Opening from a field targets that bound; picking the first date moves the active endpoint to the other bound without closing, and the second pick completes and closes. picker.range is the atomic { start, end } API (single rangechange event).

Two independent pickers — a small external link for visually separate fields:

import { linkDateRange } from "@lekoala/date-picker";

const cleanup = linkDateRange(startPicker, endPicker);

The relationship applies end >= start and start <= end as effective bounds. Neither picker knows about its sibling.

Date + time

A date pairs with one or two native time companions. The picker owns the civil date; the time inputs own their civil time values:

<date-picker>
  <input name="appointment[date]">
  <input type="time" data-time-start name="appointment[time]">
</date-picker>
appointment[date] = 2026-09-10
appointment[time] = 09:30

The application/server combines these civil values and applies the relevant timezone when a datetime/instant is needed. The picker never constructs a combined datetime value.

Availability and per-day metadata

The calendar accepts a source function or { load() } object with the same abortable shape used elsewhere in LeKoala components:

calendar.source = async ({ start, end }, { signal }) => {
  const response = await fetch(`/availability?start=${start}&end=${end}`, { signal });
  return response.json();
};

Accepted payloads are deliberately simple:

{
  dates: {
    "2026-09-10": {
      enabled: true,
      description: "Available morning and afternoon",
      morning: true,
      afternoon: true
    }
  }
}

Business metadata is not interpreted by the core. Consumers can add visual content without replacing the grid cell or its accessibility semantics:

calendar.renderDay = (_date, state) => {
  if (!state.morning && !state.afternoon) return "";
  return `${state.morning ? "●" : "○"}${state.afternoon ? "●" : "○"}`;
};

Date math

The public value model is intentionally boring: canonical YYYY-MM-DD strings. There is no public Date, timestamp, timezone or Temporal dependency.

Pure civil-date helpers are exported under dates:

import { dates } from "@lekoala/date-picker";

const weeks = dates.getMonthWeeks("2026-09-03", { firstDay: 1 });

getMonthWeeks() returns the true 4–6 full civil weeks covering the month. It never pads to six rows. fixed-weeks is a rendering choice made by <date-calendar>.

Accessibility baseline

The calendar follows the WAI-ARIA date-picker/grid interaction model:

  • semantic table[role="grid"];
  • one roving tabindex="0" grid cell;
  • arrow keys move by day/week;
  • Home/End move to week boundaries;
  • PageUp/PageDown move by month;
  • Shift+PageUp/PageDown move by year;
  • Enter/Space activate a date;
  • keyboard movement never implicitly selects;
  • aria-selected identifies the selected date;
  • aria-current="date" identifies today;
  • abbreviated weekday headers keep full abbr names.

The picker popup uses native Popover for the top layer and @lekoala/floating for geometry. It has role="dialog", but it is intentionally not marked aria-modal because the current implementation does not inert the rest of the document.

The calendar trigger is a real button painted inside the field's own box (like the native input[type=time] indicator): the field reserves its place with padding-inline-end, keeps its own border and background, and its focus ring wraps the trigger too. The trigger draws no chrome of its own and shows a small inner ring when focused.

When the popup opens on the field's focus, grid navigation is reachable via ArrowDown (or the trigger button) so the focus-restore/Escape flow stays predictable. Closing returns focus to the control that opened the popover: the field when it opened by focus, the trigger when it was activated. In range mode focus follows the active bound instead, because a first pick moves the workflow to the other bound.

See docs/ACCESSIBILITY.md.

Install / develop

bun install
bun run dev

If a previous bun run dev is still holding port 4859, clear it with bun run dev:kill (or restart in one go with bun run dev:restart).

Tooling is aligned with the current LeKoala component repos:

  • Bun 1.4.2
  • TypeScript 7.0.2 (checkJs + declaration emit)
  • Biome 2.5.13
  • Playwright 1.63.0
  • @lekoala/floating ^0.2.0

Useful commands:

bun run test
bun run typecheck
bun run lint
bun run build
bun run test:browser
bun run test:browser:all
bun run check:all

Build outputs

bun run build produces:

dist/date-picker.js
dist/date-picker.min.js
dist/date-picker.css
dist/date-picker.min.css
dist/date-picker.standalone.min.js
dist/types/**

The default ESM exports point at src/, matching the native-first development style of @lekoala/combobox. The classic build is file:// friendly; the standalone build additionally injects the component CSS.

Scope

Included in v0.2:

  • inline single-date selection;
  • mini-calendar/navigation-only use;
  • month and year selection;
  • previous/next navigation;
  • editable localized input + canonical submitted value;
  • min/max and custom disabled rules;
  • presentation-only range band (highlightedRange);
  • shared-calendar two-field range picker (<date-picker range>);
  • linked start/end pickers (linkDateRange);
  • native time companions (one or two input[type=time] beside the date);
  • Intl month/year control order + month-format="short";
  • RTL through the inherited dir (no isRTL option);
  • readable keyboard focus on disabled/selected days;
  • compact-consumer CSS tokens (--dp-header-control-size, --dp-day-radius, …);
  • async per-day state with cancellation;
  • custom day decorations that cannot replace the accessible cell;
  • fixed six-row presentation as an opt-in;
  • ISO week numbers;
  • keyboard and focus contracts.

Deliberately out of scope for v0.2:

  • custom time-picker UI and combined datetime values (only native input[type=time] companions are supported);
  • timezones;
  • in-place single-calendar range selection interaction (use the two-field picker);
  • multi-date selection;
  • recurrence;
  • natural-language parsing;
  • plugin registry;
  • virtualized months;
  • scheduling/time slots.

Docs