defmodule LivebookWeb.Output.InputComponent do use LivebookWeb, :live_component @impl true def mount(socket) do {:ok, assign(socket, error: nil, local: false)} end @impl true def update(assigns, socket) do value = assigns.input_values[assigns.attrs.id] socket = socket |> assign(assigns) |> assign(value: value, initial_value: value) {:ok, socket} end @impl true def render(assigns) do ~H"""
<%= @attrs.label %>
<.input id={"#{@id}-input"} attrs={@attrs} value={@value} error={@error} myself={@myself} /> <%= if @error do %>
<%= @error %>
<% end %>
""" end defp input(%{attrs: %{type: :select}} = assigns) do ~H""" """ end defp input(%{attrs: %{type: :checkbox}} = assigns) do ~H"""
<.switch_checkbox data-element="input" name="value" checked={@value} />
""" end defp input(%{attrs: %{type: :range}} = assigns) do ~H"""
<%= @attrs.min %>
<%= @attrs.max %>
""" end defp input(%{attrs: %{type: :textarea}} = assigns) do ~H""" """ end defp input(%{attrs: %{type: :password}} = assigns) do ~H""" <.with_password_toggle id={"#{@id}-password-toggle"}> """ end defp input(%{attrs: %{type: type}} = assigns) when type in [:number, :color, :url, :text] do ~H""" """ end defp input(assigns) do ~H"""
Unknown input type <%= @attrs.type %>
""" end defp html_input_type(:number), do: "number" defp html_input_type(:color), do: "color" defp html_input_type(:url), do: "text" defp html_input_type(:text), do: "text" @impl true def handle_event("change", %{"value" => html_value}, socket) do {:noreply, handle_html_value(socket, html_value)} end def handle_event("blur", %{"value" => html_value}, socket) do socket = handle_html_value(socket, html_value) if socket.assigns.error do {:noreply, assign(socket, value: socket.assigns.initial_value, error: nil)} else {:noreply, socket} end end def handle_event("submit", %{"value" => html_value}, socket) do socket = handle_html_value(socket, html_value) send(self(), {:queue_bound_cells_evaluation, socket.assigns.attrs.id}) {:noreply, socket} end defp handle_html_value(socket, html_value) do current_value = socket.assigns.value case parse(html_value, socket.assigns.attrs) do {:ok, ^current_value} -> socket {:ok, value} -> send( self(), {:set_input_values, [{socket.assigns.attrs.id, value}], socket.assigns.local} ) unless socket.assigns.local do report_event(socket, value) end assign(socket, value: value, error: nil) {:error, error, value} -> assign(socket, value: value, error: error) end end defp parse(html_value, %{type: :text}) do {:ok, html_value} end defp parse(html_value, %{type: :textarea}) do # The browser may normalize newlines to \r\n, but we prefer just \n value = String.replace(html_value, "\r\n", "\n") {:ok, value} end defp parse(html_value, %{type: :password}) do {:ok, html_value} end defp parse(html_value, %{type: :number}) do if html_value == "" do {:ok, nil} else case Integer.parse(html_value) do {number, ""} -> {:ok, number} _ -> {number, ""} = Float.parse(html_value) {:ok, number} end end end defp parse(html_value, %{type: :url}) do cond do html_value == "" -> {:ok, nil} Livebook.Utils.valid_url?(html_value) -> {:ok, html_value} true -> {:error, "not a valid URL", html_value} end end defp parse(html_value, %{type: :select, options: options}) do selected_idx = String.to_integer(html_value) options |> Enum.with_index() |> Enum.find_value(fn {{key, _label}, idx} -> idx == selected_idx && {:ok, key} end) end defp parse(html_value, %{type: :checkbox}) do {:ok, html_value == "true"} end defp parse(html_value, %{type: :range}) do {number, ""} = Float.parse(html_value) {:ok, number} end defp parse(html_value, %{type: :color}) do {:ok, html_value} end defp report_event(socket, value) do topic = socket.assigns.attrs.ref event = %{value: value, origin: self(), type: :change} send(socket.assigns.attrs.destination, {:event, topic, event}) end end