# `MishkaGervaz.Form.Web.State.Helpers`
[🔗](https://github.com/mishka-group/mishka_gervaz/blob/v0.0.2/lib/mishka_gervaz/form/web/state.ex#L255)

Shared helpers for `MishkaGervaz.Form.Web.State`.

A module that overrides `init/3` (via `use MishkaGervaz.Form.Web.State`)
can call any helper here without redefining it; the macro itself
imports them as `StateHelpers`.

Two functional groups:

- **Config getters** (`get_layout_mode/1`, `get_uploads/1`,
  `get_submit/1`, …) — pull a typed value out of the runtime
  `Info.config(resource)` map with sensible defaults.
- **Step helpers** (`groups_for_step/3`) and **access**
  (`mode_allowed?/3`, `resolve_access/1`) — the bits of logic the
  macro and external callers (e.g. `Form.Web.Live`) both need.

## `mode_allowed?/3` — `:restricted` semantics

The `:restricted` field on a `source` map (or a per-mode entry in
`:access_rules`) accepts two shapes, and they behave differently:

- `restricted: true` — applies the master gate. The mode is allowed
  only when `state.master_user?`.
- `restricted: fn state -> boolean end` — the function is the final
  word; the master gate is not layered on top. Returning `true`
  means the user is restricted and the mode is denied, returning
  `false` allows the mode unconditionally.

Use the boolean form unless the decision needs the whole state.

See `MishkaGervaz.Form.Web.State`,
`MishkaGervaz.Form.Web.State.Access.Default`, and
`MishkaGervaz.Form.Web.Live`.

# `field_readonly?`

```elixir
@spec field_readonly?(map(), map()) :: boolean()
```

Whether `field` is drawn read-only and left out of what the form saves.

True when the field declares `readonly true` or `readonly fn state -> … end` says so, or when
`taken_by_action?/2` is false.

    if field_readonly?(field, state), do: Map.delete(params, to_string(field.name)), else: params

# `generate_stream_name`

```elixir
@spec generate_stream_name(module()) :: atom()
```

The stream name derived from a resource when `identity do stream_name … end` is omitted.

    iex> generate_stream_name(MyApp.Blog.Post)
    :post_form_stream

# `get_footer`

```elixir
@spec get_footer(map()) :: map() | nil
```

The form's `layout do footer … end` map, or `nil` when none is declared.

# `get_header`

```elixir
@spec get_header(map()) :: map() | nil
```

The form's `layout do header … end` map, or `nil` when none is declared.

# `get_hooks`

```elixir
@spec get_hooks(map()) :: map()
```

The form's lifecycle hooks as a map, or `%{}` when the resource declares none.

# `get_layout_columns`

```elixir
@spec get_layout_columns(map()) :: 1 | 2 | 3 | 4
```

The grid width of the form, defaulting to a single column.

A group may override it for itself with `ui do columns … end`.

# `get_layout_mode`

```elixir
@spec get_layout_mode(map()) :: :standard | :wizard | :tabs
```

The form's layout mode, defaulting to `:standard` when the resource declares none.

# `get_layout_navigation`

```elixir
@spec get_layout_navigation(map()) :: :sequential | :free
```

How a wizard or tabs form moves between steps — `:sequential` (the default) or `:free`.

# `get_notices`

```elixir
@spec get_notices(map()) :: [map()]
```

Every `layout do notice … end` declared on the resource, in declaration order.

# `get_submit`

```elixir
@spec get_submit(map()) :: map()
```

The resolved submit configuration — the three buttons, their position and shared `ui`.

Falls back to a full default block rather than `nil`, so a template can read
`submit[:create][:label]` without guarding first.

# `get_uploads`

```elixir
@spec get_uploads(map()) :: [map()]
```

The `uploads do upload … end` entries declared on the resource, or `[]`.

# `groups_for_step`

```elixir
@spec groups_for_step([map()], [map()], atom()) :: [map()]
```

The groups belonging to one wizard or tabs step, in the order they were declared.

Returns every group when the step is unknown, which is what makes a `:standard` form — and a
step name that no longer exists — render rather than come back empty.

    groups_for_step(state.static.groups, state.static.steps, state.current_step)

# `mode_allowed?`

```elixir
@spec mode_allowed?(map() | nil, atom(), map()) :: boolean()
```

Whether the current reader may open the form in `mode` (`:create` or `:update`).

Checks, in order: a per-mode `access` rule, a global `access fn mode, state -> … end` gate,
then `source do restricted true end`. A form with no `source` at all is allowed. A per-mode
rule written `access :update, false` denies that mode to everyone.

    if mode_allowed?(state.static.source, :create, state), do: DataLoader.new_record(socket, state)

# `resolve_access`

```elixir
@spec resolve_access(module()) :: module()
```

The access module used to gate form modes.

# `taken_by_action?`

```elixir
@spec taken_by_action?(map(), map()) :: boolean()
```

Whether the action `state.form` saves with accepts `field` or takes it as an argument.

A `virtual` field is always taken. When `state.form` is not an `AshPhoenix.Form`, or its action
is not on its resource, the answer is `true` and the field is left as declared.

---

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