Current section
Files
Jump to
Current section
Files
src/agnostic@platform@dom.erl
-module(agnostic@platform@dom).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/agnostic/platform/dom.gleam").
-export([before_paint/1, after_paint/1, platform_strict/1, platform/1, attrs_to_string_tree/4, to_string_tree/2, to_string/1, serializer/0, is_void/3, is_self_closing/2, serialize_tree/3, serialize/2, to_serializer/1, serializer_config/0, empty_serializer_config/0, with_void/2, with_void_elements/2, with_self_closing/2, with_self_closing_tags/2, to_document_string/1, to_document_string_tree/1, to_snapshot/2, to_readable_string/1]).
-export_type([dom_node/0, dom_event/0, serializer_config/0, document_type/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(
" This module contains the DOM platform implementation and HTML-specific\n"
" rendering utilities. The DOM platform provides the low-level mutation methods\n"
" needed to render Lustre applications to the browser DOM.\n"
"\n"
" For HTML serialization and SSR, see the [`SerializerConfig`](#SerializerConfig) type and\n"
" related functions like [`to_string`](#to_string) and [`serialize`](#serialize).\n"
"\n"
).
-type dom_node() :: any().
-type dom_event() :: any().
-opaque serializer_config() :: {serializer_config,
gleam@set:set(binary()),
gleam@set:set(binary())}.
-type document_type() :: html | head_only | body_only | head_and_body | other.
-file("src/agnostic/platform/dom.gleam", 89).
?DOC(
" Schedule a side effect that is guaranteed to run after your `view` function\n"
" is called and the DOM has been updated, but **before** the browser has\n"
" painted the screen. This effect is useful when you need to read from the DOM\n"
" or perform other operations that might affect the layout of your application.\n"
"\n"
" In addition to the `dispatch` function, your callback will also be provided\n"
" with root element of your app or component. This is especially useful inside\n"
" of components, giving you a reference to the [Shadow Root](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot).\n"
"\n"
" Messages dispatched immediately in this effect will trigger a second re-render\n"
" of your application before the browser paints the screen. This let's you read\n"
" the state of the DOM, update your model, and then render a second time with\n"
" the additional information.\n"
"\n"
" > **Note**: dispatching messages synchronously in this effect can lead to\n"
" > degraded performance if not used correctly. In the worst case you can lock\n"
" > up the browser and prevent it from painting the screen _at all_.\n"
"\n"
" > **Note**: platforms that do not declare this phase — including server\n"
" > components — drop this effect and never run it.\n"
).
-spec before_paint(fun((fun((IPT) -> nil), gleam@dynamic:dynamic_()) -> nil)) -> agnostic@effect:effect(IPT).
before_paint(Effect) ->
agnostic@effect:deferred(<<"before_paint"/utf8>>, Effect).
-file("src/agnostic/platform/dom.gleam", 105).
?DOC(
" Schedule a side effect that is guaranteed to run after the browser has painted\n"
" the screen.\n"
"\n"
" In addition to the `dispatch` function, your callback will also be provided\n"
" with root element of your app or component. This is especially useful inside\n"
" of components, giving you a reference to the [Shadow Root](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot).\n"
"\n"
" > **Note**: platforms that do not declare this phase — including server\n"
" > components — drop this effect and never run it.\n"
).
-spec after_paint(fun((fun((IPV) -> nil), gleam@dynamic:dynamic_()) -> nil)) -> agnostic@effect:effect(IPV).
after_paint(Effect) ->
agnostic@effect:deferred(<<"after_paint"/utf8>>, Effect).
-file("src/agnostic/platform/dom.gleam", 146).
?DOC(false).
-spec platform_strict(dom_node()) -> agnostic@platform:platform(dom_node(), dom_node(), dom_node(), dom_event(), any(), dom_node()).
platform_strict(_) ->
erlang:error(#{gleam_error => panic,
message => <<"Cannot create DOM platform on Erlang"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"agnostic/platform/dom"/utf8>>,
function => <<"platform_strict"/utf8>>,
line => 149}).
-file("src/agnostic/platform/dom.gleam", 160).
-spec do_query_selector_raw(binary()) -> {ok, dom_node()} | {error, binary()}.
do_query_selector_raw(_) ->
{error, <<""/utf8>>}.
-file("src/agnostic/platform/dom.gleam", 152).
-spec do_query_selector(binary()) -> {ok, dom_node()} |
{error, agnostic@platform:platform_error()}.
do_query_selector(Selector) ->
case do_query_selector_raw(Selector) of
{ok, Node} ->
{ok, Node};
{error, Sel} ->
{error, {element_not_found, Sel}}
end.
-file("src/agnostic/platform/dom.gleam", 124).
?DOC(
" Returns a [`Platform`](../platform.html#Platform) configured for the browser\n"
" DOM. This is the standard platform used by client-side Lustre applications.\n"
"\n"
" The `target` argument is a CSS selector used to locate the DOM element where\n"
" the application will be mounted. The selector is resolved at construction\n"
" time, and the resolved DOM node is stored as the platform's target.\n"
"\n"
" On Erlang this always returns `Error(NotABrowser)`. On JavaScript this\n"
" succeeds when running in a browser environment and the selector matches an\n"
" element.\n"
).
-spec platform(binary()) -> {ok,
agnostic@platform:platform(dom_node(), dom_node(), dom_node(), dom_event(), any(), dom_node())} |
{error, agnostic@platform:platform_error()}.
platform(Target) ->
gleam@bool:guard(
not agnostic@platform:is_browser(),
{error, not_a_browser},
fun() ->
gleam@result:'try'(
do_query_selector(Target),
fun(Target@1) -> {ok, platform_strict(Target@1)} end
)
end
).
-file("src/agnostic/platform/dom.gleam", 823).
-spec marker_comment(binary(), binary()) -> gleam@string_tree:string_tree().
marker_comment(Label, Key) ->
case Key of
<<""/utf8>> ->
gleam_stdlib:identity(
<<<<"<!-- "/utf8, Label/binary>>/binary, " -->"/utf8>>
);
_ ->
_pipe = gleam_stdlib:identity(
<<<<"<!-- "/utf8, Label/binary>>/binary, " key=\""/utf8>>
),
_pipe@1 = gleam@string_tree:append(_pipe, houdini:escape(Key)),
gleam@string_tree:append(_pipe@1, <<"\" -->"/utf8>>)
end.
-file("src/agnostic/platform/dom.gleam", 835).
-spec attrs_to_string_tree(
binary(),
binary(),
binary(),
list(agnostic@vdom@vattr:attribute(any()))
) -> gleam@string_tree:string_tree().
attrs_to_string_tree(Key, Namespace, Parent_namespace, Attributes) ->
Attributes@1 = case Key /= <<""/utf8>> of
true ->
[agnostic@vdom@vattr:attribute(<<"data-lustre-key"/utf8>>, Key) |
Attributes];
false ->
Attributes
end,
Attributes@2 = case Namespace /= Parent_namespace of
true when Namespace =:= <<""/utf8>> ->
[agnostic@vdom@vattr:attribute(
<<"xmlns"/utf8>>,
<<"http://www.w3.org/1999/xhtml"/utf8>>
) |
Attributes@1];
true ->
[agnostic@vdom@vattr:attribute(<<"xmlns"/utf8>>, Namespace) |
Attributes@1];
false ->
Attributes@1
end,
gleam@list:fold(
Attributes@2,
gleam@string_tree:new(),
fun(Html, Attr) -> case Attr of
{attribute, _, <<"virtual:defaultValue"/utf8>>, Value} ->
gleam@string_tree:append(
Html,
<<<<" value=\""/utf8, (houdini:escape(Value))/binary>>/binary,
"\""/utf8>>
);
{attribute, _, <<"virtual:defaultChecked"/utf8>>, _} ->
gleam@string_tree:append(Html, <<" checked"/utf8>>);
{attribute, _, <<"virtual:defaultSelected"/utf8>>, _} ->
gleam@string_tree:append(Html, <<" selected"/utf8>>);
{attribute, _, <<""/utf8>>, _} ->
Html;
{attribute, _, Name, <<""/utf8>>} ->
gleam@string_tree:append(Html, <<" "/utf8, Name/binary>>);
{attribute, _, Name@1, Value@1} ->
gleam@string_tree:append(
Html,
(<<<<<<<<" "/utf8, Name@1/binary>>/binary, "=\""/utf8>>/binary,
(houdini:escape(Value@1))/binary>>/binary,
"\""/utf8>>)
);
_ ->
Html
end end
).
-file("src/agnostic/platform/dom.gleam", 325).
?DOC(
" Check if a tag is an HTML void element (cannot have children and should not\n"
" have a closing tag). Only applies to elements in the default HTML namespace.\n"
"\n"
" This uses the default HTML void elements list. For custom configurations,\n"
" use a [`SerializerConfig`](#SerializerConfig) instead.\n"
).
-spec is_void_element(binary(), binary()) -> boolean().
is_void_element(Tag, Namespace) ->
case Namespace of
<<""/utf8>> ->
case Tag of
<<"area"/utf8>> ->
true;
<<"base"/utf8>> ->
true;
<<"br"/utf8>> ->
true;
<<"col"/utf8>> ->
true;
<<"embed"/utf8>> ->
true;
<<"hr"/utf8>> ->
true;
<<"img"/utf8>> ->
true;
<<"input"/utf8>> ->
true;
<<"link"/utf8>> ->
true;
<<"meta"/utf8>> ->
true;
<<"param"/utf8>> ->
true;
<<"source"/utf8>> ->
true;
<<"track"/utf8>> ->
true;
<<"wbr"/utf8>> ->
true;
_ ->
false
end;
_ ->
false
end.
-file("src/agnostic/platform/dom.gleam", 536).
-spec children_to_string_tree(
gleam@string_tree:string_tree(),
list(agnostic@vdom@vnode:element(any())),
binary()
) -> gleam@string_tree:string_tree().
children_to_string_tree(Html, Children, Namespace) ->
gleam@list:fold(
Children,
Html,
fun(Html@1, Child) ->
gleam_stdlib:iodata_append(Html@1, to_string_tree(Child, Namespace))
end
).
-file("src/agnostic/platform/dom.gleam", 475).
?DOC(
" Convert a Lustre `Element` to an HTML `StringTree`. This is more efficient\n"
" than `to_string` when the output will be written to a socket or file.\n"
"\n"
" This uses the default HTML void elements list. For custom void or\n"
" self-closing configurations, use [`serialize_tree`](#serialize_tree) with\n"
" a custom [`SerializerConfig`](#SerializerConfig).\n"
).
-spec to_string_tree(agnostic@vdom@vnode:element(any()), binary()) -> gleam@string_tree:string_tree().
to_string_tree(Node, Parent_namespace) ->
case Node of
{text, _, _, <<""/utf8>>} ->
gleam@string_tree:new();
{text, _, _, Content} ->
gleam_stdlib:identity(houdini:escape(Content));
{element, _, Key, Namespace, Tag, Attributes, Children, _} ->
Html = gleam_stdlib:identity(<<"<"/utf8, Tag/binary>>),
Attributes@1 = attrs_to_string_tree(
Key,
Namespace,
Parent_namespace,
Attributes
),
case is_void_element(Tag, Namespace) of
true ->
_pipe = Html,
_pipe@1 = gleam_stdlib:iodata_append(_pipe, Attributes@1),
gleam@string_tree:append(_pipe@1, <<">"/utf8>>);
false ->
_pipe@2 = Html,
_pipe@3 = gleam_stdlib:iodata_append(_pipe@2, Attributes@1),
_pipe@4 = gleam@string_tree:append(_pipe@3, <<">"/utf8>>),
_pipe@5 = children_to_string_tree(
_pipe@4,
Children,
Namespace
),
gleam@string_tree:append(
_pipe@5,
<<<<"</"/utf8, Tag/binary>>/binary, ">"/utf8>>
)
end;
{raw_container,
_,
Key@1,
Namespace@1,
Tag@1,
Attributes@2,
Content@1,
_} ->
Html@1 = gleam_stdlib:identity(<<"<"/utf8, Tag@1/binary>>),
Attributes@3 = attrs_to_string_tree(
Key@1,
Namespace@1,
Parent_namespace,
Attributes@2
),
_pipe@6 = Html@1,
_pipe@7 = gleam_stdlib:iodata_append(_pipe@6, Attributes@3),
_pipe@8 = gleam@string_tree:append(_pipe@7, <<">"/utf8>>),
_pipe@9 = gleam@string_tree:append(
_pipe@8,
gleam@function:identity(Content@1)
),
gleam@string_tree:append(
_pipe@9,
<<<<"</"/utf8, Tag@1/binary>>/binary, ">"/utf8>>
);
{raw_node, _, _, Content@2, _} ->
gleam_stdlib:identity(gleam@function:identity(Content@2));
{fragment, _, Key@2, Children@1, _} ->
_pipe@10 = marker_comment(<<"lustre:fragment"/utf8>>, Key@2),
_pipe@11 = children_to_string_tree(
_pipe@10,
Children@1,
Parent_namespace
),
gleam_stdlib:iodata_append(
_pipe@11,
marker_comment(<<"/lustre:fragment"/utf8>>, <<""/utf8>>)
);
{map, _, Key@3, _, Child} ->
_pipe@12 = marker_comment(<<"lustre:map"/utf8>>, Key@3),
gleam_stdlib:iodata_append(
_pipe@12,
to_string_tree(Child, Parent_namespace)
);
{memo, _, Key@4, _, View} ->
_pipe@13 = marker_comment(<<"lustre:memo"/utf8>>, Key@4),
gleam_stdlib:iodata_append(
_pipe@13,
to_string_tree(View(), Parent_namespace)
)
end.
-file("src/agnostic/platform/dom.gleam", 462).
?DOC(
" Convert a Lustre `Element` to an HTML string. This is _not_ pretty-printed,\n"
" so there are no newlines or indentation.\n"
"\n"
" This uses the default HTML void elements. For custom void or self-closing\n"
" configurations, use [`serialize`](#serialize) with a custom\n"
" [`SerializerConfig`](#SerializerConfig).\n"
).
-spec to_string(agnostic@vdom@vnode:element(any())) -> binary().
to_string(Node) ->
_pipe = Node,
_pipe@1 = to_string_tree(_pipe, <<""/utf8>>),
unicode:characters_to_binary(_pipe@1).
-file("src/agnostic/platform/dom.gleam", 179).
?DOC(
" Returns a [`Serializer`](../platform.html#Serializer) that converts elements\n"
" to HTML strings using the default HTML void elements. This is the standard\n"
" serializer for server-side rendering.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import agnostic/platform\n"
" import agnostic/platform/dom\n"
"\n"
" let p = platform.headless(dom.serializer())\n"
" ```\n"
).
-spec serializer() -> agnostic@serializer:serializer(any()).
serializer() ->
{serializer, fun to_string/1, fun gleam@function:identity/1}.
-file("src/agnostic/platform/dom.gleam", 300).
?DOC(
" Check if a tag is a void element in this config.\n"
" Only applies to elements in the default HTML namespace (empty string).\n"
).
-spec is_void(serializer_config(), binary(), binary()) -> boolean().
is_void(Config, Tag, Namespace) ->
case Namespace of
<<""/utf8>> ->
gleam@set:contains(erlang:element(2, Config), Tag);
_ ->
false
end.
-file("src/agnostic/platform/dom.gleam", 313).
?DOC(" Check if a tag is a self-closing tag in this config.\n").
-spec is_self_closing(serializer_config(), binary()) -> boolean().
is_self_closing(Config, Tag) ->
gleam@set:contains(erlang:element(3, Config), Tag).
-file("src/agnostic/platform/dom.gleam", 443).
-spec config_children_to_string_tree(
gleam@string_tree:string_tree(),
serializer_config(),
list(agnostic@vdom@vnode:element(any())),
binary()
) -> gleam@string_tree:string_tree().
config_children_to_string_tree(Html, Config, Children, Namespace) ->
gleam@list:fold(
Children,
Html,
fun(Html@1, Child) ->
gleam_stdlib:iodata_append(
Html@1,
serialize_tree(Config, Child, Namespace)
)
end
).
-file("src/agnostic/platform/dom.gleam", 373).
?DOC(
" Convert a Lustre `Element` to an HTML `StringTree` using the given HTML config.\n"
" This is more efficient than `serialize` when the output will be written to a\n"
" socket or file.\n"
).
-spec serialize_tree(
serializer_config(),
agnostic@vdom@vnode:element(any()),
binary()
) -> gleam@string_tree:string_tree().
serialize_tree(Config, Node, Parent_namespace) ->
case Node of
{text, _, _, <<""/utf8>>} ->
gleam@string_tree:new();
{text, _, _, Content} ->
gleam_stdlib:identity(houdini:escape(Content));
{element, _, Key, Namespace, Tag, Attributes, Children, _} ->
Html = gleam_stdlib:identity(<<"<"/utf8, Tag/binary>>),
Attributes@1 = attrs_to_string_tree(
Key,
Namespace,
Parent_namespace,
Attributes
),
case {is_self_closing(Config, Tag), is_void(Config, Tag, Namespace)} of
{true, _} ->
_pipe = Html,
_pipe@1 = gleam_stdlib:iodata_append(_pipe, Attributes@1),
gleam@string_tree:append(_pipe@1, <<"/>"/utf8>>);
{_, true} ->
_pipe@2 = Html,
_pipe@3 = gleam_stdlib:iodata_append(_pipe@2, Attributes@1),
gleam@string_tree:append(_pipe@3, <<">"/utf8>>);
{false, false} ->
_pipe@4 = Html,
_pipe@5 = gleam_stdlib:iodata_append(_pipe@4, Attributes@1),
_pipe@6 = gleam@string_tree:append(_pipe@5, <<">"/utf8>>),
_pipe@7 = config_children_to_string_tree(
_pipe@6,
Config,
Children,
Namespace
),
gleam@string_tree:append(
_pipe@7,
<<<<"</"/utf8, Tag/binary>>/binary, ">"/utf8>>
)
end;
{raw_container,
_,
Key@1,
Namespace@1,
Tag@1,
Attributes@2,
Content@1,
_} ->
Html@1 = gleam_stdlib:identity(<<"<"/utf8, Tag@1/binary>>),
Attributes@3 = attrs_to_string_tree(
Key@1,
Namespace@1,
Parent_namespace,
Attributes@2
),
_pipe@8 = Html@1,
_pipe@9 = gleam_stdlib:iodata_append(_pipe@8, Attributes@3),
_pipe@10 = gleam@string_tree:append(_pipe@9, <<">"/utf8>>),
_pipe@11 = gleam@string_tree:append(
_pipe@10,
gleam@function:identity(Content@1)
),
gleam@string_tree:append(
_pipe@11,
<<<<"</"/utf8, Tag@1/binary>>/binary, ">"/utf8>>
);
{raw_node, _, _, Content@2, _} ->
gleam_stdlib:identity(gleam@function:identity(Content@2));
{fragment, _, Key@2, Children@1, _} ->
_pipe@12 = marker_comment(<<"lustre:fragment"/utf8>>, Key@2),
_pipe@13 = config_children_to_string_tree(
_pipe@12,
Config,
Children@1,
Parent_namespace
),
gleam_stdlib:iodata_append(
_pipe@13,
marker_comment(<<"/lustre:fragment"/utf8>>, <<""/utf8>>)
);
{map, _, Key@3, _, Child} ->
_pipe@14 = marker_comment(<<"lustre:map"/utf8>>, Key@3),
gleam_stdlib:iodata_append(
_pipe@14,
serialize_tree(Config, Child, Parent_namespace)
);
{memo, _, Key@4, _, View} ->
_pipe@15 = marker_comment(<<"lustre:memo"/utf8>>, Key@4),
gleam_stdlib:iodata_append(
_pipe@15,
serialize_tree(Config, View(), Parent_namespace)
)
end.
-file("src/agnostic/platform/dom.gleam", 363).
?DOC(
" Convert a Lustre `Element` to an HTML string using the given HTML config.\n"
" This is _not_ pretty-printed, so there are no newlines or indentation.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let config = dom.serializer_config()\n"
" |> dom.with_void(\"custom-void\")\n"
" dom.serialize(config, element(\"custom-void\", [], []))\n"
" // -> \"<custom-void>\"\n"
" ```\n"
).
-spec serialize(serializer_config(), agnostic@vdom@vnode:element(any())) -> binary().
serialize(Config, Node) ->
_pipe = Node,
_pipe@1 = serialize_tree(Config, _pipe, <<""/utf8>>),
unicode:characters_to_binary(_pipe@1).
-file("src/agnostic/platform/dom.gleam", 199).
?DOC(
" Converts an [`SerializerConfig`](#SerializerConfig) to a\n"
" [`Serializer`](../platform.html#Serializer) function.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import agnostic/platform\n"
" import agnostic/platform/dom\n"
"\n"
" let s = dom.serializer_config()\n"
" |> dom.with_void(\"custom-void\")\n"
" |> dom.to_serializer()\n"
"\n"
" let p = platform.headless(s)\n"
" ```\n"
).
-spec to_serializer(serializer_config()) -> agnostic@serializer:serializer(any()).
to_serializer(Config) ->
{serializer,
fun(Element) -> serialize(Config, Element) end,
fun gleam@function:identity/1}.
-file("src/agnostic/platform/dom.gleam", 225).
?DOC(
" Create an HTML config with default void elements and no self-closing tags.\n"
" This is the standard configuration for rendering HTML.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let config = dom.serializer_config()\n"
" |> dom.with_void(\"custom-void\")\n"
" ```\n"
).
-spec serializer_config() -> serializer_config().
serializer_config() ->
{serializer_config,
gleam@set:from_list(
[<<"area"/utf8>>,
<<"base"/utf8>>,
<<"br"/utf8>>,
<<"col"/utf8>>,
<<"embed"/utf8>>,
<<"hr"/utf8>>,
<<"img"/utf8>>,
<<"input"/utf8>>,
<<"link"/utf8>>,
<<"meta"/utf8>>,
<<"param"/utf8>>,
<<"source"/utf8>>,
<<"track"/utf8>>,
<<"wbr"/utf8>>]
),
gleam@set:new()}.
-file("src/agnostic/platform/dom.gleam", 242).
?DOC(
" Create an empty HTML config with no void elements and no self-closing tags.\n"
" Use this as a starting point for custom configurations.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let config = dom.empty_serializer_config()\n"
" |> dom.with_self_closing_tags([\"path\", \"circle\", \"rect\"])\n"
" ```\n"
).
-spec empty_serializer_config() -> serializer_config().
empty_serializer_config() ->
{serializer_config, gleam@set:new(), gleam@set:new()}.
-file("src/agnostic/platform/dom.gleam", 249).
?DOC(
" Add a void element to the config. Void elements render without a closing\n"
" tag (e.g., `<br>` instead of `<br></br>`).\n"
).
-spec with_void(serializer_config(), binary()) -> serializer_config().
with_void(Config, Tag) ->
{serializer_config,
gleam@set:insert(erlang:element(2, Config), Tag),
erlang:element(3, Config)}.
-file("src/agnostic/platform/dom.gleam", 258).
?DOC(" Add multiple void elements to the config.\n").
-spec with_void_elements(serializer_config(), list(binary())) -> serializer_config().
with_void_elements(Config, Tags) ->
{serializer_config,
gleam@list:fold(
Tags,
erlang:element(2, Config),
fun(Acc, Tag) -> gleam@set:insert(Acc, Tag) end
),
erlang:element(3, Config)}.
-file("src/agnostic/platform/dom.gleam", 273).
?DOC(
" Add a self-closing tag to the config. Self-closing tags render with\n"
" XML-style self-closing syntax (e.g., `<path />` instead of `<path></path>`).\n"
).
-spec with_self_closing(serializer_config(), binary()) -> serializer_config().
with_self_closing(Config, Tag) ->
{serializer_config,
erlang:element(2, Config),
gleam@set:insert(erlang:element(3, Config), Tag)}.
-file("src/agnostic/platform/dom.gleam", 285).
?DOC(" Add multiple self-closing tags to the config.\n").
-spec with_self_closing_tags(serializer_config(), list(binary())) -> serializer_config().
with_self_closing_tags(Config, Tags) ->
{serializer_config,
erlang:element(2, Config),
gleam@list:fold(
Tags,
erlang:element(3, Config),
fun(Acc, Tag) -> gleam@set:insert(Acc, Tag) end
)}.
-file("src/agnostic/platform/dom.gleam", 576).
-spec get_document_type(agnostic@vdom@vnode:element(any())) -> document_type().
get_document_type(El) ->
case El of
{element, _, _, _, <<"html"/utf8>>, _, _, _} ->
html;
{raw_container, _, _, _, <<"html"/utf8>>, _, _, _} ->
html;
{element, _, _, _, <<"head"/utf8>>, _, _, _} ->
head_only;
{raw_container, _, _, _, <<"head"/utf8>>, _, _, _} ->
head_only;
{element, _, _, _, <<"body"/utf8>>, _, _, _} ->
body_only;
{raw_container, _, _, _, <<"body"/utf8>>, _, _, _} ->
body_only;
{map, _, _, _, Child} ->
get_document_type(Child);
{memo, _, _, _, View} ->
get_document_type(View());
{fragment, _, _, [Child@1], _} ->
get_document_type(Child@1);
{fragment, _, _, [Head, Body], _} ->
case {get_document_type(Head), get_document_type(Body)} of
{head_only, body_only} ->
head_and_body;
{_, _} ->
other
end;
_ ->
other
end.
-file("src/agnostic/platform/dom.gleam", 593).
-spec wrap_document(agnostic@vdom@vnode:element(IRR)) -> agnostic@vdom@vnode:element(IRR).
wrap_document(El) ->
case get_document_type(El) of
html ->
El;
head_only ->
agnostic@vdom@vnode:element(
<<""/utf8>>,
<<""/utf8>>,
<<"html"/utf8>>,
[],
[El],
agnostic@vdom@vnode:empty_keyed_children()
);
body_only ->
agnostic@vdom@vnode:element(
<<""/utf8>>,
<<""/utf8>>,
<<"html"/utf8>>,
[],
[El],
agnostic@vdom@vnode:empty_keyed_children()
);
head_and_body ->
agnostic@vdom@vnode:element(
<<""/utf8>>,
<<""/utf8>>,
<<"html"/utf8>>,
[],
[El],
agnostic@vdom@vnode:empty_keyed_children()
);
other ->
agnostic@vdom@vnode:element(
<<""/utf8>>,
<<""/utf8>>,
<<"html"/utf8>>,
[],
[agnostic@vdom@vnode:element(
<<""/utf8>>,
<<""/utf8>>,
<<"body"/utf8>>,
[],
[El],
agnostic@vdom@vnode:empty_keyed_children()
)],
agnostic@vdom@vnode:empty_keyed_children()
)
end.
-file("src/agnostic/platform/dom.gleam", 552).
?DOC(
" Converts an element to a string like [`to_string`](#to_string), but prepends\n"
" a `<!doctype html>` declaration to the string. This is useful for rendering\n"
" complete HTML documents.\n"
"\n"
" If the provided element is not an `html` element, it will be wrapped in both\n"
" a `html` and `body` element.\n"
).
-spec to_document_string(agnostic@vdom@vnode:element(any())) -> binary().
to_document_string(El) ->
<<"<!doctype html>\n"/utf8, (to_string(wrap_document(El)))/binary>>.
-file("src/agnostic/platform/dom.gleam", 563).
?DOC(
" Converts a Lustre `Element` to an HTML `StringTree`, prepending a\n"
" `<!doctype html>` declaration. This is useful for rendering complete\n"
" HTML documents efficiently.\n"
"\n"
" If the provided element is not an `html` element, it will be wrapped in both\n"
" a `html` and `body` element.\n"
).
-spec to_document_string_tree(agnostic@vdom@vnode:element(any())) -> gleam@string_tree:string_tree().
to_document_string_tree(El) ->
_pipe = gleam_stdlib:identity(<<"<!doctype html>\n"/utf8>>),
gleam_stdlib:iodata_append(
_pipe,
to_string_tree(wrap_document(El), <<""/utf8>>)
).
-file("src/agnostic/platform/dom.gleam", 784).
-spec children_to_snapshot_builder(
gleam@string_tree:string_tree(),
list(agnostic@vdom@vnode:element(any())),
boolean(),
boolean(),
binary(),
integer()
) -> gleam@string_tree:string_tree().
children_to_snapshot_builder(Html, Children, Raw, Debug, Namespace, Indent) ->
case Children of
[{text, _, _, A}, {text, _, _, B} | Rest] ->
children_to_snapshot_builder(
Html,
[{text, 2, <<""/utf8>>, <<A/binary, B/binary>>} | Rest],
Raw,
Debug,
Namespace,
Indent
);
[Child | Rest@1] ->
_pipe = Child,
_pipe@1 = do_to_snapshot_builder(
_pipe,
Raw,
Debug,
Namespace,
Indent
),
_pipe@2 = gleam@string_tree:append(_pipe@1, <<"\n"/utf8>>),
_pipe@3 = gleam@string_tree:prepend_tree(_pipe@2, Html),
children_to_snapshot_builder(
_pipe@3,
Rest@1,
Raw,
Debug,
Namespace,
Indent
);
[] ->
Html
end.
-file("src/agnostic/platform/dom.gleam", 643).
-spec do_to_snapshot_builder(
agnostic@vdom@vnode:element(any()),
boolean(),
boolean(),
binary(),
integer()
) -> gleam@string_tree:string_tree().
do_to_snapshot_builder(Node, Raw, Debug, Parent_namespace, Indent) ->
Spaces = gleam@string:repeat(<<" "/utf8>>, Indent),
case Node of
{text, _, _, <<""/utf8>>} ->
gleam@string_tree:new();
{text, _, _, Content} when Raw ->
gleam_stdlib:identity([Spaces, Content]);
{text, _, _, Content@1} ->
gleam_stdlib:identity([Spaces, houdini:escape(Content@1)]);
{element, _, Key, Namespace, Tag, Attributes, Children, _} ->
Html = gleam_stdlib:identity(<<"<"/utf8, Tag/binary>>),
Attributes@1 = attrs_to_string_tree(
Key,
Namespace,
Parent_namespace,
Attributes
),
case {is_void_element(Tag, Namespace), Children} of
{true, _} ->
_pipe = Html,
_pipe@1 = gleam@string_tree:prepend(_pipe, Spaces),
_pipe@2 = gleam_stdlib:iodata_append(_pipe@1, Attributes@1),
gleam@string_tree:append(_pipe@2, <<">"/utf8>>);
{false, []} ->
_pipe@3 = Html,
_pipe@4 = gleam@string_tree:prepend(_pipe@3, Spaces),
_pipe@5 = gleam_stdlib:iodata_append(_pipe@4, Attributes@1),
_pipe@6 = gleam@string_tree:append(_pipe@5, <<">"/utf8>>),
gleam@string_tree:append(
_pipe@6,
<<<<"</"/utf8, Tag/binary>>/binary, ">"/utf8>>
);
{false, _} ->
_pipe@7 = Html,
_pipe@8 = gleam@string_tree:prepend(_pipe@7, Spaces),
_pipe@9 = gleam_stdlib:iodata_append(_pipe@8, Attributes@1),
_pipe@10 = gleam@string_tree:append(_pipe@9, <<">\n"/utf8>>),
_pipe@11 = children_to_snapshot_builder(
_pipe@10,
Children,
Raw,
Debug,
Namespace,
Indent + 1
),
_pipe@12 = gleam@string_tree:append(_pipe@11, Spaces),
gleam@string_tree:append(
_pipe@12,
<<<<"</"/utf8, Tag/binary>>/binary, ">"/utf8>>
)
end;
{raw_container,
_,
Key@1,
Namespace@1,
Tag@1,
Attributes@2,
Content@2,
_} ->
Html@1 = gleam_stdlib:identity(<<"<"/utf8, Tag@1/binary>>),
Attributes@3 = attrs_to_string_tree(
Key@1,
Namespace@1,
Parent_namespace,
Attributes@2
),
_pipe@13 = Html@1,
_pipe@14 = gleam@string_tree:prepend(_pipe@13, Spaces),
_pipe@15 = gleam_stdlib:iodata_append(_pipe@14, Attributes@3),
_pipe@16 = gleam@string_tree:append(_pipe@15, <<">"/utf8>>),
_pipe@17 = gleam@string_tree:append(
_pipe@16,
gleam@function:identity(Content@2)
),
gleam@string_tree:append(
_pipe@17,
<<<<"</"/utf8, Tag@1/binary>>/binary, ">"/utf8>>
);
{raw_node, _, _, Content@3, _} ->
_pipe@18 = gleam_stdlib:identity(gleam@function:identity(Content@3)),
gleam@string_tree:prepend(_pipe@18, Spaces);
{fragment, _, Key@2, Children@1, _} when Debug ->
_pipe@19 = marker_comment(<<"lustre:fragment"/utf8>>, Key@2),
_pipe@20 = gleam@string_tree:prepend(_pipe@19, Spaces),
_pipe@21 = gleam@string_tree:append(_pipe@20, <<"\n"/utf8>>),
_pipe@22 = children_to_snapshot_builder(
_pipe@21,
Children@1,
Raw,
Debug,
Parent_namespace,
Indent + 1
),
_pipe@23 = gleam@string_tree:append(_pipe@22, Spaces),
gleam_stdlib:iodata_append(
_pipe@23,
marker_comment(<<"/lustre:fragment"/utf8>>, <<""/utf8>>)
);
{fragment, _, _, Children@2, _} ->
children_to_snapshot_builder(
gleam@string_tree:new(),
Children@2,
Raw,
Debug,
Parent_namespace,
Indent
);
{map, _, Key@3, _, Child} when Debug ->
_pipe@24 = marker_comment(<<"lustre:map"/utf8>>, Key@3),
_pipe@25 = gleam@string_tree:prepend(_pipe@24, Spaces),
_pipe@26 = gleam@string_tree:append(_pipe@25, <<"\n"/utf8>>),
gleam_stdlib:iodata_append(
_pipe@26,
do_to_snapshot_builder(
Child,
Raw,
Debug,
Parent_namespace,
Indent + 1
)
);
{map, _, _, _, Child@1} ->
do_to_snapshot_builder(
Child@1,
Raw,
Debug,
Parent_namespace,
Indent
);
{memo, _, Key@4, _, View} when Debug ->
_pipe@27 = marker_comment(<<"lustre:memo"/utf8>>, Key@4),
_pipe@28 = gleam@string_tree:prepend(_pipe@27, Spaces),
_pipe@29 = gleam@string_tree:append(_pipe@28, <<"\n"/utf8>>),
gleam_stdlib:iodata_append(
_pipe@29,
do_to_snapshot_builder(
View(),
Raw,
Debug,
Parent_namespace,
Indent + 1
)
);
{memo, _, _, _, View@1} ->
do_to_snapshot_builder(
View@1(),
Raw,
Debug,
Parent_namespace,
Indent
)
end.
-file("src/agnostic/platform/dom.gleam", 638).
?DOC(
" Convert an element to a snapshot string for testing. When `debug` is `True`,\n"
" fragment and map markers are included in the output.\n"
).
-spec to_snapshot(agnostic@vdom@vnode:element(any()), boolean()) -> binary().
to_snapshot(Node, Debug) ->
_pipe = do_to_snapshot_builder(Node, false, Debug, <<""/utf8>>, 0),
unicode:characters_to_binary(_pipe).
-file("src/agnostic/platform/dom.gleam", 631).
?DOC(
" Converts a Lustre `Element` to a human-readable string by inserting new lines\n"
" and indentation where appropriate. This is useful for debugging and testing.\n"
).
-spec to_readable_string(agnostic@vdom@vnode:element(any())) -> binary().
to_readable_string(El) ->
to_snapshot(El, false).