Skip to content

Repository files navigation

keen_web_daterangepicker

Phoenix LiveView wrapper for @keenmate/web-daterangepicker — a themeable date & date-range picker web component with single/range/multiple selection, a time picker, month grids, disabled-date handling, special-date badges, and full keyboard navigation.

One package covers both plain HEEx and LiveView. The upstream JS + CSS are bundled, so no npm install is required.

What's New in v2.0.0-rc.1

Majors to 2.0 to track the upstream component's 2.x line. Bundles upstream @keenmate/web-daterangepicker v2.0.0-rc06, spanning two upstream releases: the rc05 "core adoption" (the element rebuilt on @keenmate/web-components-core / BlissElement) and the rc06 native form model. The headline change is form integration — read the first bullet before upgrading.

  • Forms are now native — no hook needed, and the wrapper's hidden input is gone. <web-daterangepicker> is now a form-associated custom element: the wrapper puts name on the element, which renders its own hidden <input> inside the <form> and keeps it in sync with every pick — across inline / floating / modal. So a plain <.web_daterangepicker field={@form[:stay]} /> works with phx-change / phx-submit / form.reset() without hook={true} (previously forms required the hook to sync a wrapper-rendered hidden input, and inline pickers submitted nothing). Behavior change: the submitted value is now stable ISO-8601 — a range is "2026-06-15/2026-06-20" (a / interval, not the old " - " join); a server parsing form params must expect this. New value_format attr ("iso" | "json" | "array") picks the shape.
  • Upstream core rework (rc05), no wrapper API change. The element's hand-rolled custom-element plumbing moved to the shared core (attribute parsing, reactivity, reflection, pre-upgrade property lifting); positioning routes through core (so @floating-ui/dom is no longer a separate concern), and logging moved onto core's category loggers. All internal — the bundled daterangepicker.{js,css,d.ts} were re-cut from the rc06 dist and upstream_version/0 now reports "2.0.0-rc06". The dist bundles core + floating-ui inline, so the single-file import stays self-contained (no new npm dependency).
  • Fixes you get for free. The selection summary is now text-selectable (copy day counts / prices / booking refs written via show_summary/3); the rolling month/year selector no longer bleeds over the sticky header; badge tooltips no longer render off-screen; and a dual-path input set as both attribute and property (e.g. disabled_dates / special_dates seeded declaratively then pushed) now resolves property-wins on upgrade.
  • New upstream escape hatches (optional). The element gains managed onDateSelect / onChange / onCustomAction handler properties (each receives the CustomEvent), an el.picker getter exposing the live engine, and el.form / event.target.form resolution. The wrapper's hook keeps using addEventListener, so these are purely additive — reach for them only in your own client-side phx-hook.

What's New in v1.0.0-rc.1

