diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | gleam.toml | 32 | ||||
-rw-r--r-- | src/lustre/attribute.gleam | 27 | ||||
-rw-r--r-- | src/lustre/effect.gleam | 23 | ||||
-rw-r--r-- | src/lustre/element.gleam | 72 | ||||
-rw-r--r-- | src/lustre/element/html.gleam | 235 | ||||
-rw-r--r-- | src/lustre/element/svg.gleam | 3 | ||||
-rw-r--r-- | src/lustre/event.gleam | 45 | ||||
-rw-r--r-- | src/lustre/internals/runtime.gleam | 39 |
9 files changed, 262 insertions, 216 deletions
@@ -19,10 +19,12 @@ <div align="center"> <h3> + <!-- <a href="https://lustre.build"> Website </a> <span> | </span> + --> <a href="https://github.com/lustre-labs/lustre/docs/guide/01-quickstart.md"> Quickstart </a> @@ -7,7 +7,7 @@ repository = { type = "github", user = "lustre-labs", repo = "lustre" } licences = ["MIT"] links = [ - { title = "Website", href = "https://lustre.build" }, + # { title = "Website", href = "https://lustre.build" }, { title = "Examples", href = "https://github.com/lustre-labs/lustre/tree/main/examples" }, { title = "Sponsor", href = "https://github.com/sponsors/hayleigh-dot-dev" }, ] @@ -21,22 +21,22 @@ internal_modules = [ [documentation] pages = [ - { title = "CLI reference", path = "#", source = "" }, - { title = " ", path = "#", source = "" }, + # { title = "CLI reference", path = "#", source = "" }, + # { title = " ", path = "#", source = "" }, { title = "Quickstart guide", path = "guide/01-quickstart.html", source = "./docs/guide/01-quickstart.md" }, - { title = "Managing state", path = "#", source = "" }, - { title = "Side effects", path = "#", source = "" }, - { title = "Server-side rendering", path = "#", source = "" }, - { title = "Components", path = "#", source = "" }, - { title = "Server components", path = "#", source = "" }, - { title = " ", path = "#", source = "" }, - { title = "Using with Wisp", path = "#", source = "" }, - { title = "Using with Glen", path = "#", source = "" }, - { title = "Using with Mist", path = "#", source = "" }, - { title = " ", path = "#", source = "" }, - { title = "For Elm developers", path = "#", source = "" }, - { title = "For React developers", path = "#", source = "" }, - { title = "For LiveView developers", path = "#", source = "" }, + # { title = "Managing state", path = "#", source = "" }, + # { title = "Side effects", path = "#", source = "" }, + # { title = "Server-side rendering", path = "#", source = "" }, + # { title = "Components", path = "#", source = "" }, + # { title = "Server components", path = "#", source = "" }, + # { title = " ", path = "#", source = "" }, + # { title = "Using with Wisp", path = "#", source = "" }, + # { title = "Using with Glen", path = "#", source = "" }, + # { title = "Using with Mist", path = "#", source = "" }, + # { title = " ", path = "#", source = "" }, + # { title = "For Elm developers", path = "#", source = "" }, + # { title = "For React developers", path = "#", source = "" }, + # { title = "For LiveView developers", path = "#", source = "" }, ] [dependencies] diff --git a/src/lustre/attribute.gleam b/src/lustre/attribute.gleam index 0ae289c..7d49b18 100644 --- a/src/lustre/attribute.gleam +++ b/src/lustre/attribute.gleam @@ -1,6 +1,3 @@ -//// To read the full documentation for this module, please visit -//// [https://lustre.build/api/lustre/attribute](https://lustre.build/api/lustre/attribute) - // IMPORTS --------------------------------------------------------------------- import gleam/dynamic.{type Dynamic} @@ -13,7 +10,8 @@ import lustre/internals/vdom.{Attribute, Event} // TYPES ----------------------------------------------------------------------- -/// +/// The `Attribute` type encompasses HTML attributes, DOM properties, and +/// event listeners. pub type Attribute(msg) = vdom.Attribute(msg) @@ -24,7 +22,7 @@ pub fn attribute(name: String, value: String) -> Attribute(msg) { Attribute(name, dynamic.from(value), as_property: False) } -/// +/// pub fn property(name: String, value: any) -> Attribute(msg) { Attribute(name, dynamic.from(value), as_property: True) } @@ -39,12 +37,16 @@ pub fn on( /// pub fn none() -> Attribute(msg) { - Attribute("", dynamic.from(""), as_property: False) + class("") } // MANIPULATIONS --------------------------------------------------------------- -/// +/// The `Attribute` type is parameterised by the type of messages it can produce +/// from events handlers. Sometimes you might end up with an attribute from a +/// library or module that produces a different type of message: this function lets +/// you map the messages produced from one type to another. +/// pub fn map(attr: Attribute(a), f: fn(a) -> b) -> Attribute(b) { case attr { Attribute(name, value, as_property) -> Attribute(name, value, as_property) @@ -55,6 +57,11 @@ pub fn map(attr: Attribute(a), f: fn(a) -> b) -> Attribute(b) { // COMMON ATTRIBUTES ----------------------------------------------------------- /// +/// +/// **Note**: unlike most attributes, multiple `style` attributes are merged +/// with any existing other styles on an element. Styles added _later_ in the +/// list will override styles added earlier. +/// pub fn style(properties: List(#(String, String))) -> Attribute(msg) { attribute("style", { use styles, #(name, value) <- list.fold(properties, "") @@ -63,6 +70,10 @@ pub fn style(properties: List(#(String, String))) -> Attribute(msg) { } /// +/// +/// **Note**: unlike most attributes, multiple `class` attributes are merged +/// with any existing other classes on an element. +/// pub fn class(name: String) -> Attribute(msg) { attribute("class", name) } @@ -100,7 +111,7 @@ pub fn type_(name: String) -> Attribute(msg) { } /// -pub fn value(val: any) -> Attribute(msg) { +pub fn value(val: String) -> Attribute(msg) { property("value", dynamic.from(val)) } diff --git a/src/lustre/effect.gleam b/src/lustre/effect.gleam index d92a2f1..38f809f 100644 --- a/src/lustre/effect.gleam +++ b/src/lustre/effect.gleam @@ -54,7 +54,7 @@ import gleam/function /// /// 2. The type of messages that (might) be sent back to the program in response. /// -/// +/// /// pub opaque type Effect(msg) { Effect(all: List(fn(fn(msg) -> Nil, fn(String, Json) -> Nil) -> Nil)) @@ -80,8 +80,8 @@ pub fn from(effect: fn(fn(msg) -> Nil) -> Nil) -> Effect(msg) { /// of Lustre's components, but in rare cases it may be useful to emit custom /// events from the DOM node that your Lustre application is mounted to. /// -/// -/// +/// +/// pub fn event(name: String, data: Json) -> Effect(msg) { Effect([fn(_, emit) { emit(name, data) }]) } @@ -97,14 +97,14 @@ pub fn none() -> Effect(msg) { // MANIPULATIONS --------------------------------------------------------------- /// Batch multiple effects to be performed at the same time. -/// +/// /// **Note:** The runtime makes no guarantees about the order on which effects /// are performed! If you need to chain or sequence effects together, you have /// two broad options: -/// +/// /// 1. Create variants of your `msg` type to represent each step in the sequence /// and fire off the next effect in response to the previous one. -/// +/// /// 2. If you're defining effects yourself, consider whether or not you can handle /// the sequencing inside the effect itself. /// @@ -117,7 +117,7 @@ pub fn batch(effects: List(Effect(msg))) -> Effect(msg) { /// Transform the result of an effect. This is useful for mapping over effects /// produced by other libraries or modules. -/// +/// /// **Note:** Remember that effects are not _required_ to dispatch any messages. /// Your mapping function may never be called! /// @@ -137,9 +137,12 @@ pub fn map(effect: Effect(a), f: fn(a) -> b) -> Effect(b) { /// This is primarily used internally by the server component runtime, but it is /// may also useful for testing. /// -/// **Note:** You should not consider this function a part of the public API. It -/// may be removed in a future minor or patch release. -/// +/// **Note:** For now, you should **not** consider this function a part of the +/// public API. It may be removed in a future minor or patch release. If you have +/// a specific use case for this function, we'd love to hear about it! Please +/// reach out on the [Gleam Discord](https://discord.gg/Fm8Pwmy) or +/// [open an issue](https://github.com/lustre-labs/lustre/issues/new)! +/// pub fn perform( effect: Effect(a), dispatch: fn(a) -> Nil, diff --git a/src/lustre/element.gleam b/src/lustre/element.gleam index 1f4d885..fc92ccd 100644 --- a/src/lustre/element.gleam +++ b/src/lustre/element.gleam @@ -1,5 +1,10 @@ //// Lustre wouldn't be much use as a frontend framework if it didn't provide a -//// way to create HTML elements. This module contains +//// way to create HTML elements. This module contains the basic functions +//// necessary to construct and manipulate different HTML elements. +//// +//// It is also possible to use Lustre as a HTML templating library, without +//// using its runtime or framework features. +//// // IMPORTS --------------------------------------------------------------------- @@ -14,49 +19,52 @@ import lustre/internals/vdom.{Element, Text} /// The `Element` type is how Lustre represents chunks of HTML. The `msg` type /// variable is used to represent the types of messages that can be produced from /// events on the element or its children. -/// +/// /// **Note:** Just because an element _can_ produces messages of a given type, /// doesn't mean that it _will_! The `msg` type variable is used to represent the /// potential for messages to be produced, not a guarantee. -/// +/// /// The most basic ways to create elements are: -/// +/// /// - The [`element`](#element) function to construct arbitrary HTML elements. /// You can also use this render Custom Elements (like those registered as /// Lustre components). -/// +/// /// - The [`text`](#text) function to turn a Gleam string into a text node. -/// +/// /// - The [`none`](#none) function to render nothing - useful for conditional /// rendering. -/// +/// /// If you have more complex needs, there are two more-advanced functions: -/// +/// /// - The [`namespaced`](#namespaced) function to create elements in a specific /// XML namespace. This is useful for SVG or MathML elements, for example. -/// +/// /// - The [`advanced`](#advanced) function to create elements with more control /// over how the element is rendered when converted to a string. This is /// necessary because some HTML, SVG, and MathML elements are self-closing or /// void elements, and Lustre needs to know how to render them correctly! -/// +/// /// For most applications, you'll only need to use the simpler functions; usually /// the [`text`](#text) and [`none`](#none) functions are enough. This is because /// Lustre already provides a module with all the standard HTML and SVG elements /// ready to use in [`lustre/element/html`](./element/html) and /// [`lustre/element/svg`](./element/svg). -/// +/// pub type Element(msg) = vdom.Element(msg) // CONSTRUCTORS ---------------------------------------------------------------- -/// -/// +/// A general function for constructing any kind of element. In most cases you +/// will want to use the [`lustre/element/html`](./element/html) instead but this +/// function is particularly handing when constructing custom elements, either +/// from your own Lustre components or from external JavaScript libraries. +/// /// **Note:** Because Lustre is primarily used to create HTML, this function /// spcieal-cases the following tags render as /// [void elements](https://developer.mozilla.org/en-US/docs/Glossary/Void_element): -/// +/// /// - area /// - base /// - br @@ -71,12 +79,12 @@ pub type Element(msg) = /// - source /// - track /// - wbr -/// +/// /// This will only affect the output of `to_string` and `to_string_builder`! /// If you need to render any of these tags with children, *or* you want to /// render some other tag as self-closing or void, use [`advanced`](#advanced) /// to construct the element instead. -/// +/// pub fn element( tag: String, attrs: List(Attribute(msg)), @@ -109,6 +117,8 @@ pub fn element( } } +/// A function for constructing elements in a specific XML namespace. This can +/// be used to construct SVG or MathML elements, for example. /// pub fn namespaced( namespace: String, @@ -126,6 +136,10 @@ pub fn namespaced( ) } +/// A function for constructing elements with more control over how the element +/// is rendered when converted to a string. This is necessary because some HTML, +/// SVG, and MathML elements are self-closing or void elements, and Lustre needs +/// to know how to render them correctly! /// pub fn advanced( namespace: String, @@ -138,11 +152,18 @@ pub fn advanced( Element(tag, namespace, attrs, children, self_closing, void) } -/// +/// A function for turning a Gleam string into a text node. Gleam doesn't have +/// union types like some other languages you may be familiar with, like TypeScript. +/// Instead, we need a way to take a `String` and turn it into an `Element` somehow: +/// this function is exactly that! +/// pub fn text(content: String) -> Element(msg) { Text(content) } +/// A function for rendering nothing. This is mostly useful for conditional +/// rendering, where you might want to render something only if a certain +/// condition is met. /// pub fn none() -> Element(msg) { Text("") @@ -169,9 +190,9 @@ fn escape(escaped: String, content: String) -> String { /// from events. Sometimes you might end up with a fragment of HTML from another /// library or module that produces a different type of message: this function lets /// you map the messages produced from one type to another. -/// +/// /// Think of it like `list.map` or `result.map` but for HTML events! -/// +/// pub fn map(element: Element(a), f: fn(a) -> b) -> Element(b) { case element { Text(content) -> Text(content) @@ -189,11 +210,22 @@ pub fn map(element: Element(a), f: fn(a) -> b) -> Element(b) { // CONVERSIONS ----------------------------------------------------------------- -/// +/// Convert a Lustre `Element` to a string. This is _not_ pretty-printed, so +/// there are no newlines or indentation. If you need to pretty-print an element, +/// reach out on the [Gleam Discord](https://discord.gg/Fm8Pwmy) or +/// [open an issue](https://github.com/lustre-labs/lustre/issues/new) with your +/// use case and we'll see what we can do! +/// pub fn to_string(element: Element(msg)) -> String { vdom.element_to_string(element) } +/// Convert a Lustre `Element` to a `StringBuilder`. This is _not_ pretty-printed, +/// so there are no newlines or indentation. If you need to pretty-print an element, +/// reach out on the [Gleam Discord](https://discord.gg/Fm8Pwmy) or +/// [open an issue](https://github.com/lustre-labs/lustre/issues/new) with your +/// use case and we'll see what we can do! +/// pub fn to_string_builder(element: Element(msg)) -> StringBuilder { vdom.element_to_string_builder(element) } diff --git a/src/lustre/element/html.gleam b/src/lustre/element/html.gleam index 248867d..426c65b 100644 --- a/src/lustre/element/html.gleam +++ b/src/lustre/element/html.gleam @@ -1,14 +1,11 @@ -//// To read the full documentation for this module, please visit -//// [https://lustre.build/api/lustre/element/html](https://lustre.build/api/lustre/element/html) - // IMPORTS --------------------------------------------------------------------- -import lustre/element.{type Element, element, namespaced, text} +import lustre/element.{type Element, element, namespaced} import lustre/attribute.{type Attribute} // HTML ELEMENTS: MAIN ROOT ---------------------------------------------------- -/// +/// pub fn html( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -16,14 +13,18 @@ pub fn html( element("html", attrs, children) } +pub fn text(content: String) -> Element(msg) { + element.text(content) +} + // HTML ELEMENTS: DOCUMENT METADATA -------------------------------------------- -/// +/// pub fn base(attrs: List(Attribute(msg))) -> Element(msg) { element("base", attrs, []) } -/// +/// pub fn head( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -31,29 +32,29 @@ pub fn head( element("head", attrs, children) } -/// +/// pub fn link(attrs: List(Attribute(msg))) -> Element(msg) { element("link", attrs, []) } -/// +/// pub fn meta(attrs: List(Attribute(msg))) -> Element(msg) { element("meta", attrs, []) } -/// +/// pub fn style(attrs: List(Attribute(msg)), css: String) -> Element(msg) { element("style", attrs, [text(css)]) } -/// +/// pub fn title(attrs: List(Attribute(msg)), content: String) -> Element(msg) { element("title", attrs, [text(content)]) } // HTML ELEMENTS: SECTIONING ROOT ----------------------------------------------- -/// +/// pub fn body( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -63,7 +64,7 @@ pub fn body( // HTML ELEMENTS: CONTENT SECTIONING ------------------------------------------- -/// +/// pub fn address( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -71,7 +72,7 @@ pub fn address( element("address", attrs, children) } -/// +/// pub fn article( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -79,7 +80,7 @@ pub fn article( element("article", attrs, children) } -/// +/// pub fn aside( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -87,7 +88,7 @@ pub fn aside( element("aside", attrs, children) } -/// +/// pub fn footer( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -95,7 +96,7 @@ pub fn footer( element("footer", attrs, children) } -/// +/// pub fn header( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -103,7 +104,7 @@ pub fn header( element("header", attrs, children) } -/// +/// pub fn h1( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -111,7 +112,7 @@ pub fn h1( element("h1", attrs, children) } -/// +/// pub fn h2( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -119,7 +120,7 @@ pub fn h2( element("h2", attrs, children) } -/// +/// pub fn h3( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -127,7 +128,7 @@ pub fn h3( element("h3", attrs, children) } -/// +/// pub fn h4( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -135,7 +136,7 @@ pub fn h4( element("h4", attrs, children) } -/// +/// pub fn h5( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -143,7 +144,7 @@ pub fn h5( element("h5", attrs, children) } -/// +/// pub fn h6( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -151,7 +152,7 @@ pub fn h6( element("h6", attrs, children) } -/// +/// pub fn hgroup( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -159,7 +160,7 @@ pub fn hgroup( element("hgroup", attrs, children) } -/// +/// pub fn main( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -167,7 +168,7 @@ pub fn main( element("main", attrs, children) } -/// +/// pub fn nav( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -175,7 +176,7 @@ pub fn nav( element("nav", attrs, children) } -/// +/// pub fn section( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -183,7 +184,7 @@ pub fn section( element("section", attrs, children) } -/// +/// pub fn search( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -193,7 +194,7 @@ pub fn search( // HTML ELEMENTS: TEXT CONTENT ------------------------------------------------- -/// +/// pub fn blockquote( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -201,7 +202,7 @@ pub fn blockquote( element("blockquote", attrs, children) } -/// +/// pub fn dd( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -209,7 +210,7 @@ pub fn dd( element("dd", attrs, children) } -/// +/// pub fn div( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -217,7 +218,7 @@ pub fn div( element("div", attrs, children) } -/// +/// pub fn dl( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -225,7 +226,7 @@ pub fn dl( element("dl", attrs, children) } -/// +/// pub fn dt( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -233,7 +234,7 @@ pub fn dt( element("dt", attrs, children) } -/// +/// pub fn figcaption( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -241,7 +242,7 @@ pub fn figcaption( element("figcaption", attrs, children) } -/// +/// pub fn figure( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -249,12 +250,12 @@ pub fn figure( element("figure", attrs, children) } -/// +/// pub fn hr(attrs: List(Attribute(msg))) -> Element(msg) { element("hr", attrs, []) } -/// +/// pub fn li( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -262,7 +263,7 @@ pub fn li( element("li", attrs, children) } -/// +/// pub fn menu( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -278,7 +279,7 @@ pub fn ol( element("ol", attrs, children) } -/// +/// pub fn p( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -286,7 +287,7 @@ pub fn p( element("p", attrs, children) } -/// +/// pub fn pre( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -294,7 +295,7 @@ pub fn pre( element("pre", attrs, children) } -/// +/// pub fn ul( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -304,7 +305,7 @@ pub fn ul( // HTML ELEMENTS: INLINE TEXT SEMANTICS ---------------------------------------- -/// +/// pub fn a( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -312,7 +313,7 @@ pub fn a( element("a", attrs, children) } -/// +/// pub fn abbr( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -320,7 +321,7 @@ pub fn abbr( element("abbr", attrs, children) } -/// +/// pub fn b( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -328,7 +329,7 @@ pub fn b( element("b", attrs, children) } -/// +/// pub fn bdi( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -336,7 +337,7 @@ pub fn bdi( element("bdi", attrs, children) } -/// +/// pub fn bdo( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -344,12 +345,12 @@ pub fn bdo( element("bdo", attrs, children) } -/// +/// pub fn br(attrs: List(Attribute(msg))) -> Element(msg) { element("br", attrs, []) } -/// +/// pub fn cite( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -357,7 +358,7 @@ pub fn cite( element("cite", attrs, children) } -/// +/// pub fn code( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -365,7 +366,7 @@ pub fn code( element("code", attrs, children) } -/// +/// pub fn data( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -373,7 +374,7 @@ pub fn data( element("data", attrs, children) } -/// +/// pub fn dfn( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -381,7 +382,7 @@ pub fn dfn( element("dfn", attrs, children) } -/// +/// pub fn em( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -389,7 +390,7 @@ pub fn em( element("em", attrs, children) } -/// +/// pub fn i( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -397,7 +398,7 @@ pub fn i( element("i", attrs, children) } -/// +/// pub fn kbd( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -405,7 +406,7 @@ pub fn kbd( element("kbd", attrs, children) } -/// +/// pub fn mark( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -413,7 +414,7 @@ pub fn mark( element("mark", attrs, children) } -/// +/// pub fn q( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -421,7 +422,7 @@ pub fn q( element("q", attrs, children) } -/// +/// pub fn rp( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -429,7 +430,7 @@ pub fn rp( element("rp", attrs, children) } -/// +/// pub fn rt( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -437,7 +438,7 @@ pub fn rt( element("rt", attrs, children) } -/// +/// pub fn ruby( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -445,7 +446,7 @@ pub fn ruby( element("ruby", attrs, children) } -/// +/// pub fn s( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -453,7 +454,7 @@ pub fn s( element("s", attrs, children) } -/// +/// pub fn samp( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -461,7 +462,7 @@ pub fn samp( element("samp", attrs, children) } -/// +/// pub fn small( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -469,7 +470,7 @@ pub fn small( element("small", attrs, children) } -/// +/// pub fn span( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -477,7 +478,7 @@ pub fn span( element("span", attrs, children) } -/// +/// pub fn strong( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -485,7 +486,7 @@ pub fn strong( element("strong", attrs, children) } -/// +/// pub fn sub( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -493,7 +494,7 @@ pub fn sub( element("sub", attrs, children) } -/// +/// pub fn sup( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -501,7 +502,7 @@ pub fn sup( element("sup", attrs, children) } -/// +/// pub fn time( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -509,7 +510,7 @@ pub fn time( element("time", attrs, children) } -/// +/// pub fn u( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -517,7 +518,7 @@ pub fn u( element("u", attrs, children) } -/// +/// pub fn var( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -525,19 +526,19 @@ pub fn var( element("var", attrs, children) } -/// +/// pub fn wbr(attrs: List(Attribute(msg))) -> Element(msg) { element("wbr", attrs, []) } // HTML ELEMENTS: IMAGE AND MULTIMEDIA ----------------------------------------- -/// +/// pub fn area(attrs: List(Attribute(msg))) -> Element(msg) { element("area", attrs, []) } -/// +/// pub fn audio( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -545,13 +546,13 @@ pub fn audio( element("audio", attrs, children) } -/// +/// pub fn img(attrs: List(Attribute(msg))) -> Element(msg) { element("img", attrs, []) } /// Used with <area> elements to define an image map (a clickable link area). -/// +/// pub fn map( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -559,12 +560,12 @@ pub fn map( element("map", attrs, children) } -/// +/// pub fn track(attrs: List(Attribute(msg))) -> Element(msg) { element("track", attrs, []) } -/// +/// pub fn video( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -574,22 +575,22 @@ pub fn video( // HTML ELEMENTS: EMBEDDED CONTENT --------------------------------------------- -/// +/// pub fn embed(attrs: List(Attribute(msg))) -> Element(msg) { element("embed", attrs, []) } -/// +/// pub fn iframe(attrs: List(Attribute(msg))) -> Element(msg) { element("iframe", attrs, []) } -/// +/// pub fn object(attrs: List(Attribute(msg))) -> Element(msg) { element("object", attrs, []) } -/// +/// pub fn picture( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -597,19 +598,19 @@ pub fn picture( element("picture", attrs, children) } -/// +/// pub fn portal(attrs: List(Attribute(msg))) -> Element(msg) { element("portal", attrs, []) } -/// +/// pub fn source(attrs: List(Attribute(msg))) -> Element(msg) { element("source", attrs, []) } // HTML ELEMENTS: SVG AND MATHML ----------------------------------------------- -/// +/// pub fn svg( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -617,7 +618,7 @@ pub fn svg( namespaced("http://www.w3.org/2000/svg", "svg", attrs, children) } -/// +/// pub fn math( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -627,12 +628,12 @@ pub fn math( // HTML ELEMENTS: SCRIPTING ---------------------------------------------------- -/// +/// pub fn canvas(attrs: List(Attribute(msg))) -> Element(msg) { element("canvas", attrs, []) } -/// +/// pub fn noscript( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -640,14 +641,14 @@ pub fn noscript( element("noscript", attrs, children) } -/// +/// pub fn script(attrs: List(Attribute(msg)), js: String) -> Element(msg) { element("script", attrs, [text(js)]) } // HTML ELEMENTS: DEMARCATING EDITS --------------------------------------------- -/// +/// pub fn del( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -655,7 +656,7 @@ pub fn del( element.element("del", attrs, children) } -/// +/// pub fn ins( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -665,7 +666,7 @@ pub fn ins( // HTML ELEMENTS: TABLE CONTENT ------------------------------------------------ -/// +/// pub fn caption( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -673,12 +674,12 @@ pub fn caption( element.element("caption", attrs, children) } -/// +/// pub fn col(attrs: List(Attribute(msg))) -> Element(msg) { element.element("col", attrs, []) } -/// +/// pub fn colgroup( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -686,7 +687,7 @@ pub fn colgroup( element.element("colgroup", attrs, children) } -/// +/// pub fn table( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -694,7 +695,7 @@ pub fn table( element.element("table", attrs, children) } -/// +/// pub fn tbody( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -702,7 +703,7 @@ pub fn tbody( element.element("tbody", attrs, children) } -/// +/// pub fn td( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -710,7 +711,7 @@ pub fn td( element.element("td", attrs, children) } -/// +/// pub fn tfoot( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -718,7 +719,7 @@ pub fn tfoot( element.element("tfoot", attrs, children) } -/// +/// pub fn th( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -726,7 +727,7 @@ pub fn th( element.element("th", attrs, children) } -/// +/// pub fn thead( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -734,7 +735,7 @@ pub fn thead( element.element("thead", attrs, children) } -/// +/// pub fn tr( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -744,7 +745,7 @@ pub fn tr( // HTML ELEMENTS: FORMS -------------------------------------------------------- -/// +/// pub fn button( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -752,7 +753,7 @@ pub fn button( element.element("button", attrs, children) } -/// +/// pub fn datalist( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -760,7 +761,7 @@ pub fn datalist( element.element("datalist", attrs, children) } -/// +/// pub fn fieldset( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -768,7 +769,7 @@ pub fn fieldset( element.element("fieldset", attrs, children) } -/// +/// pub fn form( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -776,12 +777,12 @@ pub fn form( element.element("form", attrs, children) } -/// +/// pub fn input(attrs: List(Attribute(msg))) -> Element(msg) { element.element("input", attrs, []) } -/// +/// pub fn label( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -789,7 +790,7 @@ pub fn label( element.element("label", attrs, children) } -/// +/// pub fn legend( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -797,7 +798,7 @@ pub fn legend( element.element("legend", attrs, children) } -/// +/// pub fn meter( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -805,7 +806,7 @@ pub fn meter( element.element("meter", attrs, children) } -/// +/// pub fn optgroup( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -813,12 +814,12 @@ pub fn optgroup( element.element("optgroup", attrs, children) } -/// +/// pub fn option(attrs: List(Attribute(msg))) -> Element(msg) { element.element("option", attrs, []) } -/// +/// pub fn output( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -826,7 +827,7 @@ pub fn output( element.element("output", attrs, children) } -/// +/// pub fn progress( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -834,7 +835,7 @@ pub fn progress( element.element("progress", attrs, children) } -/// +/// pub fn select( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -842,14 +843,14 @@ pub fn select( element.element("select", attrs, children) } -/// +/// pub fn textarea(attrs: List(Attribute(msg))) -> Element(msg) { element.element("textarea", attrs, []) } // HTML ELEMENTS: INTERACTIVE ELEMENTS ----------------------------------------- -/// +/// pub fn details( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -857,7 +858,7 @@ pub fn details( element.element("details", attrs, children) } -/// +/// pub fn dialog( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -865,7 +866,7 @@ pub fn dialog( element.element("dialog", attrs, children) } -/// +/// pub fn summary( attrs: List(Attribute(msg)), children: List(Element(msg)), @@ -880,7 +881,7 @@ pub fn slot(attrs: List(Attribute(msg))) -> Element(msg) { element.element("slot", attrs, []) } -/// +/// pub fn template( attrs: List(Attribute(msg)), children: List(Element(msg)), diff --git a/src/lustre/element/svg.gleam b/src/lustre/element/svg.gleam index 447a4c6..1979c2a 100644 --- a/src/lustre/element/svg.gleam +++ b/src/lustre/element/svg.gleam @@ -1,6 +1,3 @@ -//// To read the full documentation for this module, please visit -//// [https://lustre.build/api/lustre/element/svg](https://lustre.build/api/lustre/element/svg) - // IMPORTS --------------------------------------------------------------------- import lustre/element.{type Element, namespaced, text as inline_text} diff --git a/src/lustre/event.gleam b/src/lustre/event.gleam index 64628b0..1658504 100644 --- a/src/lustre/event.gleam +++ b/src/lustre/event.gleam @@ -1,6 +1,3 @@ -//// To read the full documentation for this module, please visit -//// [https://lustre.build/api/lustre/event](https://lustre.build/api/lustre/event) - // IMPORTS --------------------------------------------------------------------- import gleam/dynamic.{type DecodeError, type Dynamic} @@ -16,6 +13,11 @@ type Decoded(a) = // EFFECTS --------------------------------------------------------------------- +/// Dispatches a custom message from a Lustre component. This lets components +/// communicate with their parents the same way native DOM elements do. +/// +/// Any JSON-serialisable payload can be attached as additional data for any +/// event listeners to decode. This data will be on the event's `detail` property. /// pub fn emit(event: String, data: Json) -> Effect(msg) { effect.event(event, data) @@ -23,6 +25,13 @@ pub fn emit(event: String, data: Json) -> Effect(msg) { // CUSTOM EVENTS --------------------------------------------------------------- +/// Listens for the given event and applies the handler to the event object. If +/// the handler returns an `Ok` the resulting message will be dispatched, otherwise +/// the event (and any decoding error) will be ignored. +/// +/// The event name is typically an all-lowercase string such as "click" or "mousemove". +/// If you're listening for non-standard events (like those emitted by a custom +/// element) their event names might be slightly different. /// pub fn on( name: String, @@ -127,6 +136,10 @@ pub fn on_check(msg: fn(Bool) -> msg) -> Attribute(msg) { |> result.map(msg) } +/// Listens for the form's `submit` event, and dispatches the given message. This +/// will automatically call [`prevent_default`](#prevent_default) to stop the form +/// from submitting. +/// pub fn on_submit(msg: msg) -> Attribute(msg) { use event <- on("submit") let _ = prevent_default(event) @@ -148,18 +161,26 @@ pub fn on_blur(msg: msg) -> Attribute(msg) { // DECODERS -------------------------------------------------------------------- +/// Decoding an input element's `value` is such a common operation that we have +/// a dedicated decoder for it. This attempts to decoder `event.target.value` as +/// a string. /// pub fn value(event: Dynamic) -> Decoded(String) { event |> dynamic.field("target", dynamic.field("value", dynamic.string)) } +/// Similar to [`value`](#value), decoding a checkbox's `checked` state is common +/// enough to warrant a dedicated decoder. This attempts to decode +/// `event.target.checked` as a boolean. /// pub fn checked(event: Dynamic) -> Decoded(Bool) { event |> dynamic.field("target", dynamic.field("checked", dynamic.bool)) } +/// Decodes the mouse position from any event that has a `clientX` and `clientY` +/// property. /// pub fn mouse_position(event: Dynamic) -> Decoded(#(Float, Float)) { use x <- result.then(dynamic.field("clientX", dynamic.float)(event)) @@ -170,11 +191,29 @@ pub fn mouse_position(event: Dynamic) -> Decoded(#(Float, Float)) { // UTILS ----------------------------------------------------------------------- +/// Calls an event's `preventDefault` method. If the `Dynamic` does not have a +/// `preventDefault` method, this function does nothing. +/// +/// As the name implies, `preventDefault` will prevent any default action associated +/// with an event from occuring. For example, if you call `preventDefault` on a +/// `submit` event, the form will not be submitted. +/// +/// See: https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault +/// @external(javascript, "../client-runtime.ffi.mjs", "prevent_default") pub fn prevent_default(_event: Dynamic) -> Nil { Nil } +/// Calls an event's `stopPropagation` method. If the `Dynamic` does not have a +/// `stopPropagation` method, this function does nothing. +/// +/// Stopping event propagation means the event will not "bubble" up to parent +/// elements. If any elements higher up in the DOM have event listeners for the +/// same event, they will not be called. +/// +/// See: https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation +/// @external(javascript, "../client-runtime.ffi.mjs", "stop_propagation") pub fn stop_propagation(_event: Dynamic) -> Nil { Nil diff --git a/src/lustre/internals/runtime.gleam b/src/lustre/internals/runtime.gleam index b0e264f..716daac 100644 --- a/src/lustre/internals/runtime.gleam +++ b/src/lustre/internals/runtime.gleam @@ -219,42 +219,3 @@ fn run_effects(effects: Effect(msg), self: Subject(Action(msg, runtime))) -> Nil effect.perform(effects, dispatch, emit) } - -// Empty implementations of every function in this module are required because we -// need to be able to build the codebase *locally* with the JavaScript target to -// bundle the server component runtime. -// -// For *consumers* of Lustre this is not a problem, Gleam will see this module is -// never included in any path reachable from JavaScript but when we're *inside the -// package* Gleam has no idea that is the case. - -@target(javascript) -pub fn start( - init: #(model, Effect(msg)), - update: fn(model, msg) -> #(model, Effect(msg)), - view: fn(model) -> Element(msg), - on_attribute_change: Dict(String, Decoder(msg)), -) -> Result(Subject(Action(msg, runtime)), StartError) { - panic -} - -@target(javascript) -fn loop( - message: Action(msg, runtime), - state: State(model, msg, runtime), -) -> Next(Action(msg, runtime), State(model, msg, runtime)) { - panic -} - -@target(javascript) -fn run_renderers( - renderers: Dict(any, fn(Patch(msg)) -> Nil), - patch: Patch(msg), -) -> Nil { - panic -} - -@target(javascript) -fn run_effects(effects: Effect(msg), self: Subject(Action(msg, runtime))) -> Nil { - panic -} |