-module(agnostic@vdom@diff). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/agnostic/vdom/diff.gleam"). -export([diff/3]). -export_type([diff/1, partial_diff/1, attribute_change/1]). -type diff(KDG) :: {diff, agnostic@vdom@patch:patch(KDG), agnostic@vdom@cache:cache(KDG)}. -type partial_diff(KDH) :: {partial_diff, agnostic@vdom@patch:patch(KDH), agnostic@vdom@cache:cache(KDH), agnostic@vdom@cache:events(KDH)}. -type attribute_change(KDI) :: {attribute_change, list(agnostic@vdom@vattr:attribute(KDI)), list(agnostic@vdom@vattr:attribute(KDI)), agnostic@vdom@cache:events(KDI)}. -file("src/agnostic/vdom/diff.gleam", 955). -spec property_value_equal(gleam@json:json(), gleam@json:json()) -> boolean(). property_value_equal(A, B) -> A =:= B. -file("src/agnostic/vdom/diff.gleam", 762). -spec diff_attributes( boolean(), agnostic@vdom@path:path(), agnostic@vdom@cache:events(KEL), list(agnostic@vdom@vattr:attribute(KEL)), list(agnostic@vdom@vattr:attribute(KEL)), list(agnostic@vdom@vattr:attribute(KEL)), list(agnostic@vdom@vattr:attribute(KEL)) ) -> attribute_change(KEL). diff_attributes(Controlled, Path, Events, Old, New, Added, Removed) -> case {Old, New} of {[], []} -> {attribute_change, Added, Removed, Events}; {[{event, _, Name, _, _, _, _, _, _} = Prev | Old@1], []} -> Events@1 = agnostic@vdom@cache:remove_event(Events, Path, Name), Removed@1 = [Prev | Removed], diff_attributes( Controlled, Path, Events@1, Old@1, New, Added, Removed@1 ); {[Prev@1 | Old@2], []} -> Removed@2 = [Prev@1 | Removed], diff_attributes( Controlled, Path, Events, Old@2, New, Added, Removed@2 ); {[], [{event, _, Name@1, Handler, _, _, _, _, _} = Next | New@1]} -> Events@2 = agnostic@vdom@cache:add_event( Events, Path, Name@1, Handler ), Added@1 = [Next | Added], diff_attributes( Controlled, Path, Events@2, Old, New@1, Added@1, Removed ); {[], [Next@1 | New@2]} -> Added@2 = [Next@1 | Added], diff_attributes( Controlled, Path, Events, Old, New@2, Added@2, Removed ); {[Prev@2 | Remaining_old], [Next@2 | Remaining_new]} -> case {Prev@2, agnostic@vdom@vattr:compare(Prev@2, Next@2), Next@2} of {{attribute, _, _, _}, eq, {attribute, _, _, _}} -> Has_changes = case erlang:element(3, Next@2) of <<"value"/utf8>> -> Controlled orelse (erlang:element(4, Prev@2) /= erlang:element( 4, Next@2 )); <<"checked"/utf8>> -> Controlled orelse (erlang:element(4, Prev@2) /= erlang:element( 4, Next@2 )); <<"selected"/utf8>> -> Controlled orelse (erlang:element(4, Prev@2) /= erlang:element( 4, Next@2 )); _ -> erlang:element(4, Prev@2) /= erlang:element( 4, Next@2 ) end, Added@3 = case Has_changes of true -> [Next@2 | Added]; false -> Added end, diff_attributes( Controlled, Path, Events, Remaining_old, Remaining_new, Added@3, Removed ); {{property, _, _, _}, eq, {property, _, _, _}} -> Has_changes@1 = case erlang:element(3, Next@2) of <<"scrollLeft"/utf8>> -> true; <<"scrollRight"/utf8>> -> true; <<"value"/utf8>> -> Controlled orelse not property_value_equal( erlang:element(4, Prev@2), erlang:element(4, Next@2) ); <<"checked"/utf8>> -> Controlled orelse not property_value_equal( erlang:element(4, Prev@2), erlang:element(4, Next@2) ); <<"selected"/utf8>> -> Controlled orelse not property_value_equal( erlang:element(4, Prev@2), erlang:element(4, Next@2) ); _ -> not property_value_equal( erlang:element(4, Prev@2), erlang:element(4, Next@2) ) end, Added@4 = case Has_changes@1 of true -> [Next@2 | Added]; false -> Added end, diff_attributes( Controlled, Path, Events, Remaining_old, Remaining_new, Added@4, Removed ); {{event, _, _, _, _, _, _, _, _}, eq, {event, _, Name@2, Handler@1, _, _, _, _, _}} -> Has_changes@2 = (((erlang:element( 2, erlang:element(6, Prev@2) ) /= erlang:element(2, erlang:element(6, Next@2))) orelse (erlang:element(2, erlang:element(7, Prev@2)) /= erlang:element( 2, erlang:element(7, Next@2) ))) orelse (erlang:element(8, Prev@2) /= erlang:element( 8, Next@2 ))) orelse (erlang:element(9, Prev@2) /= erlang:element( 9, Next@2 )), Added@5 = case Has_changes@2 of true -> [Next@2 | Added]; false -> Added end, diff_attributes( Controlled, Path, agnostic@vdom@cache:add_event( Events, Path, Name@2, Handler@1 ), Remaining_old, Remaining_new, Added@5, Removed ); {{event, _, Name@3, _, _, _, _, _, _}, eq, _} -> diff_attributes( Controlled, Path, agnostic@vdom@cache:remove_event(Events, Path, Name@3), Remaining_old, Remaining_new, [Next@2 | Added], [Prev@2 | Removed] ); {_, eq, {event, _, Name@4, Handler@2, _, _, _, _, _}} -> diff_attributes( Controlled, Path, agnostic@vdom@cache:add_event( Events, Path, Name@4, Handler@2 ), Remaining_old, Remaining_new, [Next@2 | Added], [Prev@2 | Removed] ); {_, eq, _} -> diff_attributes( Controlled, Path, Events, Remaining_old, Remaining_new, [Next@2 | Added], [Prev@2 | Removed] ); {_, gt, {event, _, Name@5, Handler@3, _, _, _, _, _}} -> diff_attributes( Controlled, Path, agnostic@vdom@cache:add_event( Events, Path, Name@5, Handler@3 ), Old, Remaining_new, [Next@2 | Added], Removed ); {_, gt, _} -> diff_attributes( Controlled, Path, Events, Old, Remaining_new, [Next@2 | Added], Removed ); {{event, _, Name@6, _, _, _, _, _, _}, lt, _} -> diff_attributes( Controlled, Path, agnostic@vdom@cache:remove_event(Events, Path, Name@6), Remaining_old, New, Added, [Prev@2 | Removed] ); {_, lt, _} -> diff_attributes( Controlled, Path, Events, Remaining_old, New, Added, [Prev@2 | Removed] ) end end. -file("src/agnostic/vdom/diff.gleam", 741). -spec is_controlled( agnostic@vdom@cache:cache(any()), binary(), binary(), agnostic@vdom@path:path() ) -> boolean(). is_controlled(Cache, Namespace, Tag, Path) -> case Tag of <<"input"/utf8>> when Namespace =:= <<""/utf8>> -> agnostic@vdom@cache:has_dispatched_events(Cache, Path); <<"select"/utf8>> when Namespace =:= <<""/utf8>> -> agnostic@vdom@cache:has_dispatched_events(Cache, Path); <<"textarea"/utf8>> when Namespace =:= <<""/utf8>> -> agnostic@vdom@cache:has_dispatched_events(Cache, Path); _ -> false end. -file("src/agnostic/vdom/diff.gleam", 960). -spec is_browser() -> boolean(). is_browser() -> false. -file("src/agnostic/vdom/diff.gleam", 66). -spec do_diff( list(agnostic@vdom@vnode:element(KDO)), agnostic@internals@mutable_map:mutable_map(binary(), agnostic@vdom@vnode:element(KDO)), list(agnostic@vdom@vnode:element(KDO)), agnostic@internals@mutable_map:mutable_map(binary(), agnostic@vdom@vnode:element(KDO)), agnostic@internals@mutable_map:mutable_map(binary(), nil), integer(), integer(), integer(), integer(), list(agnostic@vdom@patch:change(KDO)), list(agnostic@vdom@patch:patch(KDO)), agnostic@vdom@path:path(), agnostic@vdom@cache:cache(KDO), agnostic@vdom@cache:events(KDO) ) -> partial_diff(KDO). do_diff( Old, Old_keyed, New, New_keyed, Moved, Moved_offset, Removed, Node_index, Patch_index, Changes, Children, Path, Cache, Events ) -> case {Old, New} of {[], []} -> Patch = case {Removed, Changes, Children, is_browser()} of {0, [], [Child], false} -> agnostic@vdom@patch:add_parent(Child, Patch_index); {_, _, _, _} -> agnostic@vdom@patch:new( Patch_index, Removed, Changes, Children ) end, {partial_diff, Patch, Cache, Events}; {[Prev | Old@1], []} -> case (erlang:element(3, Prev) =:= <<""/utf8>>) orelse not agnostic@internals@mutable_map:has_key( Moved, erlang:element(3, Prev) ) of true -> Events@1 = agnostic@vdom@cache:remove_child( Cache, Events, Path, Node_index, Prev ), do_diff( Old@1, Old_keyed, New, New_keyed, Moved, Moved_offset, Removed + 1, Node_index, Patch_index, Changes, Children, Path, Cache, Events@1 ); false -> do_diff( Old@1, Old_keyed, New, New_keyed, Moved, Moved_offset, Removed, Node_index, Patch_index, Changes, Children, Path, Cache, Events ) end; {[], [_ | _]} -> {Cache@1, Events@2} = agnostic@vdom@cache:add_children( Cache, Events, Path, Node_index, New ), Insert = agnostic@vdom@patch:insert(New, Node_index - Moved_offset), Changes@1 = [Insert | Changes], Patch@1 = agnostic@vdom@patch:new( Patch_index, Removed, Changes@1, Children ), {partial_diff, Patch@1, Cache@1, Events@2}; {[Prev@1 | Old_remaining], [Next | New_remaining]} when erlang:element( 3, Prev@1 ) =/= erlang:element(3, Next) -> Next_did_exist = agnostic@internals@mutable_map:has_key( Old_keyed, erlang:element(3, Next) ), Prev_does_exist = agnostic@internals@mutable_map:has_key( New_keyed, erlang:element(3, Prev@1) ), case {Prev_does_exist, Next_did_exist} of {true, true} -> case agnostic@internals@mutable_map:has_key( Moved, erlang:element(3, Prev@1) ) of true -> do_diff( Old_remaining, Old_keyed, New, New_keyed, Moved, Moved_offset - 1, Removed, Node_index, Patch_index, Changes, Children, Path, Cache, Events ); false -> Match = agnostic@internals@mutable_map:unsafe_get( Old_keyed, erlang:element(3, Next) ), Before = Node_index - Moved_offset, Changes@2 = [agnostic@vdom@patch:move( erlang:element(3, Next), Before ) | Changes], Moved@1 = agnostic@internals@mutable_map:insert( Moved, erlang:element(3, Next), nil ), do_diff( [Match | Old], Old_keyed, New, New_keyed, Moved@1, Moved_offset + 1, Removed, Node_index, Patch_index, Changes@2, Children, Path, Cache, Events ) end; {false, true} -> Index = Node_index - Moved_offset, Changes@3 = [agnostic@vdom@patch:remove(Index) | Changes], Events@3 = agnostic@vdom@cache:remove_child( Cache, Events, Path, Node_index, Prev@1 ), do_diff( Old_remaining, Old_keyed, New, New_keyed, Moved, Moved_offset - 1, Removed, Node_index, Patch_index, Changes@3, Children, Path, Cache, Events@3 ); {true, false} -> Before@1 = Node_index - Moved_offset, {Cache@2, Events@4} = agnostic@vdom@cache:add_child( Cache, Events, Path, Node_index, Next ), Insert@1 = agnostic@vdom@patch:insert( agnostic@internals@constants:singleton_list(Next), Before@1 ), Changes@4 = [Insert@1 | Changes], do_diff( Old, Old_keyed, New_remaining, New_keyed, Moved, Moved_offset + 1, Removed, Node_index + 1, Patch_index, Changes@4, Children, Path, Cache@2, Events@4 ); {false, false} -> Change = agnostic@vdom@patch:replace( Node_index - Moved_offset, Next ), {Cache@3, Events@5} = agnostic@vdom@cache:replace_child( Cache, Events, Path, Node_index, Prev@1, Next ), do_diff( Old_remaining, Old_keyed, New_remaining, New_keyed, Moved, Moved_offset, Removed, Node_index + 1, Patch_index, [Change | Changes], Children, Path, Cache@3, Events@5 ) end; {[{fragment, _, _, _, _} = Prev@2 | Old@2], [{fragment, _, _, _, _} = Next@1 | New@1]} -> {partial_diff, Patch@2, Cache@4, Events@6} = do_diff( erlang:element(4, Prev@2), erlang:element(5, Prev@2), erlang:element(4, Next@1), erlang:element(5, Next@1), maps:new(), 0, 0, 0, Node_index, [], [], agnostic@vdom@path:add( Path, Node_index, erlang:element(3, Next@1) ), Cache, Events ), Children@1 = case Patch@2 of {patch, _, _, 0, [], []} -> Children; _ -> [Patch@2 | Children] end, do_diff( Old@2, Old_keyed, New@1, New_keyed, Moved, Moved_offset, Removed, Node_index + 1, Patch_index, Changes, Children@1, Path, Cache@4, Events@6 ); {[{element, _, _, _, _, _, _, _} = Prev@3 | Old@3], [{element, _, _, _, _, _, _, _} = Next@2 | New@2]} when (erlang:element( 4, Prev@3 ) =:= erlang:element(4, Next@2)) andalso (erlang:element(5, Prev@3) =:= erlang:element( 5, Next@2 )) -> Child_path = agnostic@vdom@path:add( Path, Node_index, erlang:element(3, Next@2) ), Controlled = is_controlled( Cache, erlang:element(4, Next@2), erlang:element(5, Next@2), Child_path ), {attribute_change, Added_attrs, Removed_attrs, Events@7} = diff_attributes( Controlled, Child_path, Events, erlang:element(6, Prev@3), erlang:element(6, Next@2), [], [] ), Initial_child_changes = case {Added_attrs, Removed_attrs} of {[], []} -> []; {_, _} -> agnostic@internals@constants:singleton_list( agnostic@vdom@patch:update(Added_attrs, Removed_attrs) ) end, {partial_diff, Patch@3, Cache@5, Events@8} = do_diff( erlang:element(7, Prev@3), erlang:element(8, Prev@3), erlang:element(7, Next@2), erlang:element(8, Next@2), maps:new(), 0, 0, 0, Node_index, Initial_child_changes, [], Child_path, Cache, Events@7 ), Children@2 = case Patch@3 of {patch, _, _, 0, [], []} -> Children; _ -> [Patch@3 | Children] end, do_diff( Old@3, Old_keyed, New@2, New_keyed, Moved, Moved_offset, Removed, Node_index + 1, Patch_index, Changes, Children@2, Path, Cache@5, Events@8 ); {[{text, _, _, _} = Prev@4 | Old@4], [{text, _, _, _} = Next@3 | New@3]} when erlang:element( 4, Prev@4 ) =:= erlang:element(4, Next@3) -> do_diff( Old@4, Old_keyed, New@3, New_keyed, Moved, Moved_offset, Removed, Node_index + 1, Patch_index, Changes, Children, Path, Cache, Events ); {[{text, _, _, _} | Old@5], [{text, _, _, _} = Next@4 | New@4]} -> Child@1 = agnostic@vdom@patch:new( Node_index, 0, agnostic@internals@constants:singleton_list( agnostic@vdom@patch:replace_text(erlang:element(4, Next@4)) ), [] ), do_diff( Old@5, Old_keyed, New@4, New_keyed, Moved, Moved_offset, Removed, Node_index + 1, Patch_index, Changes, [Child@1 | Children], Path, Cache, Events ); {[{raw_container, _, _, _, _, _, _, _} = Prev@5 | Old@6], [{raw_container, _, _, _, _, _, _, _} = Next@5 | New@5]} -> Child_path@1 = agnostic@vdom@path:add( Path, Node_index, erlang:element(3, Next@5) ), {attribute_change, Added_attrs@1, Removed_attrs@1, Events@9} = diff_attributes( false, Child_path@1, Events, erlang:element(6, Prev@5), erlang:element(6, Next@5), [], [] ), Child_changes = case {Added_attrs@1, Removed_attrs@1} of {[], []} -> []; {_, _} -> agnostic@internals@constants:singleton_list( agnostic@vdom@patch:update( Added_attrs@1, Removed_attrs@1 ) ) end, Identical_content = case erlang:element(8, Next@5) of {some, Cmp} -> Cmp(erlang:element(7, Prev@5), erlang:element(7, Next@5)); none -> erlang:element(7, Prev@5) =:= erlang:element(7, Next@5) end, Child_changes@1 = case Identical_content of true -> Child_changes; false -> [agnostic@vdom@patch:replace_raw_content( erlang:element(7, Next@5) ) | Child_changes] end, Children@3 = case Child_changes@1 of [] -> Children; _ -> [agnostic@vdom@patch:new(Node_index, 0, Child_changes@1, []) | Children] end, do_diff( Old@6, Old_keyed, New@5, New_keyed, Moved, Moved_offset, Removed, Node_index + 1, Patch_index, Changes, Children@3, Path, Cache, Events@9 ); {[{raw_node, _, _, _, _} = Prev@6 | Old@7], [{raw_node, _, _, _, _} = Next@6 | New@6]} -> Identical_content@1 = case erlang:element(5, Next@6) of {some, Cmp@1} -> Cmp@1(erlang:element(4, Prev@6), erlang:element(4, Next@6)); none -> erlang:element(4, Prev@6) =:= erlang:element(4, Next@6) end, Children@4 = case Identical_content@1 of true -> Children; false -> [agnostic@vdom@patch:new( Node_index, 0, [agnostic@vdom@patch:replace_raw_node(Next@6)], [] ) | Children] end, do_diff( Old@7, Old_keyed, New@6, New_keyed, Moved, Moved_offset, Removed, Node_index + 1, Patch_index, Changes, Children@4, Path, Cache, Events ); {[{map, _, _, _, _} = Prev@7 | Old@8], [{map, _, _, _, _} = Next@7 | New@7]} -> Child_path@2 = agnostic@vdom@path:add( Path, Node_index, erlang:element(3, Next@7) ), Child_key = agnostic@vdom@path:child(Child_path@2), {partial_diff, Patch@4, Cache@6, Child_events} = do_diff( agnostic@internals@constants:singleton_list( erlang:element(5, Prev@7) ), maps:new(), agnostic@internals@constants:singleton_list( erlang:element(5, Next@7) ), maps:new(), maps:new(), 0, 0, 0, Node_index, [], [], agnostic@vdom@path:subtree(Child_path@2), Cache, agnostic@vdom@cache:get_subtree( Events, Child_key, erlang:element(4, Prev@7) ) ), Events@10 = agnostic@vdom@cache:update_subtree( Events, Child_key, erlang:element(4, Next@7), Child_events ), Children@5 = case Patch@4 of {patch, _, _, 0, [], []} -> Children; _ -> [Patch@4 | Children] end, do_diff( Old@8, Old_keyed, New@7, New_keyed, Moved, Moved_offset, Removed, Node_index + 1, Patch_index, Changes, Children@5, Path, Cache@6, Events@10 ); {[{memo, _, _, _, _} = Prev@8 | Old@9], [{memo, _, _, _, _} = Next@8 | New@8]} -> case agnostic@internals@ref:equal_lists( erlang:element(4, Prev@8), erlang:element(4, Next@8) ) of true -> Cache@7 = agnostic@vdom@cache:keep_memo( Cache, erlang:element(5, Prev@8), erlang:element(5, Next@8) ), do_diff( Old@9, Old_keyed, New@8, New_keyed, Moved, Moved_offset, Removed, Node_index + 1, Patch_index, Changes, Children, Path, Cache@7, Events ); false -> Prev_node = agnostic@vdom@cache:get_old_memo( Cache, erlang:element(5, Prev@8), erlang:element(5, Prev@8) ), Next_node = (erlang:element(5, Next@8))(), Cache@8 = agnostic@vdom@cache:add_memo( Cache, erlang:element(5, Next@8), Next_node ), do_diff( [Prev_node | Old@9], Old_keyed, [Next_node | New@8], New_keyed, Moved, Moved_offset, Removed, Node_index, Patch_index, Changes, Children, Path, Cache@8, Events ) end; {[Prev@9 | Old_remaining@1], [Next@9 | New_remaining@1]} -> Change@1 = agnostic@vdom@patch:replace( Node_index - Moved_offset, Next@9 ), {Cache@9, Events@11} = agnostic@vdom@cache:replace_child( Cache, Events, Path, Node_index, Prev@9, Next@9 ), do_diff( Old_remaining@1, Old_keyed, New_remaining@1, New_keyed, Moved, Moved_offset, Removed, Node_index + 1, Patch_index, [Change@1 | Changes], Children, Path, Cache@9, Events@11 ) end. -file("src/agnostic/vdom/diff.gleam", 35). -spec diff( agnostic@vdom@cache:cache(KDJ), agnostic@vdom@vnode:element(KDJ), agnostic@vdom@vnode:element(KDJ) ) -> diff(KDJ). diff(Cache, Old, New) -> Cache@1 = agnostic@vdom@cache:tick(Cache), {partial_diff, Patch, Cache@2, Events} = do_diff( agnostic@internals@constants:singleton_list(Old), maps:new(), agnostic@internals@constants:singleton_list(New), maps:new(), maps:new(), 0, 0, 0, 0, [], [], root, Cache@1, agnostic@vdom@cache:events(Cache@1) ), {diff, Patch, agnostic@vdom@cache:update_events(Cache@2, Events)}.