# `MishkaGervaz.Table.Web.UrlSync`
[🔗](https://github.com/mishka-group/mishka_gervaz/blob/v0.0.1-alpha.3/lib/mishka_gervaz/table/web/url_sync.ex#L1)

URL state synchronization for bookmarkable table views.

Syncs table state (filters, sort, page, search) to URL query params,
allowing users to bookmark or share specific table views.

## Configuration

Enable URL sync in domain table config:

    mishka_gervaz do
      table do
        url_sync do
          enabled true
          params [:filters, :sort, :page]
          prefix "table"
        end
      end
    end

## Usage in LiveView

In your parent LiveView, handle URL params:

    def handle_params(params, _uri, socket) do
      url_state = MishkaGervaz.Table.Web.UrlSync.decode(params, "table")

      {:noreply,
        socket
        |> assign(:url_state, url_state)}
    end

Pass to the component:

    <.live_component
      module={MishkaGervaz.Table.Web.Live}
      id="posts-table"
      resource={MyApp.Post}
      url_state={@url_state}
    />

## Param Format

- Filters: `?table_filter_status=active&table_filter_category=1`
- Sort: `?table_sort=name:asc` or `?table_sort=inserted_at:desc`
- Page: `?table_page=2`
- Search: `?table_search=hello`
- Template: `?table_template=grid`

See `MishkaGervaz.Table.Web.State`,
`MishkaGervaz.Table.Web.State.UrlSync` (the in-state sub-builder),
`MishkaGervaz.Table.Dsl.UrlSync` (resource-level DSL section),
`MishkaGervaz.Table.Dsl.Defaults` (domain-level defaults).

# `decode_opts`

```elixir
@type decode_opts() :: [
  allowed_params: [atom()],
  allowed_filters: [atom()],
  max_filter_length: non_neg_integer()
]
```

# `url_state`

```elixir
@type url_state() :: %{
  filters: map(),
  sort: [{atom(), :asc | :desc}],
  page: integer(),
  page_size: pos_integer() | nil,
  search: String.t() | nil,
  template: atom() | nil,
  path: String.t() | nil,
  path_params: map(),
  preserved_params: map()
}
```

# `apply_url_state`

```elixir
@spec apply_url_state(map(), url_state()) :: map()
```

Apply URL state to existing table state.

Merges decoded URL state with default table state.

# `build_path`

```elixir
@spec build_path(String.t(), map(), map()) :: String.t()
```

Build URL path with encoded state params.

Takes current path and state, returns path with query string.

# `decode`

```elixir
@spec decode(map(), String.t(), module(), decode_opts()) :: url_state() | nil
```

# `enabled?`

```elixir
@spec enabled?(map()) :: boolean()
```

Check if URL sync is enabled in config.

# `encode`

```elixir
@spec encode(map(), map()) :: map()
```

Encode table state to URL query params.

## Examples

    iex> state = %{filters: %{status: "active"}, sort: [{:name, :asc}], page: 2}
    iex> UrlSync.encode(state, %{params: [:filters, :sort, :page], prefix: "t"})
    %{"t_filter_status" => "active", "t_sort" => "name:asc", "t_page" => "2"}

# `matches_state?`

```elixir
@spec matches_state?(url_state() | nil, map()) :: boolean()
```

Check if url_state matches current table state.

Used to detect if incoming url_state is from our own push_patch
(to avoid duplicate data reload).

---

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