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
Current section
Files
lib/raxol/core/accessibility/theme_integration.ex
defmodule Raxol.Core.Accessibility.ThemeIntegration do
@moduledoc """
Manages the integration between accessibility settings and the active theme.
Listens for accessibility changes (e.g., high contrast toggle) and
updates the active theme accordingly.
"""
# require Raxol.Core.Runtime.Log # Commented out due to missing module
alias Raxol.Core.Events.EventManager, as: EventManager
alias Raxol.Core.UserPreferences
alias Raxol.UI.Theming.Theme
@doc """
Initialize the theme integration.
Registers event handlers for accessibility setting changes.
## Examples
iex> ThemeIntegration.init()
:ok
"""
def init do
EventManager.register_handler(
:accessibility_high_contrast,
__MODULE__,
:handle_high_contrast
)
EventManager.register_handler(
:accessibility_reduced_motion,
__MODULE__,
:handle_reduced_motion
)
EventManager.register_handler(
:accessibility_large_text,
__MODULE__,
:handle_large_text
)
EventManager.register_handler(
:theme_changed,
Raxol.Core.Accessibility,
:handle_theme_changed_event
)
:ok
end
@doc """
Clean up the theme integration.
Unregisters event handlers.
## Examples
iex> ThemeIntegration.cleanup()
:ok
"""
def cleanup do
handle_test_cleanup(test_env?())
# Wrap handler cleanup in try-catch to handle cases where
# EventManager may have been stopped
try do
EventManager.unregister_handler(
:accessibility_high_contrast,
__MODULE__,
:handle_high_contrast
)
EventManager.unregister_handler(
:accessibility_reduced_motion,
__MODULE__,
:handle_reduced_motion
)
EventManager.unregister_handler(
:accessibility_large_text,
__MODULE__,
:handle_large_text
)
EventManager.unregister_handler(
:theme_changed,
Raxol.Core.Accessibility,
:handle_theme_changed_event
)
catch
:exit, _ -> :ok
end
:ok
end
@doc """
Apply the current accessibility settings to components.
This function is typically called during initialization to ensure components
reflect the persisted preferences.
Accepts a keyword list of options (e.g., `[high_contrast: true, ...]`).
"""
def apply_settings(options) when is_list(options) do
high_contrast = Keyword.get(options, :high_contrast, false)
reduced_motion = Keyword.get(options, :reduced_motion, false)
large_text = Keyword.get(options, :large_text, false)
handle_high_contrast({:accessibility_high_contrast, high_contrast})
handle_reduced_motion({:accessibility_reduced_motion, reduced_motion})
handle_large_text({:accessibility_large_text, large_text})
:ok
end
@doc """
Handle high contrast mode changes.
Updates the theme based on high contrast setting.
"""
def handle_high_contrast({:accessibility_high_contrast, enabled}) do
# require Raxol.Core.Runtime.Log # Commented out due to missing module
# Only try to set preference if the process exists
case Process.whereis(UserPreferences) do
nil -> :ok
_pid -> UserPreferences.set(pref_key(:high_contrast), enabled)
end
# Raxol.Core.Runtime.Log.debug(
# "ThemeIntegration handling high contrast event: #{enabled}"
# )
EventManager.dispatch({:ui_refresh_required, %{reason: :theme_change}})
EventManager.dispatch({:theme_changed, %{high_contrast: enabled}})
:ok
end
@doc """
Returns the current accessibility mode based on settings.
Defaults to `:normal` if high contrast is off.
"""
@spec get_accessibility_mode() :: :high_contrast | :standard
def get_accessibility_mode do
# Only try to get preference if the process exists
high_contrast =
case Process.whereis(UserPreferences) do
nil -> false
_pid -> UserPreferences.get(pref_key(:high_contrast)) || false
end
determine_accessibility_mode(high_contrast)
end
@doc """
Handle reduced motion setting changes.
## Examples
iex> ThemeIntegration.handle_reduced_motion({:accessibility_reduced_motion, true})
:ok
"""
def handle_reduced_motion({:accessibility_reduced_motion, enabled}) do
# require Raxol.Core.Runtime.Log # Commented out due to missing module
# Only try to set preference if the process exists
case Process.whereis(UserPreferences) do
nil -> :ok
_pid -> UserPreferences.set(pref_key(:reduced_motion), enabled)
end
# Raxol.Core.Runtime.Log.debug("Restoring FocusRing config for normal motion")
EventManager.dispatch({:theme_changed, %{reduced_motion: enabled}})
:ok
end
@doc """
Handle large text setting changes.
## Examples
iex> ThemeIntegration.handle_large_text({:accessibility_large_text, true})
:ok
"""
def handle_large_text({:accessibility_large_text, enabled}) do
# Only try to set preference if the process exists
case Process.whereis(UserPreferences) do
nil -> :ok
_pid -> UserPreferences.set(pref_key(:large_text), enabled)
end
EventManager.dispatch({:theme_changed, %{large_text: enabled}})
:ok
end
defp pref_key(key), do: "accessibility.#{key}"
@doc """
Get the current theme based on accessibility settings.
## Examples
iex> ThemeIntegration.get_theme()
%Theme{} # Returns the current theme with accessibility adjustments
"""
def get_theme do
theme = Theme.current()
mode = get_accessibility_mode()
apply_theme_adjustments(mode == :high_contrast, theme, mode)
end
@doc """
Returns the current active theme variant for accessibility-aware theming.
Used by the renderer and theming system to select the correct theme variant.
## Examples
iex> ThemeIntegration.get_active_variant()
:standard | :high_contrast | :reduced_motion
"""
@spec get_active_variant() :: :high_contrast | :reduced_motion | :standard
def get_active_variant do
# Only try to get preferences if the process exists
{high_contrast, reduced_motion} =
case Process.whereis(UserPreferences) do
nil ->
{false, false}
_pid ->
{
UserPreferences.get(pref_key(:high_contrast)) || false,
UserPreferences.get(pref_key(:reduced_motion)) || false
}
end
case {high_contrast, reduced_motion} do
{true, _} -> :high_contrast
{_, true} -> :reduced_motion
{false, false} -> :standard
end
end
@doc """
Get the current color scheme based on accessibility settings.
## Examples
iex> ThemeIntegration.get_color_scheme()
%{bg: :black, fg: :white} # Returns high contrast colors when enabled
"""
@spec get_color_scheme() :: %{
bg: :black | {:rgb, 30, 30, 30},
fg: :white | {:rgb, 220, 220, 220},
accent: :yellow | :blue,
error: :red,
success: :green,
warning: :yellow
}
def get_color_scheme do
mode = get_accessibility_mode()
case mode do
:high_contrast ->
%{
bg: :black,
fg: :white,
accent: :yellow,
error: :red,
success: :green,
warning: :yellow
}
:standard ->
%{
bg: {:rgb, 30, 30, 30},
fg: {:rgb, 220, 220, 220},
accent: :blue,
error: :red,
success: :green,
warning: :yellow
}
end
end
@doc """
Get the current text scale based on accessibility settings.
## Examples
iex> ThemeIntegration.get_text_scale()
1.5 # Returns scale factor for large text mode
"""
@spec get_text_scale() :: float()
def get_text_scale do
# Only try to get preference if the process exists
large_text =
case Process.whereis(UserPreferences) do
nil -> false
_pid -> UserPreferences.get(pref_key(:large_text)) || false
end
get_text_scale_factor(large_text)
end
@spec handle_test_cleanup(any()) ::
{:ok, any()}
| {:error, any()}
| {:reply, any(), any()}
| {:noreply, any()}
defp handle_test_cleanup(false), do: :ok
@spec handle_test_cleanup(any()) ::
{:ok, any()}
| {:error, any()}
| {:reply, any(), any()}
| {:noreply, any()}
defp handle_test_cleanup(true) do
# Only try to reset if the process exists
case Process.whereis(UserPreferences) do
nil ->
:ok
pid when is_pid(pid) ->
try do
UserPreferences.reset_to_defaults_for_test!()
catch
:exit, _ -> :ok
end
end
end
defp determine_accessibility_mode(true), do: :high_contrast
defp determine_accessibility_mode(false), do: :standard
defp apply_theme_adjustments(true, theme, _mode) do
Theme.adjust_for_high_contrast(theme)
end
defp apply_theme_adjustments(false, theme, _mode), do: theme
defp get_text_scale_factor(true), do: 1.5
defp get_text_scale_factor(false), do: 1.0
defp test_env?, do: Code.ensure_loaded?(Mix) and Mix.env() == :test
end