First release candidate. Bundles upstream @keenmate/web-daterangepicker v2.0.0-rc03 — no npm install.

  • Component — <.web_daterangepicker> covers the full upstream API as a pure renderKeenmate.WebDaterangepicker.Components.web_daterangepicker/1 declares a typed attr/3 for every documented <web-daterangepicker> attribute (booleans, values:-whitelisted enums, integers, date strings), mapping snake_case in HEEx to kebab-case on the element (selection_modeselection-mode). Booleans render as explicit "true"/"false" because several upstream booleans default to true and need a real opt-out, not HTML presence. The initial value is emitted on the element, so a standalone <.web_daterangepicker value="…" /> shows its selection. No GenServer, no state — the same call works identically in a dead view and a LiveView.
  • One-command installer — mix keen_web_daterangepicker.install — Wires a standard esbuild Phoenix app for you: imports the bundled daterangepicker.js + hook into assets/js/app.js, registers KeenWebDaterangepickerHook on your LiveSocket (merging into the stock hooks: {...} object), and imports daterangepicker.css into assets/css/app.css. Idempotent and conservative — anything it can't confidently patch is left untouched and printed as a manual step. --dry-run previews.
  • LiveView events + form integration — opt in with hook={true} — The hook forwards the component's date-select/change/custom-action events to the server as "web_daterangepicker:select" / ":change" (payload {id, value}) and ":custom_action" (payload {id, data} — the clicked button's data-* map), so handle_event/3 matches by id. value is the calendar-date string (never a timezone-shifting ISO timestamp); inline pickers, which have no text input, derive it from the event's typed date fields. For forms, pass field={@form[:stay]} — the picker lives in Shadow DOM so isn't natively form-associated, so the wrapper renders a hidden input the hook mirrors each pick into (dispatching a native input event), and phx-change/phx-submit see the value like any input.
  • Server-driven updates + feedback — push_update/3, show_message/4, show_summary/3, show_loader/3 — Push a new value, restrictions, or special dates across the phx-update="ignore" boundary with push_update(socket, "checkout", min_date: checkin) (snake_case keys → camelCase JS properties; attribute-only inputs like locale/placeholder fall back to setAttribute). And drive the picker's message / summary / loader blocks from handle_event/handle_info with no client JS (plus hide_* / toggle_* and the low-level push_call/4) — e.g. show_loader(socket, "cal", :summary) then show_summary(socket, "cal", price_html) when an async lookup returns.
  • Read-only lock — lock/3, unlock/3, toggle_lock/3, and a declarative readonly attribute — Freeze user interaction while keeping the value readable (unlike disabled, which greys the input and, for an inline picker, does nothing). The motivating case is locking a confirmed booking so the range can't be changed. Lock everything, or compose aspects — lock(socket, "stay", [:selection, :actions]) freezes the range and buttons while leaving </> month browsing live (aspects: :selection, :navigation, :actions, :open). For a static full lock at render time, add readonly. The programmatic API (push_update/3, clearSelection) keeps working while locked.
  • Declarative localization, no hook needed — custom_strings / month_names / weekday_namesmonth_names={~w(Leden Únor …)} and weekday_names={~w(Ne Po …)} render as native pipe-delimited month-names / weekday-names attributes straight on the element, so a purely declarative picker localizes its calendar labels without hook={true}. weekday_names is indexed from Sunday (index 0 = Sunday); week_start_day only rotates the display. custom_strings (a map of UI-label overrides) is property-only upstream, so it ships as JSON and needs the hook. All three can also be pushed at runtime via push_update/3 for a server-side locale switcher.
  • Docs — a Theming guide + an LLM ai/ knowledge base ship in the package — The picker is styled entirely through CSS custom properties, so the Theming guide explains the two-tier --drp-* / --base-* cascade and the three integration paths (pure-admin, own base layer, standalone), plus dark-mode signals, --drp-rem sizing, and the unlayered-reset footgun. Alongside it, a flat-text ai/ knowledge base (ai/INDEX.txt + topic files + a 13-recipe ai/cookbook.txt), fronted on hexdocs by a Using with AI agents guide, teaches a coding agent the component, hook, push_update/3, restrictions, special dates, i18n, forms, and theming.
  • Package — the live examples site is linked from Hexmix.exs carries a homepage_url and an "Examples site" link pointing at keen-web-daterangepicker.keenmate.dev, a 1:1 mirror of the upstream example pages.

Install

def deps do
  [
    {:keen_web_daterangepicker, "~> 2.0"}
  ]
end

Currently a release candidate. The current release is 2.0.0-rc.*, and Mix skips pre-releases for a plain ~> 2.0 constraint. Until 2.0.0 is final, opt in by requiring the pre-release explicitly:

{:keen_web_daterangepicker, "~> 2.0.0-rc"}

Wire up the assets

The bundled JS and CSS live in this library's priv/static/ directory.

Quick start — the installer

On a standard esbuild Phoenix app, let the installer wire everything for you:

mix keen_web_daterangepicker.install

It edits assets/js/app.js (imports + LiveSocket hook registration) and assets/css/app.css (stylesheet import), idempotently — re-running is safe. Pass --dry-run to preview. Anything it can't confidently patch is left untouched and printed as a manual step. To wire it by hand, use one of the two paths below.

Path A — import from deps/ (esbuild, the Phoenix default)

In assets/js/app.js:

