defmodule Kino.Text do
@moduledoc ~S'''
A kino for rendering text content.
For rich text, use `Kino.Markdown`.
## Examples
Kino.Text.new("Hello!")
[:green, "Hello!"]
|> IO.ANSI.format()
|> IO.iodata_to_binary()
|> Kino.Text.new(terminal: true)
'''
@enforce_keys [:text]
defstruct [:text, :terminal, :chunk, :style]
@opaque t :: %__MODULE__{
text: String.t(),
terminal: boolean(),
chunk: boolean(),
style: style()
}
@type style :: [{:color | :font_weight | :font_size, String.Chars.t()}]
@doc """
Creates a new kino displaying the given text content.
## Options
* `:terminal` - whether to render the text as if it were printed to
standard output, supporting ANSI escape codes. Defaults to `false`
* `:chunk` - whether this is a part of a larger text. Adjacent chunks
are merged into a single text. This is useful for streaming content.
Defaults to `false`
* `:style` - a keyword list of CSS attributes, such as
`style: [color: "#FF0000", font_weight: :bold]`. The currently supported
styles are `:color`, `:font_size`, and `:font_weight`. Not supported on
terminal outputs.
## Examples
### Using the `:chunk` option
Using a `Kino.Frame`.
frame = Kino.Frame.new() |> Kino.render()
for word <- ["who", " let", " the", " dogs", " out"] do
text = Kino.Text.new(word, chunk: true)
Kino.Frame.append(frame, text)
Process.sleep(250)
end
Without using a `Kino.Frame`.
for word <- ["who", " let", " the", " dogs", " out"] do
Kino.Text.new(word, chunk: true) |> Kino.render()
Process.sleep(250)
end
Kino.nothing()
"""
@spec new(String.t(), opts) :: t()
when opts: [terminal: boolean(), chunk: boolean(), style: style()]
def new(text, opts \\ []) when is_binary(text) do
opts = Keyword.validate!(opts, terminal: false, chunk: false, style: [])
terminal? = opts[:terminal]
style = opts[:style]
cond do
not is_list(style) ->
raise ArgumentError, ":style must be a keyword list"
terminal? and style != [] ->
raise ArgumentError, ":style not supported when terminal: true"
true ->
Enum.each(style, fn
{key, value} when key in [:color, :font_weight, :font_size] ->
if String.contains?(to_string(value), ";") do
raise ArgumentError, "invalid CSS property value for #{inspect(key)}"
end
other ->
raise ArgumentError,
":style must be a keyword list of color/font_size/font_weight, got: #{inspect(other)}"
end)
end
%__MODULE__{text: text, terminal: terminal?, chunk: opts[:chunk], style: style}
end
end