Packages

Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.

Current section

Files

Jump to
raxol lib raxol core events subscription.ex
Raw

lib/raxol/core/events/subscription.ex

defmodule Raxol.Core.Events.Subscription do
@moduledoc """
Provides helpers for managing event subscriptions.
This module makes it easy to:
* Subscribe to specific event types
* Filter events based on criteria
* Handle event cleanup
* Manage multiple subscriptions
"""
alias Raxol.Core.Events.EventManager, as: Manager
@type subscription_ref :: reference()
@type subscription_opts :: keyword()
@doc """
Subscribes to keyboard events with optional key filters.
## Options
* `:keys` - List of specific keys to match
* `:exclude_keys` - List of keys to ignore
## Example
subscribe_keyboard(keys: [:enter, :esc])
subscribe_keyboard(exclude_keys: [:tab])
"""
def subscribe_keyboard(opts \\ []) do
keys = Keyword.get(opts, :keys)
_exclude_keys = Keyword.get(opts, :exclude_keys, [])
filter_opts = build_key_filter(keys)
Manager.subscribe([:key], filter_opts)
end
@spec build_key_filter(any()) :: any()
defp build_key_filter(nil), do: []
@spec build_key_filter(any()) :: any()
defp build_key_filter(keys), do: [key: keys]
@doc """
Subscribes to mouse events with optional button and position filters.
## Options
* `:buttons` - List of mouse buttons to match
* `:drag_only` - Only match drag events
* `:click_only` - Only match click events
* `:area` - Tuple of {x, y, width, height} to match position
## Example
subscribe_mouse(buttons: [:left, :right])
subscribe_mouse(area: {0, 0, 10, 10})
"""
def subscribe_mouse(opts \\ []) do
buttons = Keyword.get(opts, :buttons)
drag_only = Keyword.get(opts, :drag_only, false)
click_only = Keyword.get(opts, :click_only, false)
area = Keyword.get(opts, :area)
filter_opts =
[]
|> add_filter(:mouse_button, buttons)
|> add_filter(:drag, if(drag_only, do: true))
|> add_filter(:click, if(click_only, do: true))
|> add_filter(:area, area)
Manager.subscribe([:mouse], filter_opts)
end
@doc """
Subscribes to window events with optional action filters.
## Options
* `:actions` - List of window actions to match (:resize, :focus, :blur)
## Example
subscribe_window(actions: [:resize])
"""
def subscribe_window(opts \\ []) do
actions = Keyword.get(opts, :actions)
filter_opts = build_window_filter(actions)
Manager.subscribe([:window], filter_opts)
end
@spec build_window_filter(any()) :: any()
defp build_window_filter(nil), do: []
@spec build_window_filter(any()) :: any()
defp build_window_filter(actions), do: [window_action: actions]
@doc """
Subscribes to timer events with optional data matching.
## Options
* `:match` - Pattern to match against timer data
## Example
subscribe_timer(match: :tick)
"""
def subscribe_timer(opts \\ []) do
match = Keyword.get(opts, :match)
filter_opts = build_match_filter(match)
Manager.subscribe([:timer], filter_opts)
end
@spec build_match_filter(any()) :: any()
defp build_match_filter(nil), do: []
@spec build_match_filter(any()) :: any()
defp build_match_filter(match), do: [data: match]
@doc """
Subscribes to custom events with data matching.
## Options
* `:match` - Pattern to match against custom event data
## Example
subscribe_custom(match: {:user_action, _})
"""
def subscribe_custom(opts \\ []) do
match = Keyword.get(opts, :match)
filter_opts = build_match_filter(match)
Manager.subscribe([:custom], filter_opts)
end
@doc """
Unsubscribes from events using the subscription reference.
"""
def unsubscribe(ref) when is_reference(ref) do
Manager.unsubscribe(ref)
end
@doc """
Unsubscribes from multiple subscriptions.
"""
def unsubscribe_all(refs) when is_list(refs) do
Enum.each(refs, &unsubscribe/1)
end
@doc """
Subscribes to a list of event types.
## Parameters
- `event_types` - A list of event type atoms (e.g., `[:key, :mouse, :window]`).
Can also include tuples like `{:key, opts}` or `{:mouse, opts}`
to pass specific options to the individual subscription functions.
## Returns
- `{:ok, list(subscription_ref())}` on success, containing a list of references for each subscription.
- `{:error, reason}` if any subscription fails. Successfully created subscriptions before the failure will be automatically unsubscribed.
"""
def events(event_types) when is_list(event_types) do
do_subscribe_events(event_types, [])
end
# Helper function to recursively subscribe and handle cleanup on error
@spec do_subscribe_events(any(), reference()) :: any()
defp do_subscribe_events([], successful_refs) do
{:ok, Enum.reverse(successful_refs)}
end
@spec do_subscribe_events(any(), reference()) :: any()
defp do_subscribe_events([event_spec | rest], successful_refs) do
case subscribe_single_event(event_spec) do
{:ok, ref} ->
do_subscribe_events(rest, [ref | successful_refs])
{:error, _reason} = error ->
# Unsubscribe already successful subscriptions
unsubscribe_all(successful_refs)
error
end
end
# Helper to call the correct specific subscribe function
@spec subscribe_single_event(any()) :: any()
defp subscribe_single_event(:key), do: subscribe_keyboard()
@spec subscribe_single_event(any()) :: any()
defp subscribe_single_event({:key, opts}), do: subscribe_keyboard(opts)
@spec subscribe_single_event(any()) :: any()
defp subscribe_single_event(:mouse), do: subscribe_mouse()
@spec subscribe_single_event(any()) :: any()
defp subscribe_single_event({:mouse, opts}), do: subscribe_mouse(opts)
@spec subscribe_single_event(any()) :: any()
defp subscribe_single_event(:window), do: subscribe_window()
@spec subscribe_single_event(any()) :: any()
defp subscribe_single_event({:window, opts}), do: subscribe_window(opts)
@spec subscribe_single_event(any()) :: any()
defp subscribe_single_event(:timer), do: subscribe_timer()
@spec subscribe_single_event(any()) :: any()
defp subscribe_single_event({:timer, opts}), do: subscribe_timer(opts)
@spec subscribe_single_event(any()) :: any()
defp subscribe_single_event(:custom), do: subscribe_custom()
@spec subscribe_single_event(any()) :: any()
defp subscribe_single_event({:custom, opts}), do: subscribe_custom(opts)
@spec subscribe_single_event(any()) :: any()
defp subscribe_single_event(other), do: {:error, {:invalid_event_type, other}}
# Private Helpers
@spec add_filter(keyword(), any(), any()) :: any()
defp add_filter(opts, _key, nil), do: opts
@spec add_filter(keyword(), any(), any()) :: any()
defp add_filter(opts, key, value) do
Keyword.put(opts, key, value)
end
end