# `Localize.Inputs.Date.Components`
[🔗](https://github.com/elixir-localize/localize_datetime_inputs/blob/v0.1.1/lib/localize/inputs/date/components.ex#L3)

HEEx components for locale-aware date form input.

Provides `date_input/1`, `date_range_input/1`, and
`date_range_picker/1`. Built on `calendrical` for
multi-calendar parsing (Gregorian, Buddhist, Japanese,
Islamic, Persian, Hebrew, ROC, …).

## Setup

Add the JS hooks in your `assets/js/app.js`:

    import Hooks from "localize_datetime_inputs"
    let liveSocket = new LiveSocket("/live", Socket, {
      hooks: {
        DatePicker: Hooks.DatePicker,
        DateRangePicker: Hooks.DateRangePicker
      }
    })

## Tolerance of invalid input

These components sit on the render path and never raise on
bad input — the page always renders. Specifically:

* **Unknown `:locale`** — formatting falls back to
  whatever `Localize.Date.to_string/2` returns; on
  failure the cell renders the ISO-8601 form of the
  date (`2026-05-17`).

* **Unknown `:calendar`** — date conversion uses a
  tolerant `Date.convert/2`; on failure the date is
  kept in its original calendar (typically
  `Calendar.ISO`) and rendered using whatever pattern
  lookup succeeds.

* **Blank or unparseable `value`** — the visible text
  input renders empty; the hidden ISO carrier stays
  empty. `Localize.Inputs.Date.Parser.parse_date/2`
  returns `{:ok, nil}` for blanks and
  `{:error, %Calendrical.DateParseError{}}` for
  garbage, never raises.

* **`date_range_input/1` child field atoms** — derives
  `{field}_from` and `{field}_to` via
  `String.to_existing_atom/1`. The atom must already
  exist (it does, because your changeset/schema defines
  it for form parsing); if it doesn't, an `ArgumentError`
  surfaces at render time and points at the missing field.

* **`DatePickerLive` malformed cursor / month** — the
  server-rendered grid uses tolerant `safe_convert/2` and
  `safe_build_date/4` helpers. An invalid year/month
  combo for the target calendar falls back to today
  rather than 500ing the LiveView.

# `date_input`

Locale-aware date input with a popup calendar grid.

Renders a text input that accepts the locale's CLDR date
patterns plus ISO-8601, paired with a calendar-icon trigger
that opens a Gregorian month grid for picking. Selecting a
day fills the text input (locale-formatted) and a hidden
sibling input (ISO wire format). On submit the form
receives `params[field]` as `"YYYY-MM-DD"`.

Server-side, parse with `Localize.Inputs.Date.Parser.parse_date/2`
or `Calendrical.Date.parse/2`.

Multi-calendar parsing works (Buddhist, Islamic, Japanese,
etc.) — the user can type in their locale's calendar
representation and the server parses correctly. The popup
grid renders in Gregorian; non-Gregorian grid rendering
is a follow-on enhancement.

### Attributes

* `:form` — the `Phoenix.HTML.Form` the field belongs to.

* `:field` — the form field as an atom.

* `:value` — explicit ISO date string; otherwise pulled
  from `@form[@field]`.

* `:locale` — display locale. Defaults to
  `Localize.get_locale/0`.

* `:min`, `:max` — ISO date strings or `Date` structs.

* `:placeholder` — placeholder text for the text input.

* `:display_format` — one of `:short`, `:medium` (default),
  `:long`, `:full`. Controls the locale-formatted display
  shape; the wire value is always ISO.

* `:js` — set to `false` to skip the `phx-hook` attribute.

* `:class`, `:input_class`, `:button_class`,
  `:overlay_class` — customisation hooks.

### Examples

    <.date_input form={@form} field={:dob} />

    <.date_input
      form={@form}
      field={:start_date}
      min={~D[2026-01-01]}
      max={~D[2026-12-31]}
      display_format={:long}
    />

## Attributes

* `form` (`Phoenix.HTML.Form`) (required)
* `field` (`:atom`) (required)
* `value` (`:any`) - Defaults to `nil`.
* `locale` (`:any`) - Defaults to `nil`.
* `min` (`:any`) - Defaults to `nil`.
* `max` (`:any`) - Defaults to `nil`.
* `placeholder` (`:string`) - Defaults to `nil`.
* `display_format` (`:atom`) - Defaults to `:medium`. Must be one of `:short`, `:medium`, `:long`, or `:full`.
* `calendar` (`:atom`) - Defaults to `:gregorian`.
* `variant` (`:atom`) - Defaults to `:auto`. Must be one of `:auto`, `:dropdown`, or `:sheet`.
* `js` (`:boolean`) - Defaults to `true`.
* `class` (`:string`) - Defaults to `nil`.
* `input_class` (`:string`) - Defaults to `nil`.
* `button_class` (`:string`) - Defaults to `nil`.
* `overlay_class` (`:string`) - Defaults to `nil`.
* Global attributes are accepted. Supports all globals plus: `["disabled", "readonly", "required", "autofocus"]`.

# `date_range_input`

Locale-aware date-range input.

Renders two paired text inputs (from / to) inside a single
grouped wrapper. Each field is independently editable; the
pair submits as `params[field] = %{"from" => "YYYY-MM-DD",
"to" => "YYYY-MM-DD"}`.

Server-side, parse with
`Calendrical.Date.parse_range/2` passing the `{from, to}`
tuple from `params[field]`.

### Attributes

* `:form` — the `Phoenix.HTML.Form` the field belongs to.

* `:field` — the form field as an atom; sub-fields submit
  under `params[field][from]` and `params[field][to]`.

* `:locale`, `:min`, `:max`, `:display_format`, `:variant`,
  `:js` — passed through to both inputs.

* `:class`, `:input_class`, `:button_class`,
  `:overlay_class` — customisation hooks.

### Examples

    <.date_range_input form={@form} field={:stay} />

    <.date_range_input
      form={@form}
      field={:trip}
      min={~D[2026-01-01]}
      max={~D[2026-12-31]}
    />

## Attributes

* `form` (`Phoenix.HTML.Form`) (required)
* `field` (`:atom`) (required)
* `locale` (`:any`) - Defaults to `nil`.
* `min` (`:any`) - Defaults to `nil`.
* `max` (`:any`) - Defaults to `nil`.
* `placeholder_from` (`:string`) - Defaults to `nil`.
* `placeholder_to` (`:string`) - Defaults to `nil`.
* `display_format` (`:atom`) - Defaults to `:medium`. Must be one of `:short`, `:medium`, `:long`, or `:full`.
* `calendar` (`:atom`) - Defaults to `:gregorian`.
* `variant` (`:atom`) - Defaults to `:auto`. Must be one of `:auto`, `:dropdown`, or `:sheet`.
* `js` (`:boolean`) - Defaults to `true`.
* `class` (`:string`) - Defaults to `nil`.
* `input_class` (`:string`) - Defaults to `nil`.
* `button_class` (`:string`) - Defaults to `nil`.
* `overlay_class` (`:string`) - Defaults to `nil`.

# `date_range_picker`

Locale-aware date-range input with a unified popup
calendar (click start, then click end inside the same
grid). Pairs with `RangePicker` JS hook.

Renders two text inputs (visible "from" and "to") plus a
single shared trigger and overlay. The user clicks the
trigger to open the popup, clicks once for the start,
hovers to preview, clicks again for the end. Both text
inputs and both hidden ISO inputs populate.

Submits as `params[field] = %{"from" => "YYYY-MM-DD",
"to" => "YYYY-MM-DD"}`. Server-side, parse with
`Calendrical.Date.parse_range/2` passing the
`{from, to}` tuple.

### Attributes

Same shape as `date_range_input/1`: `:form`, `:field`,
`:locale`, `:min`, `:max`, `:display_format`,
`:calendar`, `:variant`, `:js`, `:class`, etc.

### Examples

    <.date_range_picker form={@form} field={:stay} />

## Attributes

* `form` (`Phoenix.HTML.Form`) (required)
* `field` (`:atom`) (required)
* `locale` (`:any`) - Defaults to `nil`.
* `min` (`:any`) - Defaults to `nil`.
* `max` (`:any`) - Defaults to `nil`.
* `placeholder_from` (`:string`) - Defaults to `nil`.
* `placeholder_to` (`:string`) - Defaults to `nil`.
* `display_format` (`:atom`) - Defaults to `:medium`. Must be one of `:short`, `:medium`, `:long`, or `:full`.
* `calendar` (`:atom`) - Defaults to `:gregorian`.
* `variant` (`:atom`) - Defaults to `:auto`. Must be one of `:auto`, `:dropdown`, or `:sheet`.
* `js` (`:boolean`) - Defaults to `true`.
* `class` (`:string`) - Defaults to `nil`.
* `input_class` (`:string`) - Defaults to `nil`.
* `button_class` (`:string`) - Defaults to `nil`.
* `overlay_class` (`:string`) - Defaults to `nil`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
