-module(sprocket@render). -compile([no_auto_import, nowarn_unused_vars]). -export([traverse/2, find/2, append_attribute/2, live_render/4, render/2]). -export_type([rendered_attribute/0, rendered_element/0, renderer/1, render_result/1]). -type rendered_attribute() :: {rendered_attribute, binary(), binary()} | {rendered_event_handler, binary(), binary()} | {rendered_client_hook, binary(), binary()}. -type rendered_element() :: {rendered_element, binary(), gleam@option:option(binary()), list(rendered_attribute()), list(rendered_element())} | {rendered_component, fun((sprocket@context:context(), gleam@dynamic:dynamic_()) -> {sprocket@context:context(), list(sprocket@context:element())}), gleam@option:option(binary()), gleam@dynamic:dynamic_(), sprocket@internal@utils@ordered_map:ordered_map(integer(), sprocket@context:hook()), list(rendered_element())} | {rendered_text, binary()}. -type renderer(LQI) :: {renderer, fun((rendered_element()) -> LQI)}. -type render_result(LQJ) :: {render_result, sprocket@context:context(), LQJ}. -spec get_key(sprocket@context:element()) -> gleam@option:option(binary()). get_key(El) -> case El of {keyed, Key, _} -> {some, Key}; _ -> none end. -spec find_by_key(list(rendered_element()), binary()) -> gleam@option:option(rendered_element()). find_by_key(Children, Key) -> _pipe = gleam@list:find(Children, fun(Child) -> case Child of {rendered_component, _, {some, Child_key}, _, _, _} -> Child_key =:= Key; {rendered_element, _, {some, Child_key@1}, _, _} -> Child_key@1 =:= Key; _ -> false end end), gleam@option:from_result(_pipe). -spec get_child_by_key( gleam@option:option(rendered_element()), gleam@option:option(binary()) ) -> gleam@option:option(rendered_element()). get_child_by_key(Prev, Key) -> case {Prev, Key} of {{some, {rendered_component, _, _, _, _, Children}}, {some, Key@1}} -> find_by_key(Children, Key@1); {{some, {rendered_element, _, _, _, Children@1}}, {some, Key@2}} -> find_by_key(Children@1, Key@2); {_, _} -> none end. -spec maybe_matching_el(rendered_element(), sprocket@context:element()) -> gleam@option:option(rendered_element()). maybe_matching_el(Prev_child, Child) -> case {Prev_child, Child} of {{rendered_component, Prev_fc, _, _, _, _}, {component, Fc, _}} -> case Prev_fc =:= Fc of true -> {some, Prev_child}; false -> none end; {{rendered_element, Prev_tag, _, _, _}, {element, Tag, _, _}} -> case Prev_tag =:= Tag of true -> {some, Prev_child}; false -> none end; {_, {debug, _, _, El}} -> maybe_matching_el(Prev_child, El); {_, _} -> none end. -spec get_matching_prev_child_by_index( gleam@option:option(rendered_element()), sprocket@context:element(), integer() ) -> gleam@option:option(rendered_element()). get_matching_prev_child_by_index(Prev, Child, Index) -> case Prev of {some, {rendered_component, _, _, _, _, Children}} -> case gleam@list:at(Children, Index) of {ok, Prev_child} -> maybe_matching_el(Prev_child, Child); {error, nil} -> none end; {some, {rendered_element, _, _, _, Children@1}} -> case gleam@list:at(Children@1, Index) of {ok, Prev_child@1} -> maybe_matching_el(Prev_child@1, Child); {error, nil} -> none end; _ -> none end. -spec find_prev_child( gleam@option:option(rendered_element()), sprocket@context:element(), integer() ) -> gleam@option:option(rendered_element()). find_prev_child(Prev, Child, Index) -> Child_key = get_key(Child), gleam@option:'or'( get_child_by_key(Prev, Child_key), get_matching_prev_child_by_index(Prev, Child, Index) ). -spec safe_html(sprocket@context:context(), binary()) -> render_result(rendered_element()). safe_html(Ctx, Html) -> {render_result, Ctx, {rendered_text, Html}}. -spec raw(sprocket@context:context(), binary()) -> render_result(rendered_element()). raw(Ctx, Text) -> {render_result, Ctx, {rendered_text, Text}}. -spec traverse( rendered_element(), fun((rendered_element()) -> rendered_element()) ) -> rendered_element(). traverse(El, Updater) -> case Updater(El) of {rendered_component, Fc, Key, Props, Hooks, Children} -> {rendered_component, Fc, Key, Props, Hooks, gleam@list:map( Children, fun(Child) -> traverse(Child, Updater) end )}; {rendered_element, Tag, Key@1, Attrs, Children@1} -> {rendered_element, Tag, Key@1, Attrs, gleam@list:map( Children@1, fun(Child@1) -> traverse(Child@1, Updater) end )}; _ -> El end. -spec find(rendered_element(), fun((rendered_element()) -> boolean())) -> {ok, rendered_element()} | {error, nil}. find(El, Matches) -> case Matches(El) of true -> {ok, El}; false -> case El of {rendered_component, _, _, _, _, Children} -> gleam@list:find( Children, fun(Child) -> case find(Child, Matches) of {ok, _} -> true; {error, _} -> false end end ); {rendered_element, _, _, _, Children@1} -> gleam@list:find( Children@1, fun(Child@1) -> case find(Child@1, Matches) of {ok, _} -> true; {error, _} -> false end end ); _ -> {error, nil} end end. -spec append_attribute(rendered_element(), rendered_attribute()) -> rendered_element(). append_attribute(El, Attr) -> case El of {rendered_element, Tag, Key, Attrs, Children} -> {rendered_element, Tag, Key, gleam@list:append(Attrs, [Attr]), Children}; {rendered_component, Fc, Key@1, Props, Hooks, Children@1} -> {rendered_component, Fc, Key@1, Props, Hooks, gleam@list:map( Children@1, fun(Child) -> append_attribute(Child, Attr) end )}; _ -> El end. -spec element( sprocket@context:context(), binary(), gleam@option:option(binary()), list(sprocket@html@attributes:attribute()), list(sprocket@context:element()), gleam@option:option(rendered_element()) ) -> render_result(rendered_element()). element(Ctx, Tag, Key, Attrs, Children, Prev) -> {render_result, Ctx@3, Rendered_attrs@1} = gleam@list:fold( Attrs, {render_result, Ctx, []}, fun(Acc, Current) -> {render_result, Ctx@1, Rendered_attrs} = Acc, case Current of {attribute, Name, Value} -> _assert_subject = gleam@dynamic:string(Value), {ok, Value@1} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"sprocket/render"/utf8>>, function => <<"element"/utf8>>, line => 127}) end, {render_result, Ctx@1, [{rendered_attribute, Name, Value@1} | Rendered_attrs]}; {event, Kind, Identifiable_cb} -> {Ctx@2, Id} = sprocket@context:push_event_handler( Ctx@1, Identifiable_cb ), {render_result, Ctx@2, [{rendered_event_handler, Kind, sprocket@internal@utils@unique:to_string(Id)} | Rendered_attrs]}; {client_hook, Id@1, Name@1} -> {render_result, Ctx@1, [{rendered_client_hook, Name@1, sprocket@internal@utils@unique:to_string(Id@1)} | Rendered_attrs]} end end ), {render_result, Ctx@6, Children@1} = begin _pipe = Children, gleam@list:index_fold( _pipe, {render_result, Ctx@3, []}, fun(Acc@1, Child, I) -> {render_result, Ctx@4, Rendered} = Acc@1, Prev_child = find_prev_child(Prev, Child, I), {render_result, Ctx@5, Rendered_child} = live_render( Ctx@4, Child, none, Prev_child ), {render_result, Ctx@5, [Rendered_child | Rendered]} end ) end, {render_result, Ctx@6, {rendered_element, Tag, Key, gleam@list:reverse(Rendered_attrs@1), gleam@list:reverse(Children@1)}}. -spec live_render( sprocket@context:context(), sprocket@context:element(), gleam@option:option(binary()), gleam@option:option(rendered_element()) ) -> render_result(rendered_element()). live_render(Ctx, El, Key, Prev) -> case El of {element, Tag, Attrs, Children} -> element(Ctx, Tag, Key, Attrs, Children, Prev); {component, Fc, Props} -> component(Ctx, Fc, Key, Props, Prev); {debug, Id, Meta, El@1} -> gleam@io:debug({Id, Meta}), gleam@io:debug(live_render(Ctx, El@1, Key, Prev)); {keyed, Key@1, El@2} -> live_render(Ctx, El@2, {some, Key@1}, Prev); {ignore_update, El@3} -> case Prev of {some, Prev@1} -> _pipe = Prev@1, _pipe@1 = append_attribute( _pipe, {rendered_attribute, sprocket@internal@constants:constant( ignore_update_attr ), <<"true"/utf8>>} ), {render_result, Ctx, _pipe@1}; none -> {render_result, Ctx@1, Rendered} = live_render( Ctx, El@3, Key, Prev ), _pipe@2 = Rendered, _pipe@3 = append_attribute( _pipe@2, {rendered_attribute, sprocket@internal@constants:constant( ignore_update_attr ), <<"true"/utf8>>} ), {render_result, Ctx@1, _pipe@3} end; {safe_html, Html} -> safe_html(Ctx, Html); {raw, Text} -> raw(Ctx, Text) end. -spec render(sprocket@context:element(), renderer(LQK)) -> LQK. render(El, Renderer) -> _assert_subject = ids@cuid:start(), {ok, Cuid_channel} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"sprocket/render"/utf8>>, function => <<"render"/utf8>>, line => 52}) end, {render_result, _, Rendered} = live_render( sprocket@context:new(El, Cuid_channel, none), El, none, none ), (erlang:element(2, Renderer))(Rendered). -spec component( sprocket@context:context(), fun((sprocket@context:context(), gleam@dynamic:dynamic_()) -> {sprocket@context:context(), list(sprocket@context:element())}), gleam@option:option(binary()), gleam@dynamic:dynamic_(), gleam@option:option(rendered_element()) ) -> render_result(rendered_element()). component(Ctx, Fc, Key, Props, Prev) -> Ctx@1 = case Prev of none -> erlang:setelement( 3, Ctx, {component_wip, sprocket@internal@utils@ordered_map:new(), 0, true} ); {some, {rendered_component, _, _, _, Hooks, _}} -> erlang:setelement(3, Ctx, {component_wip, Hooks, 0, false}); {some, _} -> sprocket@internal@logger:error(<<"Invalid previous element"/utf8>>), erlang:error(#{gleam_error => panic, message => <<"panic expression evaluated"/utf8>>, module => <<"sprocket/render"/utf8>>, function => <<"component"/utf8>>, line => 197}) end, {Ctx@2, Children} = Fc(Ctx@1, Props), Hooks@1 = erlang:element(2, erlang:element(3, Ctx@2)), {render_result, Ctx@5, Children@1} = begin _pipe = Children, gleam@list:index_fold( _pipe, {render_result, Ctx@2, []}, fun(Acc, Child, I) -> {render_result, Ctx@3, Rendered} = Acc, Prev_child = find_prev_child(Prev, Child, I), {render_result, Ctx@4, Rendered_child} = live_render( Ctx@3, Child, none, Prev_child ), {render_result, Ctx@4, [Rendered_child | Rendered]} end ) end, {render_result, Ctx@5, {rendered_component, Fc, Key, Props, Hooks@1, gleam@list:reverse(Children@1)}}.