# `MishkaGervaz.Form.Types.Field.KeyMap`
[🔗](https://github.com/mishka-group/mishka_gervaz/blob/v0.0.6/lib/mishka_gervaz/form/types/field/key_map.ex#L1)

Map field type whose keys are declared, drawn as one control per key.

Use it for a constrained-map column whose shape is fixed and small — a Phoenix attribute's
`opts`, a set of feature switches, a pair of coordinates. Use `:json` instead when the keys are
not known ahead of time.

Each declared key is rendered through the form's UI adapter in the control its `:type` asks for,
and the field hands back a plain map with string keys.

## Example

As a field of its own, over a `:map` column:

    field :settings, :key_map do
      options [
        [name: :theme, type: :select, options: ~w(light dark), label: "Theme"],
        [name: :compact, type: :toggle, label: "Compact"]
      ]
    end

Or as a sub-field of a nested row, which is where a Phoenix attribute's `opts` lives:

    nested_field :opts, :key_map do
      options [
        [name: :required, type: :toggle, label: "Required"],
        [name: :default, type: :text, label: "Default", placeholder: "e.g. medium"],
        [name: :doc, type: :text, label: "What it is for"]
      ]
    end

## Declaring keys

Keys are read from `options`, the same place a `:select` keeps the list it offers. Each key is a
keyword list or a map:

| Key            | Required      | Meaning                                                     |
|----------------|---------------|-------------------------------------------------------------|
| `:name`        | yes           | the key written into the map                                 |
| `:type`        | no (`:text`)  | `:text`, `:textarea`, `:toggle`, `:checkbox`, `:number`, `:select`, `:json`, `:password` |
| `:label`       | no            | shown above the control; humanized from `:name` otherwise    |
| `:placeholder` | no            | placeholder text, or the empty option's label on a `:select` |
| `:options`     | for `:select` | the choices, as `{label, value}` pairs or bare values        |
| `:required`    | no (`false`)  | marks the key's label as a required field's label is marked  |

## Keys from the form state

A field of its own may take `options fn state -> keys end` in place of a list. The form reads it
with its state each time the field is drawn and each time its params are read, so one form can
draw different keys for different openings. A page that opens one create form per source passes
the source in `defaults`, and each source supplies its keys as data:

    field :settings, :key_map do
      options fn state -> MyApp.Sources.keys(state.defaults[:source]) end
    end

    send_update(MishkaGervaz.Form.Web.Live,
      id: "connection-form",
      defaults: %{source: "wordpress"},
      reset: true
    )

A key the function no longer names is dropped from what the form saves. A sub-field of a nested
row takes a list only.

## A password key

`:password` draws the key through the UI adapter's `password_input`, with `autocomplete="off"`.
What the key holds is never written back into the page: the control starts empty each time the
form opens, the browser keeps what is typed into it while the form is open, and a key left empty
is dropped like any blank key, so a form that edits a record asks for it again.

## A key that may hold a map or a list

`:json` is a text control for a key whose value is usually text but may be a map, a list, a number
or a boolean — a Phoenix attribute's `default`, which is `"medium"` on one attribute and `%{}` on
another. A text control cannot draw a map at all, and one drawn as text would be saved back as
text, so the attribute that wanted `%{}` would get the string `"%{}"`. A `:json` key is shown and
read as the `:json` field type shows and reads its value (`MishkaGervaz.Form.Types.Field.Json`): a
map or a list is shown as JSON, and what is typed is read as JSON when it is JSON (`{}`, `[1]`,
`5`, `true`) and kept as text when it is not (`medium`).

## Blank keys are dropped

A key left blank is omitted from the stored map rather than written as `""`, and a toggle left
off is omitted rather than written as `false`, so "not set" stays distinct from "set to nothing".

Values are cast to their declared type before they reach the changeset; see
`MishkaGervaz.Form.Types.Field.Nested`.

See `MishkaGervaz.Form.Behaviours.FieldType`, `MishkaGervaz.Form.Types.Field`, and
`MishkaGervaz.Form.Types.Field.KeyList` for the repeating version.

# `key`

```elixir
@type key() :: keyword() | map()
```

One declared key, as written in `options`.

# `keys`

```elixir
@spec keys(map() | keyword() | nil) :: [map()]
```

The declared keys, normalised to maps with `:name`, `:type`, `:label`, `:placeholder`,
`:options` and `:required`.

Reads `:keys` if the config carries one, `:options` otherwise. Keys with no `:name` are dropped,
and so is `fn state -> keys end`, which only `keys/2` can read.

    iex> MishkaGervaz.Form.Types.Field.KeyMap.keys(%{options: [[name: :doc]]})
    [%{name: :doc, type: :text, label: nil, placeholder: nil, options: [], required: false}]

# `keys`

```elixir
@spec keys(map() | keyword() | nil, map()) :: [map()]
```

`keys/1`, with `options fn state -> keys end` read with the form `state`.

---

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