# `GanttEx.Live.Interaction`
[🔗](https://github.com/rocket4ce/gantt_ex/blob/v0.1.0/lib/gantt_ex/live/interaction.ex#L1)

Translates user gestures into task and link attributes.

`GanttEx.Live.Gantt` receives raw client values (pixels, strings) from the
hook and from inline editors. This module turns them into validated
attributes for `GanttEx.Store`, so the conversion rules can be tested on
their own. Every function returns `nil` (or `:error`) for values that do
not make sense, which the component simply ignores.

# `context`

```elixir
@type context() :: %{
  store: GanttEx.Store.t(),
  timeline: GanttEx.Timeline.t(),
  unit: GanttEx.Dates.unit(),
  week_start: 1..7
}
```

What the conversions need to know about the chart.

# `cell_attrs`

```elixir
@spec cell_attrs(map(), term(), GanttEx.Task.t(), GanttEx.Dates.unit()) :: map() | nil
```

Returns the attributes for an inline edit of `column`, or `nil` when the
value is invalid. Fields that are not task fields are stored in `:meta`,
reusing the existing key type (atom or string).

    iex> task = GanttEx.Task.new!(id: 1, start_date: ~D[2025-01-06], owner: "Ana")
    iex> GanttEx.Live.Interaction.cell_attrs(%{field: :end_date}, "2025-01-08", task, :day)
    %{end_date: ~N[2025-01-09 00:00:00]}
    iex> GanttEx.Live.Interaction.cell_attrs(%{field: :progress}, "120", task, :day)
    nil
    iex> GanttEx.Live.Interaction.cell_attrs(%{field: :owner}, "Luis", task, :day)
    %{meta: %{owner: "Luis"}}

# `date_at`

```elixir
@spec date_at(context(), number()) :: NaiveDateTime.t()
```

Returns the snapped datetime at a horizontal position of the timeline.

# `drag_attrs`

```elixir
@spec drag_attrs(GanttEx.Task.t(), String.t(), map(), context()) :: map() | nil
```

Returns the attributes that result from dragging a bar.

`mode` is `"move"`, `"start"`, `"end"` (resizing) or `"progress"`.
`params` holds the final `"x"` and `"width"` of the bar in pixels, or the
`"progress"` between 0 and 1. Dates snap to the duration unit and a task
keeps at least one unit. Project bars with children cannot be moved or
resized: their dates come from their children.

    iex> store = GanttEx.Store.new!([%{id: 1, start_date: ~D[2025-01-06], duration: 3}])
    iex> timeline = GanttEx.Timeline.new(scales: [[unit: :day]], start_date: ~N[2025-01-01 00:00:00], end_date: ~N[2025-02-01 00:00:00], column_width: 40)
    iex> ctx = %{store: store, timeline: timeline, unit: :day, week_start: 1}
    iex> task = GanttEx.Store.get_task(store, 1)
    iex> GanttEx.Live.Interaction.drag_attrs(task, "move", %{"x" => 318, "width" => 120}, ctx)
    %{start_date: ~N[2025-01-09 00:00:00]}
    iex> GanttEx.Live.Interaction.drag_attrs(task, "end", %{"x" => 200, "width" => 2}, ctx)
    %{end_date: ~N[2025-01-07 00:00:00]}
    iex> GanttEx.Live.Interaction.drag_attrs(task, "progress", %{"progress" => "0.456"}, ctx)
    %{progress: 0.46}
    iex> GanttEx.Live.Interaction.drag_attrs(task, "fly", %{}, ctx)
    nil

# `drop_position`

```elixir
@spec drop_position(GanttEx.Store.t(), GanttEx.Task.t(), GanttEx.Task.t(), String.t()) ::
  {GanttEx.Task.id() | nil, non_neg_integer() | nil} | nil
```

Returns `{parent, index}` for dropping `task` `"before"`, `"after"` or
`"inside"` the `target` row, or `nil` when the drop would put the task
inside its own subtree.

    iex> store = GanttEx.Store.new!([%{id: 1, start_date: ~D[2025-01-01]}, %{id: 2, parent: 1, start_date: ~D[2025-01-01]}, %{id: 3, start_date: ~D[2025-01-01]}])
    iex> [one, two, three] = GanttEx.Store.tasks(store)
    iex> GanttEx.Live.Interaction.drop_position(store, three, two, "after")
    {1, 1}
    iex> GanttEx.Live.Interaction.drop_position(store, three, one, "before")
    {nil, 0}
    iex> GanttEx.Live.Interaction.drop_position(store, one, two, "inside")
    nil

# `extra_params`

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

Converts the params of custom lightbox fields into meta attributes, reusing
the key type already present in `meta`.

    iex> GanttEx.Live.Interaction.extra_params(%{"text" => "A", "owner" => "Ana", "_target" => "x"}, %{owner: nil})
    %{owner: "Ana"}

# `number`

```elixir
@spec number(term()) :: number() | nil
```

Parses a number sent by the client.

    iex> GanttEx.Live.Interaction.number("12.5")
    12.5

    iex> GanttEx.Live.Interaction.number("12px")
    nil

# `resource_attrs`

```elixir
@spec resource_attrs(
  map(),
  %{required(String.t()) =&gt; GanttEx.Resource.t()},
  map() | nil
) :: map()
```

Returns the resource assignments chosen in the lightbox (`"resources"`, a
list of resource keys), keeping the values of existing assignments.
Returns `%{}` when the form has no resources field.

    iex> resources = %{"ana" => GanttEx.Resource.new!(id: "ana"), "7" => GanttEx.Resource.new!(id: 7)}
    iex> task = GanttEx.Task.new!(id: 1, resources: [%{id: "ana", value: 4}])
    iex> GanttEx.Live.Interaction.resource_attrs(%{"resources" => ["", "ana", "7", "ghost"]}, resources, task)
    %{resources: [%{id: "ana", value: 4}, %{id: 7, value: nil}]}
    iex> GanttEx.Live.Interaction.resource_attrs(%{"text" => "A"}, resources, task)
    %{}

# `side`

```elixir
@spec side(term()) :: {:ok, :start | :end} | :error
```

Casts a link edge sent by the client.

    iex> GanttEx.Live.Interaction.side("end")
    {:ok, :end}

    iex> GanttEx.Live.Interaction.side("middle")
    :error

# `span_at`

```elixir
@spec span_at(context(), number(), number()) :: {NaiveDateTime.t(), NaiveDateTime.t()}
```

Returns `{start, end}` for a task drawn on the timeline from `x` to
`x + width`, lasting at least one unit.

---

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