# `Flicker.FacetEditor.Calendar`
[🔗](https://github.com/team-alembic/flicker/blob/v0.1.0/lib/flicker/facet_editor/calendar.ex#L1)

The date and date-range editor ([Spec 019](https://github.com/team-alembic/flicker/blob/main/docs/specs/spec-019-facet-editors.md)):
a presets rail beside one or two month grids, with a footer carrying the draft
state.

Two details carry most of the value. Each preset row shows **the range it
resolves to** ("Last 30 days · Jun 29 – Jul 28"), so presets are trustworthy
rather than mysterious. And a range takes two clicks, with the footer reading
`Jun 18, 2026 → pick an end date` in between — an incomplete value that looks
incomplete, instead of an inert control.

Choosing a preset commits its **id**, not the dates it resolved to, so a saved
or shared query stays relative
([ADR-011](https://github.com/team-alembic/flicker/blob/main/docs/adrs/adr-011-facet-editors-are-modal-subcontexts.md)).

Month names, weekday initials and the locale's own first day of week come from
`localize` where present
([ADR-013](https://github.com/team-alembic/flicker/blob/main/docs/adrs/adr-013-canonical-tokens-localised-display.md)),
falling back to English and Monday.

# `complete?`

```elixir
@spec complete?(term(), Flicker.Facet.t()) :: boolean()
```

A preset is complete. An explicit range needs at least one endpoint — a lone
draft start is not committable, which is exactly what the footer's
"pick an end date" is telling the user.

## Examples

    iex> facet = Flicker.Facet.new(key: :created, type: :date_range)
    ...> Flicker.FacetEditor.Calendar.complete?(%Flicker.Facet.Range{preset: :all_time}, facet)
    true

    iex> facet = Flicker.Facet.new(key: :created, type: :date_range)
    ...> range = %Flicker.Facet.Range{from: ~D[2026-06-01], to: ~D[2026-06-30]}
    ...> Flicker.FacetEditor.Calendar.complete?(range, facet)
    true

    iex> facet = Flicker.Facet.new(key: :created, type: :date_range)
    ...> Flicker.FacetEditor.Calendar.complete?(%Flicker.Facet.Range{}, facet)
    false

# `draft_label`

```elixir
@spec draft_label(term(), Flicker.Facet.t(), keyword()) :: String.t() | nil
```

The footer line: a draft start with no end says so in words, rather than
leaving the control looking inert.

# `in_band?`

```elixir
@spec in_band?(Date.t(), Date.t() | nil, Date.t() | nil) :: boolean()
```

Whether `date` falls inside the range being built, for the hover band.

Takes the hovered day so a half-made range still previews — the interaction
that makes a two-click range feel like one gesture.

## Examples

    iex> Flicker.FacetEditor.Calendar.in_band?(~D[2026-07-05], ~D[2026-07-01], ~D[2026-07-10])
    true

    iex> Flicker.FacetEditor.Calendar.in_band?(~D[2026-07-20], ~D[2026-07-01], ~D[2026-07-10])
    false

    iex> Flicker.FacetEditor.Calendar.in_band?(~D[2026-07-05], ~D[2026-07-01], nil)
    false

# `month_grid`

```elixir
@spec month_grid(Date.t(), 1..7) :: [Date.t() | nil]
```

The days to render for `month`, as full weeks starting on `first_day_of_week`.

Leading and trailing days from adjacent months are included as `nil` so the
grid stays rectangular without rendering neighbouring dates as pickable.

## Examples

    iex> Flicker.FacetEditor.Calendar.month_grid(~D[2026-07-01], 1) |> length() |> rem(7)
    0

    iex> grid = Flicker.FacetEditor.Calendar.month_grid(~D[2026-07-01], 1)
    ...> Enum.take(grid, 3)
    [nil, nil, ~D[2026-07-01]]

# `month_grids`

```elixir
@spec month_grids(Date.t(), pos_integer()) :: [{Date.t(), :only | :first | :last}]
```

The months to render, tagged `:first`/`:last` so only the outer grids carry
their paging control.

Two for a range type, one otherwise. A single month is both first and last.

## Examples

    iex> Flicker.FacetEditor.Calendar.month_grids(~D[2026-07-01], 1)
    [{~D[2026-07-01], :only}]

    iex> Flicker.FacetEditor.Calendar.month_grids(~D[2026-07-01], 2)
    [{~D[2026-07-01], :first}, {~D[2026-08-01], :last}]

# `parse`

```elixir
@spec parse(String.t(), Flicker.Facet.t()) :: {:ok, term()} | :error
```

Delegates to the facet's own casting, so the editor can never accept a token
the parser would reject.

## Examples

    iex> facet = Flicker.Facet.new(key: :created, type: :date_range)
    ...> {:ok, range} = Flicker.FacetEditor.Calendar.parse("last-30-days", facet)
    ...> range.preset
    :last_30_days

    iex> facet = Flicker.Facet.new(key: :created, type: :date_range)
    ...> Flicker.FacetEditor.Calendar.parse("nonsense", facet)
    :error

# `serialise`

```elixir
@spec serialise(term(), Flicker.Facet.t()) :: String.t()
```

A preset commits as its token; an explicit range as a range literal; a single
date as an ISO date.

## Examples

    iex> facet = Flicker.Facet.new(key: :created, type: :date_range)
    ...>
    ...> Flicker.FacetEditor.Calendar.serialise(
    ...>   %Flicker.Facet.Range{preset: :last_30_days},
    ...>   facet
    ...> )
    "last-30-days"

    iex> facet = Flicker.Facet.new(key: :created, type: :date_range)
    ...> range = %Flicker.Facet.Range{from: ~D[2026-06-01], to: ~D[2026-06-30]}
    ...> Flicker.FacetEditor.Calendar.serialise(range, facet)
    "2026-06-01..2026-06-30"

    iex> facet = Flicker.Facet.new(key: :created, type: :date_range)
    ...> Flicker.FacetEditor.Calendar.serialise(%Flicker.Facet.Range{from: ~D[2026-06-01]}, facet)
    "2026-06-01.."

    iex> facet = Flicker.Facet.new(key: :created, type: :date)
    ...> Flicker.FacetEditor.Calendar.serialise(~D[2026-06-01], facet)
    "2026-06-01"

# `weekday_initials`

```elixir
@spec weekday_initials(1..7) :: [String.t()]
```

Weekday initials starting on `first_day_of_week`.

## Examples

    iex> Flicker.FacetEditor.Calendar.weekday_initials(1)
    ["M", "T", "W", "T", "F", "S", "S"]

    iex> Flicker.FacetEditor.Calendar.weekday_initials(7) |> hd()
    "S"

---

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