Drafter.Widget.Link (drafter v0.3.1)

Copy Markdown View Source

An inline hyperlink widget that opens a URL in the system browser when activated.

Renders as underlined text. When focused or hovered the text is wrapped in square brackets ([label]) for visibility. Activating via Enter or mouse click invokes the platform's default browser opener (open on macOS, xdg-open on Linux, cmd /c start on Windows).

Component tag

Tag :link, built by Drafter.App as {:link, text, opts}:

link(text, opts)
link(text, url)

The positional argument becomes :text, falling back to opts[:text] when nil. Passing a binary second argument is shorthand for [url: binary].

Options

  • :text - String.t/0 display text. Default nil, in which case the :url is used as the label. Supplied positionally through the link/2 element, falling back to opts[:text] when the positional value is nil
  • :url - String.t/0 to open. Default nil; without it, activating the link does nothing
  • :tooltip - stored on the widget state; never rendered. Default nil
  • :style - map/0 of style overrides. Default %{}
  • :class - theme class atom or list of them, reaching mount/1 as :classes. Default []
  • :app_module - module supplying a per-app theme, passed by the renderer as :__app_module__. Default nil

mount/1 always starts :focused and :hovered at false and ignores props of those names. update/2 re-reads every option, but through the component tree only :text, :url and :app_module are re-applied on a re-render, making :style, :classes and :tooltip effectively mount-only there.

Widget value

Drafter.get_widget_value/1 returns the link's :text, which is nil when the label falls back to the URL.

Usage

link("Elixir website", url: "https://elixir-lang.org")

Summary

Functions

The registry tag for this widget.

Turns the {:link, text, opts} element into a props map for mount/1.

Handles events directly instead of going through Drafter.Widget.EventRouter.

Builds the link state from props.

Always 1: the link occupies a single row.

Draws the link as a single underlined strip.

Callback implementation for Drafter.Widget.unmount/1.

Folds fresh props into state, re-reading :text, :url, :style, :classes, :app_module and :tooltip. :focused and :hovered are left alone.

Narrows the props a re-render feeds to update/2 to :text, :url and :app_module, so :style, :classes and :tooltip stay as mounted.

Types

t()

@type t() :: %Drafter.Widget.Link{
  app_module: module() | nil,
  classes: [atom()],
  focused: boolean(),
  hovered: boolean(),
  style: map(),
  text: String.t() | nil,
  tooltip: String.t() | nil,
  url: String.t() | nil
}

Functions

component_tag()

@spec component_tag() :: :link

The registry tag for this widget.

iex> Drafter.Widget.Link.component_tag()
:link

focused(state)

from_component_opts(text, opts)

@spec from_component_opts(
  term(),
  keyword()
) :: Drafter.Widget.props()

Turns the {:link, text, opts} element into a props map for mount/1.

A nil positional text falls back to opts[:text]. :class is normalised into :classes and :__app_module__ becomes :app_module.

iex> Drafter.Widget.Link.from_component_opts("Elixir", url: "https://elixir-lang.org")
%{text: "Elixir", url: "https://elixir-lang.org", style: %{}, classes: [], tooltip: nil, app_module: nil}

handle_event(event, state)

@spec handle_event(Drafter.Event.t() | atom(), t() | Drafter.Widget.props()) ::
  {:ok, t()} | {:noreply, t()}

Handles events directly instead of going through Drafter.Widget.EventRouter.

{:key, :enter} and a mouse release run the platform browser opener for :url and return {:ok, state} unchanged; with no :url nothing is run. {:focus} sets both :focused and :hovered, {:blur} clears both, and :hover/:unhover move :hovered alone. Everything else, including Space, returns {:noreply, state}.

iex> l = Drafter.Widget.Link.mount(%{text: "Elixir", url: "https://elixir-lang.org"})
iex> {:ok, focused} = Drafter.Widget.Link.handle_event({:focus}, l)
iex> {focused.focused, focused.hovered}
{true, true}

iex> l = Drafter.Widget.Link.mount(%{text: "Elixir"})
iex> Drafter.Widget.Link.handle_event({:key, :" "}, l) |> elem(0)
:noreply

mount(props)

@spec mount(Drafter.Widget.props()) :: t()

Builds the link state from props.

iex> l = Drafter.Widget.Link.mount(%{text: "Elixir", url: "https://elixir-lang.org"})
iex> {l.text, l.url, l.focused, l.hovered}
{"Elixir", "https://elixir-lang.org", false, false}

iex> l = Drafter.Widget.Link.mount(%{})
iex> {l.text, l.url, l.style, l.classes, l.tooltip}
{nil, nil, %{}, [], nil}

preferred_height(args, opts)

@spec preferred_height(
  term(),
  keyword()
) :: pos_integer()

Always 1: the link occupies a single row.

render(state, rect)

Draws the link as a single underlined strip.

Accepts either a t/0 or a raw props map, which is mounted first. The label is :text, falling back to :url, and is wrapped in square brackets while focused or hovered. rect is not consulted, so a label wider than the rect is neither cropped nor padded. While hovered a :hover class and while focused a :focus class are added to the computed style.

unmount(state)

Callback implementation for Drafter.Widget.unmount/1.

update(props, state)

@spec update(Drafter.Widget.props(), t()) :: t()

Folds fresh props into state, re-reading :text, :url, :style, :classes, :app_module and :tooltip. :focused and :hovered are left alone.

iex> l = Drafter.Widget.Link.mount(%{text: "Old", url: "https://a.example"})
iex> Drafter.Widget.Link.update(%{text: "New"}, l).url
"https://a.example"

update_props_from_mount(mount_props, existing_state, opts)

@spec update_props_from_mount(Drafter.Widget.props(), t(), keyword()) ::
  Drafter.Widget.props()

Narrows the props a re-render feeds to update/2 to :text, :url and :app_module, so :style, :classes and :tooltip stay as mounted.