The date and date-range editor (Spec 019): 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).
Month names, weekday initials and the locale's own first day of week come from
localize where present
(ADR-013),
falling back to English and Monday.
Summary
Functions
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.
The footer line: a draft start with no end says so in words, rather than leaving the control looking inert.
Whether date falls inside the range being built, for the hover band.
The days to render for month, as full weeks starting on first_day_of_week.
The months to render, tagged :first/:last so only the outer grids carry
their paging control.
Delegates to the facet's own casting, so the editor can never accept a token the parser would reject.
A preset commits as its token; an explicit range as a range literal; a single date as an ISO date.
Weekday initials starting on first_day_of_week.
Functions
@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
@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.
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
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]]
@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}]
@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
@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"
@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"