# `Emerge.UI.Event`
[🔗](https://github.com/emerge-elixir/emerge/blob/v0.2.1/lib/emerge/ui/event.ex#L1)

Event handler helpers for interactive elements.

Event helpers attach process messages to elements. When the event fires,
Emerge sends the stored message to the target process.

All helpers accept either:

- a bare `message`
- a `{pid, message}` tuple

Passing only a `message` is shorthand for `{self(), message}`. Inside a
viewport `render/0` or `render/1` callback, that means the viewport process, so the
message arrives in `handle_info/2`.

## Payload Routing

Some element events include a payload. Today the main public example is
`on_change/1` for text-input value changes.

When a payload-bearing event is delivered through a viewport, the payload is
wrapped into the stored message using the viewport's `wrap_payload/3`
callback. The default behavior is:

- non-tuple message: `{message, payload}`
- tuple message: append the payload to the tuple

For example:

This field sends the new value back to the viewport whenever the text changes.

```elixir
def render(state) do
  Input.text(
    [key(:search), Event.on_change(:search_changed)],
    state.query
  )
end

def handle_info({:search_changed, value}, state) do
  {:noreply, %{state | query: value} |> Viewport.rerender()}
end
```

If you want more structure in the delivered message, store a tuple message:

This is useful when several fields share the same `handle_info/2` path and you
want the message to identify which field changed.

```elixir
Input.text([Event.on_change({self(), {:search_changed, :field}})], state.query)

def handle_info({:search_changed, :field, value}, state) do
  {:noreply, %{state | query: value} |> Viewport.rerender()}
end
```

## Pointer Events

Prefer `on_press/1` for normal button-like activation. It is the default for
actions such as save, submit, open, and delete.

Use `on_click/1` when you specifically want pointer click behavior.

Swipe helpers emit once a pointer gesture resolves to that direction.

`on_mouse_down/1` and `on_mouse_up/1` are left-button only.

`on_mouse_enter/1`, `on_mouse_leave/1`, and `on_mouse_move/1` do not include
cursor coordinates in the delivered message.

Events do not bubble to parent elements. Attach the handler to the element
that should react.

If you only want visual hover, focus, or pressed styling, use
`Emerge.UI.Interactive` instead of event handlers.

## Input And Focus

`on_change/1` is intended for text inputs.

Text editing still works without `on_change/1`; the handler only controls
whether a change message is emitted.

`on_focus/1` and `on_blur/1` work on focusable elements, not only text inputs.

## Keyboard Events

Focused elements can listen for keyboard input with:

- `on_key_down/2`
- `on_key_up/2`
- `on_key_press/2`

Keyboard matchers can be written as either:

- a key atom such as `:enter`
- a keyword matcher such as `[key: :digit_1, mods: [:ctrl], match: :all]`

Supported modifier atoms are `:shift`, `:ctrl`, `:alt`, and `:meta`.

Match modes are:

- `:exact` - the modifier set must match exactly
- `:all` - the listed modifiers must be present, but extra modifiers are ok

`on_key_press/2` is for completed key gestures. It fires on release after a
matching press.

You can attach multiple keyboard listeners to one element by listing
`on_key_down/2`, `on_key_up/2`, or `on_key_press/2` more than once.

## Virtual Keys

`virtual_key/1` is for on-screen keyboard keys and similar soft-key controls.

A virtual key spec must include `:tap`, which can be one of:

- `{:text, binary}`
- `{:key, key, mods}`
- `{:text_and_key, text, key, mods}`

Optional hold behavior is:

- `nil` - no hold behavior
- `:repeat` - repeat the tap action while held
- `{:event, payload}` - emit a separate hold event

Defaults:

- `hold_ms: 350`
- `repeat_ms: 40`

`virtual_key/1` cannot be combined with `on_click/1` or `on_press/1` on the
same element.

## Examples

In this example, the first button sends `:save`, the second reacts to focused
`Enter`, and the third acts like a soft keyboard key.

```elixir
def render(_state) do
  column([spacing(16)], [
    Input.button(
      [
        Event.on_press(:save),
        Background.color(color(:sky, 500)),
        Border.rounded(8),
        Font.color(color(:white)),
        padding(12)
      ],
      text("Save")
    ),
    Input.button(
      [Event.on_key_down(:enter, :submit)],
      text("Submit")
    ),
    Input.button(
      [Event.virtual_key(tap: {:text_and_key, "A", :a, [:shift]})],
      text("A")
    )
  ])
end
```

# `blur_attr`

```elixir
@type blur_attr() :: {:on_blur, payload()}
```

# `change_attr`

```elixir
@type change_attr() :: {:on_change, payload()}
```

# `click_attr`

```elixir
@type click_attr() :: {:on_click, payload()}
```

# `focus_attr`

```elixir
@type focus_attr() :: {:on_focus, payload()}
```

# `key_binding`

```elixir
@type key_binding() :: %{
  key: key_name(),
  mods: [key_modifier()],
  match: key_match_mode(),
  payload: payload(),
  route: binary()
}
```

Normalized keyboard binding stored on an element.

`route` is derived automatically from the key, modifiers, and match mode.

# `key_binding_descriptor`

```elixir
@type key_binding_descriptor() :: %{
  key: key_name(),
  mods: [key_modifier()],
  match: key_match_mode(),
  route: binary()
}
```

Public descriptor form of a normalized keyboard binding.

# `key_down_attr`

```elixir
@type key_down_attr() :: {:on_key_down, key_binding()}
```

# `key_match_mode`

```elixir
@type key_match_mode() :: :exact | :all
```

How modifier matching is interpreted for keyboard listeners.

- `:exact` requires the active modifiers to match exactly.
- `:all` requires the listed modifiers to be present, but allows extras.

# `key_matcher`

```elixir
@type key_matcher() ::
  key_name()
  | [key: key_name(), mods: [key_modifier()], match: key_match_mode()]
```

Keyboard matcher accepted by `on_key_down/2`, `on_key_up/2`, and
`on_key_press/2`.

Use either a key atom like `:enter` or a keyword matcher like
`[key: :digit_1, mods: [:ctrl], match: :all]`.

# `key_modifier`

```elixir
@type key_modifier() :: :shift | :ctrl | :alt | :meta
```

Modifier keys accepted by keyboard matchers.

# `key_name`

```elixir
@type key_name() ::
  :a
  | :b
  | :c
  | :d
  | :e
  | :f
  | :g
  | :h
  | :i
  | :j
  | :k
  | :l
  | :m
  | :n
  | :o
  | :p
  | :q
  | :r
  | :s
  | :t
  | :u
  | :v
  | :w
  | :x
  | :y
  | :z
  | :digit_0
  | :digit_1
  | :digit_2
  | :digit_3
  | :digit_4
  | :digit_5
  | :digit_6
  | :digit_7
  | :digit_8
  | :digit_9
  | :minus
  | :equal
  | :plus
  | :asterisk
  | :left_bracket
  | :right_bracket
  | :backslash
  | :semicolon
  | :apostrophe
  | :grave
  | :comma
  | :period
  | :slash
  | :space
  | :enter
  | :tab
  | :escape
  | :backspace
  | :insert
  | :delete
  | :home
  | :end
  | :page_up
  | :page_down
  | :arrow_left
  | :arrow_right
  | :arrow_up
  | :arrow_down
  | :shift
  | :control
  | :alt
  | :alt_graph
  | :super
  | :caps_lock
  | :num_lock
  | :scroll_lock
  | :print_screen
  | :pause
  | :context_menu
  | :f1
  | :f2
  | :f3
  | :f4
  | :f5
  | :f6
  | :f7
  | :f8
  | :f9
  | :f10
  | :f11
  | :f12
  | :f13
  | :f14
  | :f15
  | :f16
  | :f17
  | :f18
  | :f19
  | :f20
  | :f21
  | :f22
  | :f23
  | :f24
  | :unknown
```

Canonical key atoms accepted by keyboard event helpers.

# `key_press_attr`

```elixir
@type key_press_attr() :: {:on_key_press, key_binding()}
```

# `key_up_attr`

```elixir
@type key_up_attr() :: {:on_key_up, key_binding()}
```

# `mouse_down_attr`

```elixir
@type mouse_down_attr() :: {:on_mouse_down, payload()}
```

# `mouse_enter_attr`

```elixir
@type mouse_enter_attr() :: {:on_mouse_enter, payload()}
```

# `mouse_leave_attr`

```elixir
@type mouse_leave_attr() :: {:on_mouse_leave, payload()}
```

# `mouse_move_attr`

```elixir
@type mouse_move_attr() :: {:on_mouse_move, payload()}
```

# `mouse_up_attr`

```elixir
@type mouse_up_attr() :: {:on_mouse_up, payload()}
```

# `payload`

```elixir
@type payload() :: {pid(), term()}
```

Destination process and message sent when the event fires.

Most helpers also accept a bare `message`, which is shorthand for
`{self(), message}`.

# `press_attr`

```elixir
@type press_attr() :: {:on_press, payload()}
```

# `swipe_down_attr`

```elixir
@type swipe_down_attr() :: {:on_swipe_down, payload()}
```

# `swipe_left_attr`

```elixir
@type swipe_left_attr() :: {:on_swipe_left, payload()}
```

# `swipe_right_attr`

```elixir
@type swipe_right_attr() :: {:on_swipe_right, payload()}
```

# `swipe_up_attr`

```elixir
@type swipe_up_attr() :: {:on_swipe_up, payload()}
```

# `t`

```elixir
@type t() ::
  click_attr()
  | press_attr()
  | swipe_up_attr()
  | swipe_down_attr()
  | swipe_left_attr()
  | swipe_right_attr()
  | mouse_down_attr()
  | mouse_up_attr()
  | mouse_enter_attr()
  | mouse_leave_attr()
  | mouse_move_attr()
  | change_attr()
  | focus_attr()
  | blur_attr()
  | key_down_attr()
  | key_up_attr()
  | key_press_attr()
  | virtual_key_attr()
```

# `virtual_key_attr`

```elixir
@type virtual_key_attr() :: {:virtual_key, virtual_key_spec()}
```

# `virtual_key_descriptor`

```elixir
@type virtual_key_descriptor() :: %{
  tap: virtual_key_tap(),
  hold: :none | :repeat | :event,
  hold_ms: non_neg_integer(),
  repeat_ms: pos_integer()
}
```

Normalized descriptor form of a virtual key spec.

# `virtual_key_hold`

```elixir
@type virtual_key_hold() :: nil | :repeat | {:event, payload()}
```

Optional hold behavior for `virtual_key/1`.

# `virtual_key_spec`

```elixir
@type virtual_key_spec() :: %{
  :tap =&gt; virtual_key_tap(),
  optional(:hold) =&gt; virtual_key_hold(),
  optional(:hold_ms) =&gt; non_neg_integer(),
  optional(:repeat_ms) =&gt; pos_integer()
}
```

User-facing virtual key spec.

Required key:

- `:tap`

Optional keys:

- `:hold`
- `:hold_ms`
- `:repeat_ms`

# `virtual_key_tap`

```elixir
@type virtual_key_tap() ::
  {:text, binary()}
  | {:key, key_name(), [key_modifier()]}
  | {:text_and_key, binary(), key_name(), [key_modifier()]}
```

Tap behavior for `virtual_key/1`.

# `on_blur`

```elixir
@spec on_blur(payload() | term()) :: blur_attr()
```

Register a blur payload for a focusable element.

# `on_change`

```elixir
@spec on_change(payload() | term()) :: change_attr()
```

Register a value-change payload for a text input.

Use `on_change/1` with `Emerge.UI.Input.text/2` or
`Emerge.UI.Input.multiline/2`.

Text editing still works without this handler; `on_change/1` only controls
whether a message is emitted when the value changes.

When delivered through a viewport, the changed value is wrapped into the
stored message using `wrap_payload/3`. Multiline values use the same payload
shape; newline characters remain part of the emitted binary.

## Examples

This example keeps the input value in viewport state and updates that state
whenever the field changes.

```elixir
def render(state) do
  Input.text(
    [
      key(:search),
      Event.on_change(:search_changed)
    ],
    state.query
  )
end

def handle_info({:search_changed, value}, state) do
  {:noreply, %{state | query: value} |> Viewport.rerender()}
end
```

# `on_click`

```elixir
@spec on_click(payload() | term()) :: click_attr()
```

Register a pointer click payload for this element.

Use this when you specifically want click behavior from the pointer.

For normal button-like activation, prefer `on_press/1`.

# `on_focus`

```elixir
@spec on_focus(payload() | term()) :: focus_attr()
```

Register a focus payload for a focusable element.

# `on_key_down`

```elixir
@spec on_key_down(key_matcher(), payload() | term()) :: key_down_attr()
```

Register a focused key-down payload for this element.

`matcher` can be a key atom like `:enter` or a keyword matcher such as
`[key: :digit_1, mods: [:ctrl], match: :all]`.

`:match` defaults to `:exact`.

## Example

This lets one focused button respond both to `Ctrl+1` and to plain `Enter`.

```elixir
Input.button(
  [
    Event.on_key_down([key: :digit_1, mods: [:ctrl], match: :all], :select_tab_1),
    Event.on_key_down(:enter, :submit)
  ],
  text("Save")
)
```

On focused `Emerge.UI.Input.text/2` and `Emerge.UI.Input.multiline/2`
elements, a matching `on_key_down/2` suppresses the input's default keydown
behavior for that keydown. This lets apps override built-in editing such as
character insertion or multiline `Enter` handling.

For example, `on_key_down(:enter, :submit)` on a focused
`Emerge.UI.Input.multiline/2` intercepts `Enter` before the default newline is
inserted.

# `on_key_press`

```elixir
@spec on_key_press(key_matcher(), payload() | term()) :: key_press_attr()
```

Register a focused completed key-press payload for this element.

`on_key_press/2` is for completed key gestures and fires on release after a
matching press.

# `on_key_up`

```elixir
@spec on_key_up(key_matcher(), payload() | term()) :: key_up_attr()
```

Register a focused key-up payload for this element.

Key-up handlers use the same matcher forms as `on_key_down/2`.

# `on_mouse_down`

```elixir
@spec on_mouse_down(payload() | term()) :: mouse_down_attr()
```

Register a mouse-down payload for this element. Left mouse button only.

# `on_mouse_enter`

```elixir
@spec on_mouse_enter(payload() | term()) :: mouse_enter_attr()
```

Register a mouse-enter payload for this element. Delivered without cursor coordinates.

# `on_mouse_leave`

```elixir
@spec on_mouse_leave(payload() | term()) :: mouse_leave_attr()
```

Register a mouse-leave payload for this element. Delivered without cursor coordinates.

# `on_mouse_move`

```elixir
@spec on_mouse_move(payload() | term()) :: mouse_move_attr()
```

Register a mouse-move payload for this element. Delivered without cursor coordinates.

# `on_mouse_up`

```elixir
@spec on_mouse_up(payload() | term()) :: mouse_up_attr()
```

Register a mouse-up payload for this element. Left mouse button only.

# `on_press`

```elixir
@spec on_press(payload() | term()) :: press_attr()
```

Register a press payload for this element.

This is the recommended default for buttons and other action controls.
For standard activation, prefer `on_press/1` over `on_click/1` because it
also works with focused `Enter`.

## Examples

This is a conventional action button: pressing it sends `:save` to the target
process while the surrounding attrs provide the visual styling.

```elixir
Input.button(
  [
    Event.on_press(:save),
    Background.color(color(:sky, 500)),
    Border.rounded(8),
    Font.color(color(:white)),
    padding(12)
  ],
  text("Save")
)
```

# `on_swipe_down`

```elixir
@spec on_swipe_down(payload() | term()) :: swipe_down_attr()
```

Register a swipe-down payload for this element. Fires when the gesture resolves downward.

# `on_swipe_left`

```elixir
@spec on_swipe_left(payload() | term()) :: swipe_left_attr()
```

Register a swipe-left payload for this element. Fires when the gesture resolves leftward.

# `on_swipe_right`

```elixir
@spec on_swipe_right(payload() | term()) :: swipe_right_attr()
```

Register a swipe-right payload for this element. Fires when the gesture resolves rightward.

# `on_swipe_up`

```elixir
@spec on_swipe_up(payload() | term()) :: swipe_up_attr()
```

Register a swipe-up payload for this element. Fires when the gesture resolves upward.

# `virtual_key`

```elixir
@spec virtual_key(virtual_key_spec() | keyword()) :: virtual_key_attr()
```

Register virtual-key behavior for an element.

This is useful for on-screen keyboards and similar soft-key controls.

The spec must include `:tap`. Optional keys are `:hold`, `:hold_ms`, and
`:repeat_ms`.

`virtual_key/1` cannot be combined with `on_click/1` or `on_press/1` on the
same element.

## Example

This virtual key inserts an uppercase `A` on tap and sends a separate
`:show_alternates` event when the key is held.

```elixir
Input.button(
  [
    width(px(56)),
    height(px(56)),
    Event.virtual_key(
      tap: {:text_and_key, "A", :a, [:shift]},
      hold: {:event, {self(), :show_alternates}}
    )
  ],
  text("A")
)
```

`{:text_and_key, text, key, mods}` participates in the same text-input
keydown suppression rules as a physical key press. `{:text, text}` does not,
because it inserts text without a preceding keydown. See `on_key_down/2` for
the suppression behavior on focused `Input.text/2` and `Input.multiline/2`.

---

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