# elm-cally

A pure-Elm port of [Cally](https://github.com/WickyNilliams/cally), the
small, dependency-light calendar web components. No JavaScript, no ports,
no `Intl`, no shadow DOM -- `elm-cally` renders the same markup and the
same `part` attributes as the JS version with `Html.node`, so it can be
styled with (a light-DOM rewrite of) Cally's own CSS.

Three pickers are provided: `Cally.Date` (single date), `Cally.Range`
(date range) and `Cally.Multi` (multiple dates), each built from a shared
`Cally.Month` grid. Cally's test suite is the specification: every
upstream test is either ported (see `tests/`) or explicitly skipped with a
reason, and `upstream/ported-tests.json` / `check-coverage.mjs` enforce
that mapping in CI.

## Install

```sh
elm install alexbruf/elm-cally
```

## Usage

```elm
module Main exposing (main)

import Browser
import Cally.Date as Date
import Cally.Locale as Locale
import Cally.Month as Month
import Date
import Html exposing (Html)


type alias Model =
    { value : Date.Value
    , picker : Date.Model
    }


type Msg
    = PickerMsg Date.Msg
    | Changed Date.Value


config : Date.Config Msg
config =
    Date.defaultConfig
        { id = "my-date-picker"
        , today = today -- a `Date.Date` from `elm/time` + `justinmimbs/date`
        , locale = Locale.enGB
        , toMsg = PickerMsg
        , onChange = Changed
        }


init : Model
init =
    { value = Nothing
    , picker = Date.init config Nothing
    }


view : Model -> Html Msg
view model =
    Date.view config
        model.value
        model.picker
        [ Month.view { offset = 0 } ]


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        PickerMsg subMsg ->
            let
                ( picker, cmd ) =
                    Date.update config model.value subMsg model.picker
            in
            ( { model | picker = picker }, Cmd.map PickerMsg cmd )

        Changed value ->
            ( { model | value = value }, Cmd.none )
```

`Cally.Month.view` (and `Cally.SelectYear.view` / `Cally.SelectMonth.view`)
take a `Context msg`, which `Date.view` / `Range.view` / `Multi.view` build
and pass to each child function -- that's how Elm reproduces Cally's
light-DOM slotting: children are just functions of `Context`, so they can
be placed in any order, including inside the `heading` slot for a custom
year/month picker. See `CLAUDE.md` for the full API contract and
`example/` for range, multi, multi-month, week-number and RTL usage.

### Styling

Include `cally.css` (at the package root) to get Cally's default look. It
is a translation of Cally's own shadow-DOM stylesheet into plain CSS: no
shadow DOM means no `::part()`, so `:host` becomes the element name and
`::part(x)` becomes the attribute selector `[part~="x"]`, which matches
space-separated `part` tokens exactly the way `::part()` does. If you
already have Cally-targeting CSS, the rewrite is mechanical:

```css
/* before (targets the Cally web component) */
calendar-date::part(day selected) {
  background: hotpink;
}

/* after (targets elm-cally's light-DOM markup) */
calendar-date [part~="day"][part~="selected"] {
  background: hotpink;
}
```

See `docs/migrate-css.md` for the full rule, a `sed`/`perl` regex that
performs it, and more examples.

## Development

```sh
bun install                      # root dev tooling (elm-test, elm-review, elm-format, playwright)
cd upstream && npm ci            # pins the upstream `cally` version (Dependabot-owned)
node upstream/scripts/fetch-upstream-src.mjs   # clones cally's source at the pinned tag

bunx elm-test                    # Layer 1: elm-test (Test.Html.Query/Event)
bunx elm-review                  # jfmengels standard config
bunx elm-format --validate src tests example/src
bunx elm make --docs=docs.json

node upstream/scripts/list-upstream-tests.mjs    # print every upstream describe/it title
node upstream/scripts/generate-ported-tests.mjs  # regenerate upstream/ported-tests.json from tests/
node upstream/scripts/check-coverage.mjs         # fail if any upstream title is unmapped
node upstream/scripts/capture-parts.mjs          # regenerate upstream/parts-fixture.json via real Cally in Playwright

bunx playwright test             # Layer 2: tests-browser/, against example/
```

Use `bun`/`bunx`, not `npm`/`npx`, everywhere except inside `upstream/`,
which is a separate npm project so Dependabot can bump the pinned `cally`
version on its own.

See `CLAUDE.md` for the full working contract (upstream facts, API
contract, DOM ids, behaviour rules, test conventions) and `SPEC.md` for
the original brief.

## Attribution

`elm-cally` is a from-scratch Elm implementation, ported test-for-test and
markup-for-markup from [Cally](https://github.com/WickyNilliams/cally) by
Nick Williams and contributors, MIT licensed. No Cally source code is
vendored or redistributed; the upstream repository is cloned into
`upstream/.cache/` (gitignored) only as a reference for the port and its
tests.

`elm-cally` itself is MIT licensed -- see `LICENSE`.
