-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.