-module(lustre@component). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -define(FILEPATH, "src/lustre/component.gleam"). -export([new/1, on_attribute_change/2, on_property_change/2, on_context_change/2, form_associated/0, on_form_autofill/1, on_form_reset/1, on_form_restore/1, open_shadow_root/1, adopt_styles/1, delegates_focus/1, to_server_component_config/1, default_slot/2, named_slot/3, part/1, parts/1, exportparts/1, slot/1, set_form_value/1, clear_form_value/0, set_pseudo_state/1, remove_pseudo_state/1]). -export_type([config/1, option/1]). -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( " Lustre's component system is built on top of the Custom Elements API and\n" " the Shadow DOM API. This module helps you configure new components and\n" " interact with existing ones.\n" "\n" " While it's not required, understanding the spec and how it works will help\n" " you get the most out of Lustre's component system. The following resources\n" " are a great place to start:\n" "\n" " - https://developer.mozilla.org/en-US/docs/Web/Web_Components\n" "\n" " - https://css-tricks.com/web-components-demystified/\n" "\n" " - https://github.com/web-padawan/awesome-web-components\n" "\n" " ## Examples\n" "\n" " We have a small number of examples showing how to set up and use components\n" " that are a great place to see some code:\n" "\n" " - [`Basic setup`](https://github.com/lustre-labs/lustre/tree/main/examples/05-components/01-basic-setup)\n" "\n" " - [`Custom attributes and events`](https://github.com/lustre-labs/lustre/tree/main/examples/05-components/02-attributes-and-events)\n" "\n" " - [`Slots`](https://github.com/lustre-labs/lustre/tree/main/examples/05-components/03-slots)\n" "\n" " This list of examples is likely to grow over time, so be sure to check back\n" " every now and then to see what's new!\n" "\n" " ## Getting help\n" "\n" " If you're having trouble with Lustre or not sure what the right way to do\n" " something is, the best place to get help is the [Gleam Discord server](https://discord.gg/Fm8Pwmy).\n" " You could also open an issue on the [Lustre GitHub repository](https://github.com/lustre-labs/lustre/issues).\n" "\n" ). -opaque config(SFD) :: {config, boolean(), boolean(), boolean(), list({binary(), fun((binary()) -> {ok, SFD} | {error, nil})}), list({binary(), gleam@dynamic@decode:decoder(SFD)}), list({binary(), gleam@dynamic@decode:decoder(SFD)}), boolean(), gleam@option:option(fun((binary()) -> SFD)), gleam@option:option(SFD), gleam@option:option(fun((binary()) -> SFD))}. -opaque option(SFE) :: {option, fun((config(SFE)) -> config(SFE))}. -file("src/lustre/component.gleam", 133). ?DOC(false). -spec new(list(option(SFF))) -> config(SFF). new(Options) -> Init = {config, true, true, false, [], [], [], false, none, none, none}, gleam@list:fold( Options, Init, fun(Config, Option) -> (erlang:element(2, Option))(Config) end ). -file("src/lustre/component.gleam", 167). ?DOC( " Register a decoder to run whenever the named attribute changes. Attributes\n" " can be set in Lustre using the [`attribute`](./attribute.html#attribute)\n" " function, set directly on the component's HTML tag, or in JavaScript using\n" " the [`setAttribute`](https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute)\n" " method.\n" "\n" " Attributes are always strings, but your decoder is responsible for decoding\n" " the string into a message that your component can understand.\n" ). -spec on_attribute_change(binary(), fun((binary()) -> {ok, SFJ} | {error, nil})) -> option(SFJ). on_attribute_change(Name, Decoder) -> {option, fun(Config) -> Attributes = [{Name, Decoder} | erlang:element(5, Config)], _record = Config, {config, erlang:element(2, _record), erlang:element(3, _record), erlang:element(4, _record), Attributes, erlang:element(6, _record), erlang:element(7, _record), erlang:element(8, _record), erlang:element(9, _record), erlang:element(10, _record), erlang:element(11, _record)} end}. -file("src/lustre/component.gleam", 185). ?DOC( " Register decoder to run whenever the given property is set on the component.\n" " Properties can be set in Lustre using the [`property`](./attribute.html#property)\n" " function or in JavaScript by setting a property directly on the component\n" " object.\n" "\n" " Properties can be any JavaScript object. For server components, properties\n" " will be any _JSON-serialisable_ value.\n" ). -spec on_property_change(binary(), gleam@dynamic@decode:decoder(SFN)) -> option(SFN). on_property_change(Name, Decoder) -> {option, fun(Config) -> Properties = [{Name, Decoder} | erlang:element(6, Config)], _record = Config, {config, erlang:element(2, _record), erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record), Properties, erlang:element(7, _record), erlang:element(8, _record), erlang:element(9, _record), erlang:element(10, _record), erlang:element(11, _record)} end}. -file("src/lustre/component.gleam", 201). ?DOC( " Register a decoder to run whenever a parent component or application\n" " [provides](./effect.html#provide) a new context value for the given `key`.\n" " Contexts are a powerful feature that allow parents to inject data into\n" " child components without knowledge of the DOM structurre, making them great\n" " for advanced use-cases like design systems and flexible component hierarchies.\n" "\n" " Contexts can be any JavaScript object. For server components, contexts will\n" " be any _JSON-serialisable_ value.\n" ). -spec on_context_change(binary(), gleam@dynamic@decode:decoder(SFQ)) -> option(SFQ). on_context_change(Key, Decoder) -> {option, fun(Config) -> Contexts = [{Key, Decoder} | erlang:element(7, Config)], _record = Config, {config, erlang:element(2, _record), erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record), erlang:element(6, _record), Contexts, erlang:element(8, _record), erlang:element(9, _record), erlang:element(10, _record), erlang:element(11, _record)} end}. -file("src/lustre/component.gleam", 216). ?DOC( " Mark a component as \"form-associated\". This lets your component participate\n" " in form submission and respond to additional form-specific events such as\n" " the form being reset or the browser autofilling this component's value.\n" "\n" " > **Note**: form-associated components are not supported in server components\n" " > for both technical and ideological reasons. If you'd like a component that\n" " > participates in form submission, you should use a client component!\n" ). -spec form_associated() -> option(any()). form_associated() -> {option, fun(Config) -> _record = Config, {config, erlang:element(2, _record), erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record), erlang:element(6, _record), erlang:element(7, _record), true, erlang:element(9, _record), erlang:element(10, _record), erlang:element(11, _record)} end}. -file("src/lustre/component.gleam", 230). ?DOC( " Register a callback that runs when the browser autofills this\n" " [form-associated](#form_associated) component's `\"value\"` attribute. The\n" " callback should convert the autofilled value into a message that you handle\n" " in your `update` function.\n" "\n" " > **Note**: server components cannot participate in form submission and configuring\n" " > this option will do nothing.\n" ). -spec on_form_autofill(fun((binary()) -> SFV)) -> option(SFV). on_form_autofill(Handler) -> {option, fun(Config) -> _record = Config, {config, erlang:element(2, _record), erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record), erlang:element(6, _record), erlang:element(7, _record), true, {some, Handler}, erlang:element(10, _record), erlang:element(11, _record)} end}. -file("src/lustre/component.gleam", 242). ?DOC( " Set a message to be dispatched whenever a form containing this\n" " [form-associated](#form_associated) component is reset.\n" "\n" " > **Note**: server components cannot participate in form submission and configuring\n" " > this option will do nothing.\n" ). -spec on_form_reset(SFX) -> option(SFX). on_form_reset(Msg) -> {option, fun(Config) -> _record = Config, {config, erlang:element(2, _record), erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record), erlang:element(6, _record), erlang:element(7, _record), true, erlang:element(9, _record), {some, Msg}, erlang:element(11, _record)} end}. -file("src/lustre/component.gleam", 255). ?DOC( " Set a callback that runs when the browser restores this\n" " [form-associated](#form_associated) component's `\"value\"` attribute. This is\n" " often triggered when the user navigates back or forward in their history.\n" "\n" " > **Note**: server components cannot participate in form submission and configuring\n" " > this option will do nothing.\n" ). -spec on_form_restore(fun((binary()) -> SFZ)) -> option(SFZ). on_form_restore(Handler) -> {option, fun(Config) -> _record = Config, {config, erlang:element(2, _record), erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record), erlang:element(6, _record), erlang:element(7, _record), true, erlang:element(9, _record), erlang:element(10, _record), {some, Handler}} end}. -file("src/lustre/component.gleam", 269). ?DOC( " Configure whether a component's [Shadow Root](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot)\n" " is open or closed. A closed shadow root means the elements rendered inside\n" " the component are not accessible from JavaScript outside the component.\n" "\n" " By default a component's shadow root is **open**. You may want to configure\n" " this option manually if you intend to build a component for use outside of\n" " Lustre.\n" ). -spec open_shadow_root(boolean()) -> option(any()). open_shadow_root(Open) -> {option, fun(Config) -> _record = Config, {config, Open, erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record), erlang:element(6, _record), erlang:element(7, _record), erlang:element(8, _record), erlang:element(9, _record), erlang:element(10, _record), erlang:element(11, _record)} end}. -file("src/lustre/component.gleam", 289). ?DOC( " Configure whether a component should attempt to adopt stylesheets from\n" " its parent document. Components in Lustre use the shadow DOM to unlock native\n" " web component features like slots, but this means elements rendered inside a\n" " component are isolated from the document's styles.\n" "\n" " To get around this, Lustre can attempt to adopt all stylesheets from the\n" " parent document when the component is first created; meaning in many cases\n" " you can use the same CSS to style your components as you do the rest of your\n" " application.\n" "\n" " By default, this option is **enabled**. You may want to disable this option\n" " if you are building a component for use outside of Lustre and do not want\n" " document styles to interfere with your component's styling\n" ). -spec adopt_styles(boolean()) -> option(any()). adopt_styles(Adopt) -> {option, fun(Config) -> _record = Config, {config, erlang:element(2, _record), Adopt, erlang:element(4, _record), erlang:element(5, _record), erlang:element(6, _record), erlang:element(7, _record), erlang:element(8, _record), erlang:element(9, _record), erlang:element(10, _record), erlang:element(11, _record)} end}. -file("src/lustre/component.gleam", 311). ?DOC( " Indicates whether or not this component should delegate focus to its children.\n" " When set to `True`, a number of focus-related features are enabled:\n" "\n" " - Clicking on any non-interactive part of the component will automatically\n" " focus the first focusable child element.\n" "\n" " - The component can receive focus through the `.focus()` method or the\n" " `autofocus` attribute, and it will automatically focus the first\n" " focusable child element.\n" "\n" " - The component receives the `:focus` CSS pseudo-class when any of its\n" " focusable children have focus.\n" "\n" " By default this option is **disabled**. You may want to enable this option\n" " when creating complex interactive widgets.\n" ). -spec delegates_focus(boolean()) -> option(any()). delegates_focus(Delegates) -> {option, fun(Config) -> _record = Config, {config, erlang:element(2, _record), erlang:element(3, _record), Delegates, erlang:element(5, _record), erlang:element(6, _record), erlang:element(7, _record), erlang:element(8, _record), erlang:element(9, _record), erlang:element(10, _record), erlang:element(11, _record)} end}. -file("src/lustre/component.gleam", 330). ?DOC(false). -spec to_server_component_config(config(SGH)) -> lustre@runtime@server@runtime:config(SGH). to_server_component_config(Config) -> {config, erlang:element(2, Config), erlang:element(3, Config), maps:from_list(lists:reverse(erlang:element(5, Config))), maps:from_list(lists:reverse(erlang:element(6, Config))), maps:from_list(lists:reverse(erlang:element(7, Config)))}. -file("src/lustre/component.gleam", 354). ?DOC( " Create a default slot for a component. Any elements rendered as children of\n" " the component will be placed inside the default slot unless explicitly\n" " redirected using the [`slot`](#slot) attribute.\n" "\n" " If no children are placed into the slot, the `fallback` elements will be\n" " rendered instead.\n" "\n" " To learn more about Shadow DOM and slots, see this excellent guide:\n" "\n" " https://javascript.info/slots-composition\n" ). -spec default_slot( list(lustre@vdom@vattr:attribute(SGK)), list(lustre@vdom@vnode:element(SGK)) ) -> lustre@vdom@vnode:element(SGK). default_slot(Attributes, Fallback) -> lustre@element@html:slot(Attributes, Fallback). -file("src/lustre/component.gleam", 372). ?DOC( " Create a named slot for a component. Any elements rendered as children of\n" " the component with a [`slot`](#slot) attribute matching the `name` will be\n" " rendered inside this slot.\n" "\n" " If no children are placed into the slot, the `fallback` elements will be\n" " rendered instead.\n" "\n" " To learn more about Shadow DOM and slots, see this excellent guide:\n" "\n" " https://javascript.info/slots-composition\n" ). -spec named_slot( binary(), list(lustre@vdom@vattr:attribute(SGQ)), list(lustre@vdom@vnode:element(SGQ)) ) -> lustre@vdom@vnode:element(SGQ). named_slot(Name, Attributes, Fallback) -> lustre@element@html:slot( [lustre@attribute:attribute(<<"name"/utf8>>, Name) | Attributes], Fallback ). -file("src/lustre/component.gleam", 423). ?DOC( " Lustre's component system is built on top the Custom Elements API and the\n" " Shadow DOM API. A component's `view` function is rendered inside a shadow\n" " root, which means the component's HTML is isolated from the rest of the\n" " document.\n" "\n" " This can make it difficult to style components from CSS outside the component.\n" " To help with this, the `part` attribute lets you expose parts of your component\n" " by name to be styled by external CSS.\n" "\n" " For example, if the `view` function for a component called `\"my-component`\"\n" " looks like this:\n" "\n" " ```gleam\n" " import gleam/int\n" " import lustre/component\n" " import lustre/element/html\n" "\n" " fn view(model) {\n" " html.div([], [\n" " html.button([], [html.text(\"-\")]),\n" " html.p([component.part(\"count\")], [html.text(int.to_string(model.count))]),\n" " html.button([], [html.text(\"+\")]),\n" " ])\n" " }\n" " ```\n" "\n" " Then the following CSS in the **parent** document can be used to style the\n" " `
` element:\n"
"\n"
" ```css\n"
" my-component::part(count) {\n"
" color: red;\n"
" }\n"
" ```\n"
"\n"
" To learn more about the CSS Shadow Parts specification, see:\n"
"\n"
" - https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/part\n"
"\n"
" - https://developer.mozilla.org/en-US/docs/Web/CSS/::part\n"
).
-spec part(binary()) -> lustre@vdom@vattr:attribute(any()).
part(Name) ->
lustre@attribute:attribute(<<"part"/utf8>>, Name).
-file("src/lustre/component.gleam", 452).
-spec do_parts(list({binary(), boolean()}), binary()) -> binary().
do_parts(Names, Part) ->
case Names of
[] ->
Part;
[{Name, true} | Rest] ->
<<<<< ` element nested\n"
" deep inside the `