-module(lightspeed@form_compat). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/lightspeed/form_compat.gleam"). -export([new/1, change/4, blur/3, tick/2, submit/3, dispatch_label/1, has_pending/2, mode/2]). -export_type([change_mode/0, input_config/0, dispatch/0, input_state/0, runtime/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(" LiveView-style form change/submit compatibility semantics.\n"). -type change_mode() :: immediate | {debounce, integer()} | debounce_blur | {throttle, integer()}. -type input_config() :: {input_config, binary(), change_mode()}. -type dispatch() :: {dispatch_change, binary(), lightspeed@form:form_data()} | {dispatch_submit, lightspeed@form:form_data()} | {queued, binary()} | {suppressed, binary(), binary()} | no_dispatch. -type input_state() :: {input_state, binary(), change_mode(), gleam@option:option(integer()), gleam@option:option(lightspeed@form:form_data()), gleam@option:option(integer())}. -opaque runtime() :: {runtime, list(input_state())}. -file("src/lightspeed/form_compat.gleam", 126). -spec from_configs(list(input_config()), list(input_state())) -> list(input_state()). from_configs(Configs, States_rev) -> case Configs of [] -> lists:reverse(States_rev); [{input_config, Name, Mode} | Rest] -> from_configs( Rest, [{input_state, Name, Mode, none, none, none} | States_rev] ) end. -file("src/lightspeed/form_compat.gleam", 45). ?DOC(" Build a runtime from known inputs.\n"). -spec new(list(input_config())) -> runtime(). new(Configs) -> {runtime, from_configs(Configs, [])}. -file("src/lightspeed/form_compat.gleam", 400). -spec reverse_into(list(input_state()), list(input_state())) -> list(input_state()). reverse_into(Left, Right) -> case Left of [] -> Right; [Entry | Rest] -> reverse_into(Rest, [Entry | Right]) end. -file("src/lightspeed/form_compat.gleam", 392). -spec rebuild(list(input_state()), input_state(), list(input_state())) -> list(input_state()). rebuild(States_rev, Updated, Rest) -> reverse_into([Updated | States_rev], Rest). -file("src/lightspeed/form_compat.gleam", 365). -spec should_dispatch_throttle( gleam@option:option(integer()), integer(), integer() ) -> boolean(). should_dispatch_throttle(Last_dispatch, Now_ms, Window_ms) -> case Window_ms =< 0 of true -> true; false -> case Last_dispatch of none -> true; {some, Last} -> (Now_ms - Last) >= Window_ms end end. -file("src/lightspeed/form_compat.gleam", 147). -spec apply_change( list(input_state()), binary(), lightspeed@form:form_data(), integer(), list(input_state()) ) -> {list(input_state()), dispatch()}. apply_change(States, Input, Payload, At_ms, States_rev) -> case States of [] -> {lists:reverse(States_rev), {suppressed, Input, <<"unknown_input"/utf8>>}}; [State | Rest] -> case erlang:element(2, State) =:= Input of false -> apply_change( Rest, Input, Payload, At_ms, [State | States_rev] ); true -> case erlang:element(3, State) of immediate -> Next = {input_state, erlang:element(2, State), erlang:element(3, State), {some, At_ms}, none, none}, {rebuild(States_rev, Next, Rest), {dispatch_change, Input, Payload}}; {debounce, Wait_ms} -> case Wait_ms =< 0 of true -> Next@1 = {input_state, erlang:element(2, State), erlang:element(3, State), {some, At_ms}, none, none}, {rebuild(States_rev, Next@1, Rest), {dispatch_change, Input, Payload}}; false -> Next@2 = {input_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), {some, Payload}, {some, At_ms}}, {rebuild(States_rev, Next@2, Rest), {queued, Input}} end; debounce_blur -> Next@3 = {input_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), {some, Payload}, {some, At_ms}}, {rebuild(States_rev, Next@3, Rest), {queued, Input}}; {throttle, Window_ms} -> case should_dispatch_throttle( erlang:element(4, State), At_ms, Window_ms ) of true -> Next@4 = {input_state, erlang:element(2, State), erlang:element(3, State), {some, At_ms}, none, none}, {rebuild(States_rev, Next@4, Rest), {dispatch_change, Input, Payload}}; false -> {rebuild(States_rev, State, Rest), {suppressed, Input, <<"throttled"/utf8>>}} end end end end. -file("src/lightspeed/form_compat.gleam", 356). -spec clear_timers(input_state()) -> input_state(). clear_timers(State) -> {input_state, erlang:element(2, State), erlang:element(3, State), none, none, none}. -file("src/lightspeed/form_compat.gleam", 325). -spec reset_other_timers(list(input_state()), binary(), list(input_state())) -> list(input_state()). reset_other_timers(States, Selected_input, States_rev) -> case States of [] -> lists:reverse(States_rev); [State | Rest] -> case erlang:element(2, State) =:= Selected_input of true -> reset_other_timers( Rest, Selected_input, [State | States_rev] ); false -> reset_other_timers( Rest, Selected_input, [clear_timers(State) | States_rev] ) end end. -file("src/lightspeed/form_compat.gleam", 56). ?DOC( " Handle one `phx-change`-style event.\n" "\n" " Semantics:\n" " - debounce stores pending payload\n" " - debounce blur stores pending payload until blur\n" " - throttle emits immediately then suppresses inside window\n" " - change for one input resets timers for all other inputs\n" ). -spec change(runtime(), binary(), binary(), integer()) -> {runtime(), dispatch()}. change(Runtime, Input, Payload, At_ms) -> Parsed = lightspeed@form:parse_payload(Payload), Inputs = reset_other_timers(erlang:element(2, Runtime), Input, []), {Inputs@1, Dispatch} = apply_change(Inputs, Input, Parsed, At_ms, []), {{runtime, Inputs@1}, Dispatch}. -file("src/lightspeed/form_compat.gleam", 250). -spec apply_blur(list(input_state()), binary(), integer(), list(input_state())) -> {list(input_state()), dispatch()}. apply_blur(States, Input, At_ms, States_rev) -> case States of [] -> {lists:reverse(States_rev), {suppressed, Input, <<"unknown_input"/utf8>>}}; [State | Rest] -> case erlang:element(2, State) =:= Input of false -> apply_blur(Rest, Input, At_ms, [State | States_rev]); true -> case {erlang:element(3, State), erlang:element(5, State)} of {debounce_blur, {some, Payload}} -> Next = {input_state, erlang:element(2, State), erlang:element(3, State), {some, At_ms}, none, none}, {rebuild(States_rev, Next, Rest), {dispatch_change, Input, Payload}}; {_, _} -> {rebuild(States_rev, State, Rest), no_dispatch} end end end. -file("src/lightspeed/form_compat.gleam", 69). ?DOC(" Handle one blur event used by `phx-debounce=\"blur\"`.\n"). -spec blur(runtime(), binary(), integer()) -> {runtime(), dispatch()}. blur(Runtime, Input, At_ms) -> {Inputs, Dispatch} = apply_blur( erlang:element(2, Runtime), Input, At_ms, [] ), {{runtime, Inputs}, Dispatch}. -file("src/lightspeed/form_compat.gleam", 288). -spec flush_debounce( list(input_state()), integer(), list(input_state()), list(dispatch()) ) -> {list(input_state()), list(dispatch())}. flush_debounce(States, At_ms, States_rev, Dispatches_rev) -> case States of [] -> {lists:reverse(States_rev), Dispatches_rev}; [State | Rest] -> case {erlang:element(3, State), erlang:element(5, State), erlang:element(6, State)} of {{debounce, Wait_ms}, {some, Payload}, {some, Pending_since}} -> case (At_ms - Pending_since) >= Wait_ms of true -> Next = {input_state, erlang:element(2, State), erlang:element(3, State), {some, At_ms}, none, none}, flush_debounce( Rest, At_ms, [Next | States_rev], [{dispatch_change, erlang:element(2, State), Payload} | Dispatches_rev] ); false -> flush_debounce( Rest, At_ms, [State | States_rev], Dispatches_rev ) end; {_, _, _} -> flush_debounce( Rest, At_ms, [State | States_rev], Dispatches_rev ) end end. -file("src/lightspeed/form_compat.gleam", 79). ?DOC(" Flush due debounce timers.\n"). -spec tick(runtime(), integer()) -> {runtime(), list(dispatch())}. tick(Runtime, At_ms) -> {Inputs, Dispatches_rev} = flush_debounce( erlang:element(2, Runtime), At_ms, [], [] ), {{runtime, Inputs}, lists:reverse(Dispatches_rev)}. -file("src/lightspeed/form_compat.gleam", 345). -spec clear_all_timers(list(input_state()), list(input_state())) -> list(input_state()). clear_all_timers(States, States_rev) -> case States of [] -> lists:reverse(States_rev); [State | Rest] -> clear_all_timers(Rest, [clear_timers(State) | States_rev]) end. -file("src/lightspeed/form_compat.gleam", 87). ?DOC( " Handle one `phx-submit`-style event.\n" "\n" " Submit resets existing change timers.\n" ). -spec submit(runtime(), binary(), integer()) -> {runtime(), dispatch()}. submit(Runtime, Payload, _) -> Inputs = clear_all_timers(erlang:element(2, Runtime), []), {{runtime, Inputs}, {dispatch_submit, lightspeed@form:parse_payload(Payload)}}. -file("src/lightspeed/form_compat.gleam", 100). ?DOC(" Stable dispatch label for tests and logs.\n"). -spec dispatch_label(dispatch()) -> binary(). dispatch_label(Dispatch) -> case Dispatch of {dispatch_change, _, _} -> <<"change"/utf8>>; {dispatch_submit, _} -> <<"submit"/utf8>>; {queued, _} -> <<"queued"/utf8>>; {suppressed, _, Reason} -> <<"suppressed:"/utf8, Reason/binary>>; no_dispatch -> <<"none"/utf8>> end. -file("src/lightspeed/form_compat.gleam", 381). -spec find_input(list(input_state()), binary()) -> gleam@option:option(input_state()). find_input(States, Name) -> case States of [] -> none; [State | Rest] -> case erlang:element(2, State) =:= Name of true -> {some, State}; false -> find_input(Rest, Name) end end. -file("src/lightspeed/form_compat.gleam", 111). ?DOC(" Return pending status for one input.\n"). -spec has_pending(runtime(), binary()) -> boolean(). has_pending(Runtime, Input) -> case find_input(erlang:element(2, Runtime), Input) of {some, {input_state, _, _, _, {some, _}, _}} -> true; _ -> false end. -file("src/lightspeed/form_compat.gleam", 119). ?DOC(" Return the current mode for one input.\n"). -spec mode(runtime(), binary()) -> gleam@option:option(change_mode()). mode(Runtime, Input) -> case find_input(erlang:element(2, Runtime), Input) of {some, {input_state, _, Mode, _, _, _}} -> {some, Mode}; none -> none end.