import KeenWebDaterangepickerHook from "../../deps/keen_web_daterangepicker/priv/static/keen_web_daterangepicker_hook.js";
import "../../deps/keen_web_daterangepicker/priv/static/daterangepicker.js";

let liveSocket = new LiveSocket("/live", Socket, {
  hooks: { KeenWebDaterangepickerHook },
  params: { _csrf_token: csrfToken }
});

In assets/css/app.css:

@import "../../deps/keen_web_daterangepicker/priv/static/daterangepicker.css";

The web component injects its own styles into its Shadow DOM, so the CSS import is only strictly required for positioning_mode="inline" (which renders in light DOM) and for overriding the theme variables at document level. Importing it is harmless and recommended.

Path B — serve directly from the dep's priv/static

In your endpoint, add another Plug.Static:

plug Plug.Static,
  at: "/keen_web_daterangepicker",
  from: {:keen_web_daterangepicker, "priv/static"},
  gzip: false,
  only: ~w(daterangepicker.js daterangepicker.css keen_web_daterangepicker_hook.js)

Then reference /keen_web_daterangepicker/daterangepicker.js from your layout <script type="module"> tag and the CSS from a <link>.

Use the component

Import the component in the module where you render templates (a LiveView, a LiveComponent, or your MyAppWeb.html_helpers/0):

import Keenmate.WebDaterangepicker.Components

Declarative — no JavaScript needed

<.web_daterangepicker
  id="checkin"
  placeholder="Pick a date"
  min_date="2026-01-01"
  max_date="2026-12-31"
/>

Date range with two months and a summary

<.web_daterangepicker
  id="stay"
  selection_mode="range"
  visible_months_count={2}
  disabled_weekdays={[0, 6]}
  disabled_dates_handling="block"
/>

LiveView events

Set hook={true} and the hook forwards the upstream date-select, change, and custom-action events to your LiveView (pass a string instead to name a custom hook):

<.web_daterangepicker
  id="stay"
  hook={true}
  selection_mode="range"
/>
def handle_event("web_daterangepicker:change", %{"id" => "stay", "value" => value}, socket) do
  # value is a calendar-date string, e.g. "2026-04-30 - 2026-05-04"
  {:noreply, assign(socket, :stay, value)}
end

Dates are strings, not timestamps. <web-daterangepicker> is a calendar-date picker. The value the hook forwards is the upstream formattedValue — a "YYYY-MM-DD" string. Keep dates as strings end-to-end; converting to an ISO timestamp (toISOString()) shifts them a day for non-UTC users.

Driving the component from the server

Because the element renders phx-update="ignore", LiveView's DOM patcher won't push new values or restrictions to it. Use Keenmate.WebDaterangepicker.push_update/3 (the sanctioned channel — it sends the event KeenWebDaterangepickerHook listens for):

# Cascading pickers — check-in changed, raise the check-out minimum
def handle_event("web_daterangepicker:change", %{"id" => "checkin", "value" => v}, socket) do
  {:noreply, Keenmate.WebDaterangepicker.push_update(socket, "checkout", min_date: v)}
end

# Server-authoritative availability — push freshly-booked dates as disabled
Keenmate.WebDaterangepicker.push_update(socket, "stay", disabled_dates: booked_dates(listing))

Snake_case keys become camelCase JS properties, so any documented property works (value, min_date, max_date, disabled_dates, special_dates, selection_mode, disabled, …). Only the keys you pass are sent. The target needs hook={true} and a matching id.

Server-driven feedback (loader / summary / message)

Upstream v2.0.0 gave the picker's message, summary, and loader blocks an imperative API. The wrapper exposes it as LiveView helpers, so you can drive the picker's feedback UI from the server — a spinner while an async lookup runs, then the result — with no client JavaScript. Like push_update/3, these need hook={true}:

# Show a spinner in the summary block, look the price up, then pin it
def handle_event("web_daterangepicker:change", %{"id" => "stay", "value" => _v}, socket) do
  send(self(), :price_it)
  {:noreply, Keenmate.WebDaterangepicker.show_loader(socket, "stay", :summary)}
end

