diff --git a/lib/livebook/session.ex b/lib/livebook/session.ex index b94cb489df4..bb1ba9e37b1 100644 --- a/lib/livebook/session.ex +++ b/lib/livebook/session.ex @@ -3320,6 +3320,15 @@ defmodule Livebook.Session do Map.put_new(plain_text, :style, []) end + defp normalize_runtime_output(%{type: :input} = input) when input.attrs.type == :number do + update_in(input.attrs, fn attrs -> + attrs + |> Map.put_new(:min, nil) + |> Map.put_new(:max, nil) + |> Map.put_new(:step, nil) + end) + end + # Traverse composite outputs defp normalize_runtime_output(%{type: :grid} = grid) do diff --git a/lib/livebook_web/live/output/input_component.ex b/lib/livebook_web/live/output/input_component.ex index 80ee32c98a5..c1e983815d8 100644 --- a/lib/livebook_web/live/output/input_component.ex +++ b/lib/livebook_web/live/output/input_component.ex @@ -25,11 +25,11 @@ defmodule LivebookWeb.Output.InputComponent do @impl true def render(assigns) when assigns.input.attrs.type == :image do ~H""" -
+
<.input_label label={@input.attrs.label} changed={@changed} /> <.live_component module={LivebookWeb.Output.ImageInputComponent} - id={"#{@id}-input"} + id={"#{@id}-input-#{@counter}"} input_component_id={@id} value={@value} height={@input.attrs.size && elem(@input.attrs.size, 0)} @@ -47,11 +47,11 @@ defmodule LivebookWeb.Output.InputComponent do def render(assigns) when assigns.input.attrs.type == :audio do ~H""" -
+
<.input_label label={@input.attrs.label} changed={@changed} /> <.live_component module={LivebookWeb.Output.AudioInputComponent} - id={"#{@id}-input"} + id={"#{@id}-input-#{@counter}"} input_component_id={@id} value={@value} format={@input.attrs.format} @@ -67,11 +67,11 @@ defmodule LivebookWeb.Output.InputComponent do def render(assigns) when assigns.input.attrs.type == :file do ~H""" -
+
<.input_label label={@input.attrs.label} changed={@changed} /> <.live_component module={LivebookWeb.Output.FileInputComponent} - id={"#{@id}-input"} + id={"#{@id}-input-#{@counter}"} input_component_id={@id} value={@value} accept={@input.attrs.accept} @@ -86,7 +86,7 @@ defmodule LivebookWeb.Output.InputComponent do def render(assigns) when assigns.input.attrs.type == :utc_datetime do ~H""" -
+
<.input_label label={@input.attrs.label} changed={@changed} @@ -95,7 +95,7 @@ defmodule LivebookWeb.Output.InputComponent do
<.text_field class="w-auto" - id={@id} + id={"#{@id}-input-#{@counter}"} type="datetime-local" data-el-input name="html_value" @@ -115,7 +115,7 @@ defmodule LivebookWeb.Output.InputComponent do def render(assigns) when assigns.input.attrs.type == :utc_time do ~H""" -
+
<.input_label label={@input.attrs.label} changed={@changed} @@ -123,7 +123,7 @@ defmodule LivebookWeb.Output.InputComponent do />
<.text_field - id={@id} + id={"#{@id}-input-#{@counter}"} type="time" data-el-input name="html_value" @@ -143,9 +143,14 @@ defmodule LivebookWeb.Output.InputComponent do def render(assigns) do ~H""" -
+ <.input_label label={@input.attrs.label} changed={@changed} /> - <.input_output id={"#{@id}-input"} attrs={@input.attrs} value={@value} myself={@myself} /> + <.input_output + id={"#{@id}-input-#{@counter}"} + attrs={@input.attrs} + value={@value} + myself={@myself} + />
""" end @@ -268,8 +273,28 @@ defmodule LivebookWeb.Output.InputComponent do """ end - defp input_output(%{attrs: %{type: type}} = assigns) - when type in [:number, :url, :text] do + defp input_output(%{attrs: %{type: :number}} = assigns) do + ~H""" +
+ <.text_field + type="number" + data-el-input + id={@id} + name="html_value" + value={to_string(@value)} + phx-debounce={@attrs.debounce} + phx-target={@myself} + min={@attrs.min} + max={@attrs.max} + step={@attrs.step} + spellcheck="false" + autocomplete="off" + /> +
+ """ + end + + defp input_output(%{attrs: %{type: type}} = assigns) when type in [:url, :text] do ~H"""
<.text_field @@ -312,7 +337,6 @@ defmodule LivebookWeb.Output.InputComponent do """ end - defp html_input_type(:number), do: "number" defp html_input_type(:url), do: "url" defp html_input_type(:text), do: "text" @@ -374,17 +398,15 @@ defmodule LivebookWeb.Output.InputComponent do {:ok, html_value} end - defp parse(html_value, %{type: :number}) do + defp parse(html_value, %{type: :number} = attrs) do if html_value == "" do {:ok, nil} else - case Integer.parse(html_value) do - {number, ""} -> - {:ok, number} - - _ -> - {number, ""} = Float.parse(html_value) - {:ok, number} + with {:ok, number} <- parse_number(html_value), + true <- in_range?(number, attrs.min, attrs.max) do + {:ok, number} + else + _ -> :error end end end @@ -461,6 +483,22 @@ defmodule LivebookWeb.Output.InputComponent do end end + defp parse_number(html_value) do + case Integer.parse(html_value) do + {number, ""} -> + {:ok, number} + + _ -> + case Float.parse(html_value) do + {number, ""} -> + {:ok, number} + + _ -> + :error + end + end + end + defp truncate_datetime(datetime) do datetime |> NaiveDateTime.truncate(:second) @@ -479,6 +517,10 @@ defmodule LivebookWeb.Output.InputComponent do (max == nil or struct.compare(datetime, max) != :gt) end + defp in_range?(number, min, max) when is_number(number) do + (min == nil or number >= min) and (max == nil or number <= max) + end + defp report_event(socket, value) do topic = socket.assigns.input.ref event = %{value: value, origin: socket.assigns.client_id, type: :change} diff --git a/mix.lock b/mix.lock index 4ebf54a4727..8d72edd2086 100644 --- a/mix.lock +++ b/mix.lock @@ -44,7 +44,7 @@ "phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.1", "05df733a09887a005ed0d69a7fc619d376aea2730bf64ce52ac51ce716cc1ef0", [:mix], [{:file_system, "~> 0.2.10 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "74273843d5a6e4fef0bbc17599f33e3ec63f08e69215623a0cd91eea4288e5a0"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.11", "1b4d8fa56898d93b6f528c89227198a3fce7c5b242819b22ed9e92b73c1bb077", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "266823602e11a54e562ac03a25b3d232d79de12514262db7cfcbb83fdfd8fd57"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.24", "1a000a048d5971b61a9efe29a3c4144ca955afd42224998d841c5011a5354838", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0c724e6c65f197841cac49d73be4e0f9b93a7711eaa52d2d4d1b9f859c329267"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "plug": {:hex, :plug, "1.18.0", "d78df36c41f7e798f2edf1f33e1727eae438e9dd5d809a9997c463a108244042", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "819f9e176d51e44dc38132e132fe0accaf6767eab7f0303431e404da8476cfa2"},