Flicker.FacetEditor.Calendar (Flicker v0.1.0)

Copy Markdown View Source

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

complete?(arg1, facet)

@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(arg1, facet, opts)

@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?(date, start, other)

@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(month, first_day_of_week)

@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(month, arg2)

@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(text, facet)

@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(date, facet)

@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(first_day_of_week)

@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"