def handle_info(:price_it, socket) do
  {:noreply,
   socket
   |> Keenmate.WebDaterangepicker.hide_loader("stay", :summary)
   |> Keenmate.WebDaterangepicker.show_summary("stay", "<strong>€474</strong> / 6 nights")
   |> Keenmate.WebDaterangepicker.show_message("stay", "Available!", type: "success", auto_hide: 3000)}
end

Helpers: show_message/4 · hide_message/2 · toggle_message/4 · show_summary/3 · hide_summary/2 · refresh_summary/2 · show_loader/3 · hide_loader/3 · toggle_loader/3 (loader target is :calendar | :message | :summary). All are thin wrappers over push_call/4, which invokes any element method by name.

Special dates (holidays, events, prices)

Decorate specific days with badges, tooltips, and CSS classes. special_dates is a property-only input upstream, so the wrapper ships it as JSON and the hook applies it — it requires hook={true}:

<.web_daterangepicker
  id="calendar"
  hook={true}
  special_dates={[
    %{date: "2026-12-25", badge_text: "🎄", day_tooltip: "Christmas", day_class: "holiday"},
    %{date: "2026-07-04", badge_text: "🎆", day_tooltip: "Independence Day", day_class: "holiday"}
  ]}
/>

The map keys follow the *_member attributes (date_member, badge_text_member, day_class_member, …); the defaults above match upstream's canonical shape.

Form integration

Pass a Phoenix.HTML.FormField and the component fills in id, name, and the initial value. The picker is a form-associated custom element (upstream v2.0.0-rc06): given a name it renders its own hidden <input> inside the <form> and keeps it in sync with every pick, so form integration works with no hook — including inline pickers:

<.simple_form for={@form} phx-change="validate">
  <.web_daterangepicker field={@form[:stay]} selection_mode="range" />
</.simple_form>

phx-change and phx-submit then see the selected value in params[form_name]["stay"] just like a native input. The submitted value is stable ISO-8601, independent of the display masks — a single date is "2026-06-15", a range is "2026-06-15/2026-06-20", and multiple dates are joined by ,. Set value_format="json" for a JSON.stringifyd selection, or value_format="array" to submit multiple name[] inputs (a range flattens to start, end). Add hook={true} only when you also want server-side handle_event/3, push_update/3, or the property-only custom_strings / special_dates.

Attributes

Every documented attribute from the upstream component is exposed as a typed attr/3. See Keenmate.WebDaterangepicker.Components.web_daterangepicker/1 for the full list, or the upstream usage docs for what each one does.

Snake_case in HEEx maps to kebab-case on the rendered element: selection_modeselection-mode, visible_months_countvisible-months-count, etc.

Theming

The picker is styled entirely through CSS custom properties, in a two-tier cascade: component tokens (--drp-*) each fall back to a shared design token (--base-*), so it works out of the box, inherits a design system when one is present, and is overridable per-instance from HEEx via class / style.

See the Theming guide for the three integration paths:

  • With pure-admin — the picker inherits the --base-* tokens pure-admin provides, matching palette and dark mode with zero configuration.
  • With other KeenMate components (no pure-admin) — define the --base-* layer yourself once as a single source of truth; every component reads it.
  • Standalone — built-in light-dark() fallbacks give a working light/dark theme; override --drp-* to restyle just the picker.

Versioning

keen_web_daterangepicker versions are independent of @keenmate/web-daterangepicker. The bundled upstream version is reported by:

Keenmate.WebDaterangepicker.upstream_version()
#=> "2.0.0-rc06"

For LLMs and coding agents

A flat-text knowledge base for coding agents ships in the package under the ai/ folder — modelled on the sibling keen_web_multiselect layout but written for this wrapper. Browse it in the repository, or read it from deps/keen_web_daterangepicker/ai/ in a consuming app: start at ai/INDEX.txt (keyword index + common questions) or ai/cookbook.txt (copy-paste recipes).

On hexdocs, see the Using with AI agents page. ex_doc also publishes a machine-readable llms.txt for the package (the llms.txt convention), so agents that fetch hexdocs.pm/keen_web_daterangepicker/llms.txt get a structured index of the docs.

License

MIT.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages