From 0a5597f8ab9f4926b176809509de1a1b25d50f61 Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Mon, 23 Dec 2024 01:36:45 +0100 Subject: [PATCH 01/56] :bug: Fixed a bug where ticker component displayed target time not target value. --- src/lustre/ui/ticker.gleam | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/lustre/ui/ticker.gleam b/src/lustre/ui/ticker.gleam index 2587b43..f412dd6 100644 --- a/src/lustre/ui/ticker.gleam +++ b/src/lustre/ui/ticker.gleam @@ -91,7 +91,7 @@ fn init(_) -> #(Model, Effect(Msg)) { from: 0.0, value: 0.0, to: 0.0, - duration: 0.0, + duration: 1000.0, now: 0.0, start: 0.0, target: 0.0, @@ -131,14 +131,15 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { } ParentSetDuration(duration) -> { - let model = Model(..model, duration: duration) + let target = model.start +. duration + let model = Model(..model, target:, duration:) let effect = effect.none() #(model, effect) } ParentSetFunction(function) -> { - let model = Model(..model, function: function) + let model = Model(..model, function:) let effect = effect.none() #(model, effect) @@ -158,7 +159,6 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { let model = Model( ..model, - from: model.value, to: to, start: start, target: start +. model.duration, @@ -244,18 +244,13 @@ fn animation_time() -> Float // VIEW ------------------------------------------------------------------------ fn view(model: Model) -> Element(Msg) { - let target = float.round(model.target) + let to = float.round(model.to) let value = float.round(model.value) element( "lustre-ui-intersection-observer", [event.on("intersection", handle_intersection)], - [ - value - |> int.min(target) - |> int.to_string - |> html.text, - ], + [value |> int.min(to) |> int.to_string |> html.text], ) } From 98c609aab0c1d101d97e702badb08c3125296f22 Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Mon, 23 Dec 2024 01:42:18 +0100 Subject: [PATCH 02/56] :recycle: Schedule an animation tick whenever the duration changes. --- src/lustre/ui/ticker.gleam | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lustre/ui/ticker.gleam b/src/lustre/ui/ticker.gleam index f412dd6..9075bf2 100644 --- a/src/lustre/ui/ticker.gleam +++ b/src/lustre/ui/ticker.gleam @@ -133,7 +133,10 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { ParentSetDuration(duration) -> { let target = model.start +. duration let model = Model(..model, target:, duration:) - let effect = effect.none() + let effect = case model.can_animate { + True -> after_paint() + False -> effect.none() + } #(model, effect) } From 0f051ac52ce984fea459e9937006b1525bfb0d3d Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Mon, 23 Dec 2024 01:43:56 +0100 Subject: [PATCH 03/56] :recycle: Remove 'use_x' attributes, increase default space scale. --- src/lustre/ui/theme.gleam | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/src/lustre/ui/theme.gleam b/src/lustre/ui/theme.gleam index c446167..d81cc8b 100644 --- a/src/lustre/ui/theme.gleam +++ b/src/lustre/ui/theme.gleam @@ -9,7 +9,7 @@ import gleam/pair import gleam/result import gleam/string import gleam_community/colour.{type Colour} as gleam_community_colour -import lustre/attribute.{type Attribute, attribute} +import lustre/attribute.{attribute} import lustre/element.{type Element} import lustre/element/html import lustre/ui/colour.{type ColourPalette, type ColourScale, ColourPalette} @@ -153,7 +153,7 @@ pub fn default() -> Theme { let id = "lustre-ui-default" let font = Fonts(heading: sans, body: sans, code: code) let radius = perfect_fifth(0.75) - let space = golden_ratio(0.75) + let space = golden_ratio(1.0) let light = colour.default_light_palette() let dark = colour.default_dark_palette() @@ -437,32 +437,6 @@ pub fn with_dark_danger_scale(theme: Theme, scale: ColourScale) -> Theme { ) } -// ATTRIBUTES ------------------------------------------------------------------ - -pub fn use_base() -> Attribute(msg) { - attribute.class("base") -} - -pub fn use_primary() -> Attribute(msg) { - attribute.class("primary") -} - -pub fn use_secondary() -> Attribute(msg) { - attribute.class("secondary") -} - -pub fn use_success() -> Attribute(msg) { - attribute.class("success") -} - -pub fn use_warning() -> Attribute(msg) { - attribute.class("warning") -} - -pub fn use_danger() -> Attribute(msg) { - attribute.class("danger") -} - // CONVERSIONS ----------------------------------------------------------------- /// From 69e7d21658d43fa7567301e1513dd171a0bc18cf Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Mon, 23 Dec 2024 01:44:16 +0100 Subject: [PATCH 04/56] :art: Remove hr resets. --- src/lustre/ui/primitives/reset.css | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/lustre/ui/primitives/reset.css b/src/lustre/ui/primitives/reset.css index fd11a96..8396ef3 100644 --- a/src/lustre/ui/primitives/reset.css +++ b/src/lustre/ui/primitives/reset.css @@ -26,12 +26,6 @@ body { line-height: inherit; } -hr { - height: 0; - color: inherit; - border-top-width: 1px; -} - abbr:where([title]) { -webkit-text-decoration: underline dotted; text-decoration: underline dotted; From cb6f5f5c62f75b21f34f359ec7c8ea1b15f6ac16 Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Wed, 8 Jan 2025 18:46:10 +0100 Subject: [PATCH 05/56] :recycle: Rename 'main' element to 'element' in each component. --- src/lustre/ui/accordion.css | 2 +- src/lustre/ui/accordion.gleam | 30 ++++++++++++---------- src/lustre/ui/alert.gleam | 18 +++++++------- src/lustre/ui/badge.gleam | 18 +++++++------- src/lustre/ui/breadcrumb.gleam | 14 +++++------ src/lustre/ui/button.css | 6 +++++ src/lustre/ui/button.gleam | 33 +++++++++++++++---------- src/lustre/ui/card.css | 4 +-- src/lustre/ui/card.gleam | 2 +- src/lustre/ui/checkbox.gleam | 2 +- src/lustre/ui/combobox.gleam | 24 +++++++++--------- src/lustre/ui/divider.css | 8 ++++++ src/lustre/ui/divider.gleam | 2 +- src/lustre/ui/input.gleam | 2 +- src/lustre/ui/primitives/collapse.gleam | 6 ++--- src/lustre/ui/primitives/popover.gleam | 6 ++--- src/lustre/ui/reveal.gleam | 8 +++--- src/lustre/ui/ticker.gleam | 8 +++--- 18 files changed, 110 insertions(+), 83 deletions(-) diff --git a/src/lustre/ui/accordion.css b/src/lustre/ui/accordion.css index 13242d0..d5a0655 100644 --- a/src/lustre/ui/accordion.css +++ b/src/lustre/ui/accordion.css @@ -1,7 +1,7 @@ lustre-ui-accordion { --padding-x: var(--lustre-ui-spacing-sm); --padding-y: var(--lustre-ui-spacing-sm); - --border: rbg(var(--lustre-ui-accent)); + --border: rgb(var(--lustre-ui-accent)); --border-focus: rgb(var(--lustre-ui-primary-solid)); --border-width: 1px; --text: rgb(var(--lustre-ui-text)); diff --git a/src/lustre/ui/accordion.gleam b/src/lustre/ui/accordion.gleam index 889f140..fa7c621 100644 --- a/src/lustre/ui/accordion.gleam +++ b/src/lustre/ui/accordion.gleam @@ -1,4 +1,4 @@ -//// The [`accordion`](#accordion) element is an interactive component that allows +//// The [`accordion`](#element) element is an interactive component that allows //// users to show and hide grouped sections of content. Each section has a panel //// containing additional content that can be shown or hidden. //// @@ -14,7 +14,7 @@ //// //// An accordion is made up of two different parts: //// -//// - The main [`accordion`](#accordion) container used to organize content into +//// - The main [`element`](#element) container used to organize content into //// collapsible sections. (**required**) //// //// - One or more [`item`](#item) elements representing each expandable section, @@ -28,10 +28,10 @@ //// //// ```gleam //// import lustre/element/html -//// import lustre/ui/accordion.{accordion} +//// import lustre/ui/accordion //// //// pub fn faq() { -//// accordion([], [ +//// accordion.element([], [ //// accordion.item( //// value: "q1", //// label: "What is an accordion?", @@ -50,10 +50,10 @@ //// //// ```gleam //// import lustre/element/html -//// import lustre/ui/accordion.{accordion} +//// import lustre/ui/accordion //// //// pub fn settings() { -//// accordion([accordion.exactly_one()], [ +//// accordion.element([accordion.exactly_one()], [ //// accordion.item( //// value: "general", //// label: "General", @@ -99,6 +99,7 @@ import gleam/bool import gleam/dict.{type Dict} import gleam/dynamic.{type DecodeError, type Decoder, type Dynamic, dynamic} import gleam/int +import gleam/json import gleam/list import gleam/pair import gleam/result @@ -106,11 +107,11 @@ import gleam/set.{type Set} import lustre import lustre/attribute.{type Attribute, attribute} import lustre/effect.{type Effect} -import lustre/element.{type Element, element} +import lustre/element.{type Element} import lustre/element/html import lustre/event import lustre/ui/data/bidict.{type Bidict} -import lustre/ui/primitives/collapse.{collapse} +import lustre/ui/primitives/collapse import lustre/ui/primitives/icon // TYPES ----------------------------------------------------------------------- @@ -201,16 +202,16 @@ pub fn register() -> Result(Nil, lustre.Error) { /// /// /// -pub fn accordion( +pub fn element( attributes: List(Attribute(msg)), children: List(Item(msg)), ) -> Element(msg) { - element.keyed(element(name, attributes, _), { + element.keyed(element.element(name, attributes, _), { use Item(value, label, content) <- list.flat_map(children) use <- bool.guard(value == "", []) let item = - element("lustre-ui-accordion-item", [attribute.value(value)], [ + element.element("lustre-ui-accordion-item", [attribute.value(value)], [ html.text(label), ]) let content = html.div([attribute("slot", value)], content) @@ -480,7 +481,10 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { } let model = Model(..model, expanded:) - let effect = effect.none() + let effect = case set.contains(expanded, value) { + True -> event.emit("expand", json.string(value)) + False -> event.emit("collapse", json.string(value)) + } #(model, effect) } @@ -536,7 +540,7 @@ fn view(model: Model) -> Element(Msg) { use #(key, label) <- list.map(model.options.all) let is_expanded = set.contains(model.expanded, key) let item = - collapse( + collapse.element( [ collapse.expanded(is_expanded), collapse.on_change(fn(_) { UserToggledItem(key) }), diff --git a/src/lustre/ui/alert.gleam b/src/lustre/ui/alert.gleam index bb94854..3152f88 100644 --- a/src/lustre/ui/alert.gleam +++ b/src/lustre/ui/alert.gleam @@ -1,6 +1,6 @@ -//// The [`alert`](#alert) element, sometimes called a "callout", is used to direct -//// the user's attention away from the main content on the page and to some important -//// information or context. +//// The [`alert`](#element) element, sometimes called a "callout", is used to +//// direct the user's attention away from the main content on the page and to +//// some important information or context. //// //// Common uses for the `alert` element include: //// @@ -15,7 +15,7 @@ //// //// An alert is made up of different parts: //// -//// - The main [`alert`](#alert) container used to control the alert's styles +//// - The main [`element`](#element) container used to control the alert's styles //// and layout. (**required**) //// //// - An [`indicator`](#indicator) used to provide users with a visual clue about @@ -35,10 +35,10 @@ //// //// ```gleam //// import lustre/element/html -//// import lustre/ui/alert.{alert} +//// import lustre/ui/alert //// //// pub fn new_todo_added() { -//// alert([alert.success()], [ +//// alert.element([alert.success()], [ //// alert.title([], [html.text("New todo added to your list.")]) //// ]) //// } @@ -48,10 +48,10 @@ //// //// ```gleam //// import lustre/element/html -//// import lustre/ui/alert.{alert} +//// import lustre/ui/alert //// //// pub fn delete_todo_failed() { -//// alert([alert.danger()], [ +//// alert.element([alert.danger()], [ //// alert.indicator([], [icon.exclamation_triangle([])]), //// alert.title([], [html.text("Could not delete todo")]), //// alert.content([], [ @@ -127,7 +127,7 @@ import lustre/ui/theme /// /// /// -pub fn alert( +pub fn element( attributes: List(Attribute(msg)), children: List(Element(msg)), ) -> Element(msg) { diff --git a/src/lustre/ui/badge.gleam b/src/lustre/ui/badge.gleam index 7f71cc2..aab5102 100644 --- a/src/lustre/ui/badge.gleam +++ b/src/lustre/ui/badge.gleam @@ -1,7 +1,7 @@ -//// The [`badge`] element, sometimes called a "tag", is commonly used to attach -//// a small piece of information to some other element. You might use a badge to -//// indicate the type of a product in a grid, or to annotate something as "new" -//// or "updated". +//// The [`badge`](#element) element, sometimes called a "tag", is commonly used +//// to attach a small piece of information to some other element. You might use +//// a badge to indicate the type of a product in a grid, or to annotate something +//// as "new" or "updated". //// //// ## Anatomy //// @@ -9,11 +9,11 @@ //// //// A badge is made up of only one part: //// -//// - The main [`badge`](#badge) container used to apply the badge styles. Often +//// - The main [`root`](#root) container used to apply the badge styles. Often //// the only direct child of the container is the text used for the badges //// label, but it may also be an icon. //// -//// When empty, the [`badge`](#badge) container becomes a circle and can be +//// When empty, the [`root`](#root) container becomes a circle and can be //// used as a status indicator. //// //// ## Recipes @@ -27,12 +27,12 @@ //// ```gleam //// import lustre/attribute //// import lustre/element/html -//// import lustre/ui/badge.{badge} +//// import lustre/ui/badge //// //// pub fn online_avatar(src) { //// html.div([attribute.class("inline-block relative")], [ //// html.img([attribute.class("size-6 rounded-full"), attribute.src(src)]), -//// badge([ +//// badge.element([ //// badge.background("green"), //// badge.solid(), //// attribute.class("absolute top-0 right-0"), @@ -83,7 +83,7 @@ import lustre/ui/theme /// /// /// -pub fn badge( +pub fn element( attributes: List(Attribute(msg)), children: List(Element(msg)), ) -> Element(msg) { diff --git a/src/lustre/ui/breadcrumb.gleam b/src/lustre/ui/breadcrumb.gleam index ffe2fdd..852e957 100644 --- a/src/lustre/ui/breadcrumb.gleam +++ b/src/lustre/ui/breadcrumb.gleam @@ -1,4 +1,4 @@ -//// The [`breadcrumb`](#breadcrumb) element helps users understand their current +//// The [`breadcrumb`](#element) element helps users understand their current //// location in a hierarchical navigation structure and provides an easy way to //// navigate back up to parent pages. //// @@ -15,7 +15,7 @@ //// //// A breadcrumb is made up of different parts: //// -//// - The main [`breadcrumb`](#breadcrumb) container used to organize the navigation +//// - The main [`element`](#element) container used to organize the navigation //// items. (**required**) //// //// - One or more [`item`](#item) elements representing pages in the navigation @@ -39,10 +39,10 @@ //// ```gleam //// import lustre/attribute //// import lustre/element/html -//// import lustre/ui/breadcrumb.{breadcrumb} +//// import lustre/ui/breadcrumb //// //// pub fn nav() { -//// breadcrumb([], [ +//// breadcrumb.element([], [ //// breadcrumb.item([], [ //// html.a([attribute.href("/")], [html.text("Home")]), //// ]), @@ -60,10 +60,10 @@ //// //// ```gleam //// import lustre/element/html -//// import lustre/ui/breadcrumb.{breadcrumb} +//// import lustre/ui/breadcrumb //// //// pub fn nav() { -//// breadcrumb([], [ +//// breadcrumb.element([], [ //// breadcrumb.item([], [ //// html.a([attribute.href("/")], [html.text("Home")]), //// ]), @@ -107,7 +107,7 @@ import lustre/ui/primitives/icon /// /// /// -pub fn breadcrumb( +pub fn element( attributes: List(Attribute(msg)), children: List(Element(msg)), ) -> Element(msg) { diff --git a/src/lustre/ui/button.css b/src/lustre/ui/button.css index cf8882e..a81d9b3 100644 --- a/src/lustre/ui/button.css +++ b/src/lustre/ui/button.css @@ -45,6 +45,12 @@ transform: translateY(1px); } + @media (prefers-reduced-motion) { + &:active { + transform: unset; + } + } + &:is(a) { text-decoration: none; diff --git a/src/lustre/ui/button.gleam b/src/lustre/ui/button.gleam index 7b55dd1..65059df 100644 --- a/src/lustre/ui/button.gleam +++ b/src/lustre/ui/button.gleam @@ -1,4 +1,4 @@ -//// The [`button`](#button) element is a clickable control that receives keyboard +//// The [`button`](#element) element is a clickable control that receives keyboard //// and pointer events that you would use to dispatch messages to your `update` //// function when interacted with. //// @@ -16,7 +16,7 @@ //// //// A button is made up of different parts: //// -//// - The main [`button`](#button) container used to control the button's styles +//// - The main [`element`](#element) container used to control the button's styles //// and layout. (**required**) //// //// - One or more items of content like text or icons that provide visual and @@ -31,11 +31,11 @@ //// ```gleam //// import lustre/element/html //// import lustre/event -//// import lustre/ui/button.{button} +//// import lustre/ui/button //// import lustre/ui/icon //// //// pub fn save(handle_click) { -//// button([event.on_click(handle_click)], [ +//// button.element([event.on_click(handle_click)], [ //// icon.save([]), //// html.text("Save"), //// ]) @@ -47,10 +47,10 @@ //// ```gleam //// import lustre/element/html //// import lustre/event -//// import lustre/ui/button.{button} +//// import lustre/ui/button //// //// pub fn command_palette(handle_click) { -//// button([button.solid(), event.on_click(handle_click)], [ +//// button.element([button.solid(), event.on_click(handle_click)], [ //// html.text("Open"), //// button.shortcut_badge([], ["⌘", "k"]) //// ]) @@ -121,7 +121,7 @@ import lustre/ui/theme /// them focusable, buttons in Lustre UI have their `tabindex` attribute set to /// `0` for consistent behaviour across browsers. /// -pub fn button( +pub fn element( attributes: List(Attribute(msg)), children: List(Element(msg)), ) -> Element(msg) { @@ -216,12 +216,19 @@ pub fn shortcut_badge( /// If the count is greater than 99, the badge will display `"99+"`. /// pub fn count_badge(attributes: List(Attribute(msg)), count: Int) -> Element(msg) { - html.span([attribute.class("button-badge"), ..attributes], [ - html.text(case count < 100 { - True -> int.to_string(count) - False -> "99+" - }), - ]) + html.span( + [ + attribute.class("button-badge"), + attribute.style([#("font-variant-numeric", "tabular-nums")]), + ..attributes + ], + [ + html.text(case count < 100 { + True -> int.to_string(count) + False -> "99+" + }), + ], + ) } // ATTRIBUTES ------------------------------------------------------------------ diff --git a/src/lustre/ui/card.css b/src/lustre/ui/card.css index 4a08ea0..c33c31d 100644 --- a/src/lustre/ui/card.css +++ b/src/lustre/ui/card.css @@ -4,8 +4,8 @@ --background: rgb(var(--lustre-ui-bg)); --border: rgb(var(--lustre-ui-accent-subtle)); --border-width: 1px; - --padding-x: var(--lustre-ui-spacing-xl); - --padding-y: var(--lustre-ui-spacing-xl); + --padding-x: var(--lustre-ui-spacing-md); + --padding-y: var(--lustre-ui-spacing-md); --radius: var(--lustre-ui-radius-md); /* BASE STYLES ---------------------------------------------------------- */ diff --git a/src/lustre/ui/card.gleam b/src/lustre/ui/card.gleam index 8baee1a..6af78cb 100644 --- a/src/lustre/ui/card.gleam +++ b/src/lustre/ui/card.gleam @@ -7,7 +7,7 @@ import lustre/ui/theme // ELEMENTS -------------------------------------------------------------------- -pub fn card( +pub fn element( attributes: List(Attribute(msg)), children: List(Element(msg)), ) -> Element(msg) { diff --git a/src/lustre/ui/checkbox.gleam b/src/lustre/ui/checkbox.gleam index 2472561..6fa39c4 100644 --- a/src/lustre/ui/checkbox.gleam +++ b/src/lustre/ui/checkbox.gleam @@ -6,7 +6,7 @@ import lustre/element/html // ELEMENTS -------------------------------------------------------------------- -pub fn checkbox(attributes: List(Attribute(msg))) -> Element(msg) { +pub fn element(attributes: List(Attribute(msg))) -> Element(msg) { html.input([ attribute.class("lustre-ui-checkbox"), attribute.type_("checkbox"), diff --git a/src/lustre/ui/combobox.gleam b/src/lustre/ui/combobox.gleam index 344dad3..08220a5 100644 --- a/src/lustre/ui/combobox.gleam +++ b/src/lustre/ui/combobox.gleam @@ -15,13 +15,13 @@ import gleam/string import lustre import lustre/attribute.{type Attribute, attribute} import lustre/effect.{type Effect} -import lustre/element.{type Element, element} +import lustre/element.{type Element} import lustre/element/html import lustre/event import lustre/ui/data/bidict.{type Bidict} -import lustre/ui/input.{input} +import lustre/ui/input import lustre/ui/primitives/icon -import lustre/ui/primitives/popover.{popover} +import lustre/ui/primitives/popover // TYPES ----------------------------------------------------------------------- @@ -43,16 +43,18 @@ pub fn register() -> Result(Nil, lustre.Error) { } } -pub fn combobox( +pub fn element( attributes: List(Attribute(msg)), children: List(Item), ) -> Element(msg) { - element.keyed(element(name, attributes, _), { + element.keyed(element.element(name, attributes, _), { use item <- list.map(children) let el = - element("lustre-ui-combobox-option", [attribute.value(item.value)], [ - html.text(item.label), - ]) + element.element( + "lustre-ui-combobox-option", + [attribute.value(item.value)], + [html.text(item.label)], + ) #(item.value, el) }) @@ -407,7 +409,7 @@ fn view(model: Model) -> Element(Msg) { attribute.style([#("display", "none")]), event.on("slotchange", handle_slot_change), ]), - popover( + popover.element( [ popover.anchor(popover.BottomMiddle), popover.equal_width(), @@ -550,7 +552,7 @@ fn view_trigger( fn view_input(query: String) -> Element(Msg) { input.container([attribute("part", "combobox-input")], [ icon.magnifying_glass([]), - input([ + input.element([ attribute.style([ #("width", "100%"), #("border-bottom-left-radius", "0px"), @@ -678,7 +680,7 @@ fn view_option( [ icon([attribute.style([#("height", "1rem"), #("width", "1rem")])]), html.span([attribute.style([#("flex", "1 1 0%")])], [ - element("slot", [attribute.name("option-" <> option.value)], [ + element.element("slot", [attribute.name("option-" <> option.value)], [ html.text(option.label), ]), ]), diff --git a/src/lustre/ui/divider.css b/src/lustre/ui/divider.css index 61a6378..dc38159 100644 --- a/src/lustre/ui/divider.css +++ b/src/lustre/ui/divider.css @@ -1,4 +1,12 @@ .lustre-ui-divider { + /* Frameworks like Tailwind like to set the default height of
elements + to 0 which messes up our styles that expect the browser defaults. This rolls + back any of those changes. + */ + + height: initial; + width: initial; + /* VARIABLES ------------------------------------------------------------ */ --colour: rgb(var(--lustre-ui-accent)); diff --git a/src/lustre/ui/divider.gleam b/src/lustre/ui/divider.gleam index 00725a4..32fe529 100644 --- a/src/lustre/ui/divider.gleam +++ b/src/lustre/ui/divider.gleam @@ -6,7 +6,7 @@ import lustre/element/html // ELEMENTS -------------------------------------------------------------------- -pub fn divider( +pub fn element( attributes: List(Attribute(msg)), children: List(Element(msg)), ) -> Element(msg) { diff --git a/src/lustre/ui/input.gleam b/src/lustre/ui/input.gleam index 6515e0d..16f9fc4 100644 --- a/src/lustre/ui/input.gleam +++ b/src/lustre/ui/input.gleam @@ -6,7 +6,7 @@ import lustre/element/html // ELEMENTS -------------------------------------------------------------------- -pub fn input(attributes: List(Attribute(msg))) -> Element(msg) { +pub fn element(attributes: List(Attribute(msg))) -> Element(msg) { html.input([attribute.class("lustre-ui-input"), ..attributes]) } diff --git a/src/lustre/ui/primitives/collapse.gleam b/src/lustre/ui/primitives/collapse.gleam index 31cbdfd..54ed359 100644 --- a/src/lustre/ui/primitives/collapse.gleam +++ b/src/lustre/ui/primitives/collapse.gleam @@ -12,7 +12,7 @@ import gleam/string import lustre import lustre/attribute.{type Attribute, attribute} import lustre/effect.{type Effect} -import lustre/element.{type Element, element} +import lustre/element.{type Element} import lustre/element/html import lustre/event @@ -38,12 +38,12 @@ pub fn register() -> Result(Nil, lustre.Error) { // The size of the `content` element is measured whenever it changes (but not // if its children change) and the collapse will adjust its height accordingly. // -pub fn collapse( +pub fn element( attributes: List(Attribute(msg)), trigger trigger: Element(msg), content content: Element(msg), ) -> Element(msg) { - element(name, attributes, [ + element.element(name, attributes, [ html.div([attribute("slot", "trigger")], [trigger]), content, ]) diff --git a/src/lustre/ui/primitives/popover.gleam b/src/lustre/ui/primitives/popover.gleam index 0e09c95..bc1eb38 100644 --- a/src/lustre/ui/primitives/popover.gleam +++ b/src/lustre/ui/primitives/popover.gleam @@ -11,7 +11,7 @@ import gleam/string import lustre import lustre/attribute.{type Attribute, attribute} import lustre/effect.{type Effect} -import lustre/element.{type Element, element} +import lustre/element.{type Element} import lustre/element/html import lustre/event @@ -42,12 +42,12 @@ pub fn register() -> Result(Nil, lustre.Error) { lustre.register(app, name) } -pub fn popover( +pub fn element( attributes: List(Attribute(msg)), trigger trigger: Element(msg), content content: Element(msg), ) -> Element(msg) { - element(name, attributes, [ + element.element(name, attributes, [ html.div([attribute("slot", "trigger")], [trigger]), html.div([attribute("slot", "popover")], [content]), ]) diff --git a/src/lustre/ui/reveal.gleam b/src/lustre/ui/reveal.gleam index ff1deb1..df564e3 100644 --- a/src/lustre/ui/reveal.gleam +++ b/src/lustre/ui/reveal.gleam @@ -10,7 +10,7 @@ import gleam/string import lustre import lustre/attribute.{type Attribute, attribute} import lustre/effect.{type Effect} -import lustre/element.{type Element, element} +import lustre/element.{type Element} import lustre/element/html import lustre/event import lustre/ui/tween.{tween} @@ -50,8 +50,8 @@ fn register_intersection_observer() -> Result(Nil, lustre.Error) // The size of the `content` element is measured whenever it changes (but not // if its children change) and the collapse will adjust its height accordingly. // -pub fn reveal(attributes: List(Attribute(msg))) -> Element(msg) { - element(name, attributes, []) +pub fn element(attributes: List(Attribute(msg))) -> Element(msg) { + element.element(name, attributes, []) } // ATTRIBUTES ------------------------------------------------------------------ @@ -265,7 +265,7 @@ fn view(model: Model) -> Element(Msg) { let target = float.round(model.target) let value = float.round(model.value) - element( + element.element( "lustre-ui-intersection-observer", [event.on("intersection", handle_intersection)], [ diff --git a/src/lustre/ui/ticker.gleam b/src/lustre/ui/ticker.gleam index 9075bf2..552e5de 100644 --- a/src/lustre/ui/ticker.gleam +++ b/src/lustre/ui/ticker.gleam @@ -9,7 +9,7 @@ import gleam/result import lustre import lustre/attribute.{type Attribute, attribute} import lustre/effect.{type Effect} -import lustre/element.{type Element, element} +import lustre/element.{type Element} import lustre/element/html import lustre/event import lustre/ui/tween.{tween} @@ -49,8 +49,8 @@ fn register_intersection_observer() -> Result(Nil, lustre.Error) // The size of the `content` element is measured whenever it changes (but not // if its children change) and the collapse will adjust its height accordingly. // -pub fn ticker(attributes: List(Attribute(msg))) -> Element(msg) { - element(name, attributes, []) +pub fn element(attributes: List(Attribute(msg))) -> Element(msg) { + element.element(name, attributes, []) } // ATTRIBUTES ------------------------------------------------------------------ @@ -250,7 +250,7 @@ fn view(model: Model) -> Element(Msg) { let to = float.round(model.to) let value = float.round(model.value) - element( + element.element( "lustre-ui-intersection-observer", [event.on("intersection", handle_intersection)], [value |> int.min(to) |> int.to_string |> html.text], From f313aeb015cf3814dfe002fae29746e32286ef30 Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Sat, 19 Jul 2025 06:08:35 +0100 Subject: [PATCH 06/56] :construction: --- .gitignore | 2 - README.md | 18 + dev/build.gleam | 80 + dev/lustre/ui/accordion_stories.gleam | 27 + dev/lustre/ui/alert_stories.gleam | 43 + dev/lustre/ui/badge_stories.gleam | 36 + dev/lustre/ui/breadcrumb_stories.gleam | 46 + dev/lustre/ui/button_stories.gleam | 32 + dev/lustre/ui/card_stories.gleam | 49 + dev/lustre/ui/combobox_stories.gleam | 35 + dev/lustre/ui/divider_stories.gleam | 52 + dev/lustre_ui_storybook.gleam | 34 + gleam.toml | 14 +- index.html | 18 + manifest.toml | 80 +- pages/01-getting-started.md | 225 + pages/02-elements-vs-components.md | 236 + priv/static/lustre_ui.css | 1801 +-- priv/static/lustre_ui.min.css | 1 + priv/static/lustre_ui_components.min.mjs | 15 + priv/static/lustre_ui_components.mjs | 8768 +++++++++++++ priv/static/lustre_ui_no_reset.css | 1151 ++ priv/static/lustre_ui_no_reset.min.css | 1 + priv/static/lustre_ui_storybook.mjs | 14280 +++++++++++++++++++++ src/dom.ffi.mjs | 175 - src/lustre/ffi/dom.ffi.mjs | 72 + src/lustre/ffi/dom.gleam | 138 + src/lustre/ui/accordion.gleam | 186 +- src/lustre/ui/alert.gleam | 22 +- src/lustre/ui/badge.gleam | 20 +- src/lustre/ui/button.gleam | 65 +- src/lustre/ui/card.gleam | 113 +- src/lustre/ui/checkbox.gleam | 47 +- src/lustre/ui/colour.gleam | 66 +- src/lustre/ui/combobox.gleam | 537 +- src/lustre/ui/divider.gleam | 82 +- src/lustre/ui/primitives/collapse.gleam | 149 +- src/lustre/ui/primitives/popover.gleam | 154 +- src/lustre/ui/reveal.gleam | 295 - src/lustre/ui/theme.gleam | 181 +- src/lustre/ui/ticker.gleam | 275 - src/lustre/ui/tween.gleam | 575 - src/lustre_ui_components.mjs | 11 + src/scheduler.ffi.mjs | 14 - test/build.gleam | 27 - test/lustre/ui/tween_test.gleam | 200 - 46 files changed, 27333 insertions(+), 3115 deletions(-) create mode 100644 dev/build.gleam create mode 100644 dev/lustre/ui/accordion_stories.gleam create mode 100644 dev/lustre/ui/alert_stories.gleam create mode 100644 dev/lustre/ui/badge_stories.gleam create mode 100644 dev/lustre/ui/breadcrumb_stories.gleam create mode 100644 dev/lustre/ui/button_stories.gleam create mode 100644 dev/lustre/ui/card_stories.gleam create mode 100644 dev/lustre/ui/combobox_stories.gleam create mode 100644 dev/lustre/ui/divider_stories.gleam create mode 100644 dev/lustre_ui_storybook.gleam create mode 100644 index.html create mode 100644 pages/01-getting-started.md create mode 100644 pages/02-elements-vs-components.md create mode 100644 priv/static/lustre_ui.min.css create mode 100644 priv/static/lustre_ui_components.min.mjs create mode 100644 priv/static/lustre_ui_components.mjs create mode 100644 priv/static/lustre_ui_no_reset.css create mode 100644 priv/static/lustre_ui_no_reset.min.css create mode 100644 priv/static/lustre_ui_storybook.mjs delete mode 100644 src/dom.ffi.mjs create mode 100644 src/lustre/ffi/dom.ffi.mjs create mode 100644 src/lustre/ffi/dom.gleam delete mode 100644 src/lustre/ui/reveal.gleam delete mode 100644 src/lustre/ui/ticker.gleam delete mode 100644 src/lustre/ui/tween.gleam create mode 100644 src/lustre_ui_components.mjs delete mode 100644 src/scheduler.ffi.mjs delete mode 100644 test/build.gleam delete mode 100644 test/lustre/ui/tween_test.gleam diff --git a/.gitignore b/.gitignore index fef0b97..4e26457 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,3 @@ build erl_crash.dump node_modules -example/index.html -example/priv diff --git a/README.md b/README.md index 1af794f..1f75d48 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,24 @@ gleam add lustre_ui@1.0.0-rc.1 Ensure the required CSS is rendered in your apps by serving the stylesheet found in the `priv/static` directory of this package! +## Usage + +In order for lustre/ui to work correctly you need to do two things: + +1. Include the lustre/ui stylesheet in your app. This can be found in the + `priv/static` directory for _this package_ and can either be served by your + backend (in case of a full stack application) or copied or otherwise included + from `build/packages/lustre_ui/priv/static` in your frontend application. + +2. Construct a theme from the `lustre/ui/theme` module and render the dynamic + style element by calling `theme.to_style` or wrapping your application's view + function using `theme.inject`. + +**Both** of these steps must be done in order for the components to render +correctly. The base stylesheet includes the necessary CSS rules and classes for +every component, and your theme defines the design tokens and CSS variables used +by those components. + ## Support Lustre is mostly built by just me, [Hayleigh](https://github.com/hayleigh-dot-dev), diff --git a/dev/build.gleam b/dev/build.gleam new file mode 100644 index 0000000..41e1c48 --- /dev/null +++ b/dev/build.gleam @@ -0,0 +1,80 @@ +import esgleam +import esgleam/mod/install +import gleam/list +import gleam/result +import globlin +import simplifile + +pub fn main() { + case simplifile.is_file("build/dev/bin/package/bin/esbuild") { + Ok(True) -> Nil + _ -> install.fetch() + } + + // Process CSS files + let assert Ok(_) = process_css(True) + let assert Ok(_) = process_css(False) + + // Process JS files + let assert Ok(_) = bundle_js(False) + let assert Ok(_) = bundle_js(True) +} + +fn process_css(include_reset: Bool) { + let assert Ok(files) = simplifile.get_files("./src") + let assert Ok(pattern) = globlin.new_pattern("./**/*.css") + let initial_layers = case include_reset { + True -> "@layer reset, primitives, components;\n\n" + False -> "@layer primitives, components;\n\n" + } + + let assert Ok(css) = + files + |> list.filter(globlin.match_pattern(pattern:, path: _)) + |> list.try_fold(initial_layers, fn(css, path) { + use src <- result.map(simplifile.read(path)) + let src = case path { + "./src/lustre/ui/primitives/reset.css" if include_reset -> + case include_reset { + True -> "@layer reset {\n" <> src <> "\n}" + False -> "" + } + + "./src/lustre/ui/primitives/" <> _ -> + "@layer primitives {\n" <> src <> "\n}" + + _ -> "@layer components {\n" <> src <> "\n}" + } + + css <> src <> "\n\n" + }) + + let filename = case include_reset { + True -> "lustre_ui" + False -> "lustre_ui_no_reset" + } + + let assert Ok(_) = + simplifile.write("./priv/static/" <> filename <> ".css", css) + + let assert Ok(_) = + esgleam.new("") + |> esgleam.entry("../../../../priv/static/" <> filename <> ".css") + |> esgleam.minify(True) + |> esgleam.raw("--outfile=./priv/static/" <> filename <> ".min.css") + |> esgleam.bundle +} + +fn bundle_js(minify: Bool) { + let filename = case minify { + True -> ".min.mjs" + False -> ".mjs" + } + + let assert Ok(_) = + esgleam.new("") + |> esgleam.entry("../../../../src/lustre_ui_components.mjs") + |> esgleam.minify(minify) + |> esgleam.raw("--outfile=./priv/static/lustre_ui_components" <> filename) + |> esgleam.bundle +} diff --git a/dev/lustre/ui/accordion_stories.gleam b/dev/lustre/ui/accordion_stories.gleam new file mode 100644 index 0000000..591547e --- /dev/null +++ b/dev/lustre/ui/accordion_stories.gleam @@ -0,0 +1,27 @@ +// IMPORTS --------------------------------------------------------------------- + +import lustre/dev/fable +import lustre/element/html +import lustre/ui/accordion +import lustre/ui/theme + +// STORIES --------------------------------------------------------------------- + +pub fn all() { + fable.chapter("Accordion", [faq_story()]) +} + +fn faq_story() { + use <- fable.story("FAQ") + use _ <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + accordion.element([], [ + accordion.item(value: "q1", label: "What is an accordion?", content: [ + html.text("An interactive element for showing/hiding content"), + ]), + accordion.item(value: "q2", label: "When should I use one?", content: [ + html.text("When you want to organize content into sections"), + ]), + ]) +} diff --git a/dev/lustre/ui/alert_stories.gleam b/dev/lustre/ui/alert_stories.gleam new file mode 100644 index 0000000..2b76707 --- /dev/null +++ b/dev/lustre/ui/alert_stories.gleam @@ -0,0 +1,43 @@ +// IMPORTS --------------------------------------------------------------------- + +import lustre/dev/fable +import lustre/element/html +import lustre/ui/accordion +import lustre/ui/alert +import lustre/ui/primitives/icon +import lustre/ui/theme + +// STORIES --------------------------------------------------------------------- + +pub fn all() { + let assert Ok(_) = accordion.register() + + fable.chapter("Alert", [ + title_only_success_alert_story(), + title_indicator_content_error_alert_story(), + ]) +} + +fn title_only_success_alert_story() { + use <- fable.story("Title only, success") + use _ <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + alert.element([alert.success()], [ + alert.title([], [html.text("New todo added to your list.")]), + ]) +} + +fn title_indicator_content_error_alert_story() { + use <- fable.story("Title + indicator + content, error") + use _ <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + alert.element([alert.danger()], [ + alert.indicator(icon.exclamation_triangle([])), + alert.title([], [html.text("Could not delete todo")]), + alert.content([], [ + html.p([], [html.text("Check your internet connection and try again.")]), + ]), + ]) +} diff --git a/dev/lustre/ui/badge_stories.gleam b/dev/lustre/ui/badge_stories.gleam new file mode 100644 index 0000000..76fcf42 --- /dev/null +++ b/dev/lustre/ui/badge_stories.gleam @@ -0,0 +1,36 @@ +// IMPORTS --------------------------------------------------------------------- + +import lustre/attribute +import lustre/dev/fable +import lustre/element/html +import lustre/ui/badge +import lustre/ui/theme + +// STORIES --------------------------------------------------------------------- + +pub fn all() { + fable.chapter("Badge", [online_avatar_story()]) +} + +fn online_avatar_story() { + use <- fable.story("Online Avatar Indicator") + use _ <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + html.div([attribute.class("inline-block relative")], [ + html.img([ + attribute.class("h-10 w-10 rounded-full"), + attribute.src("https://placehold.co/100"), + attribute.alt("Avatar"), + ]), + badge.element( + [ + badge.background("green"), + badge.solid(), + attribute.class("absolute top-0 right-0"), + attribute.title("online"), + ], + [], + ), + ]) +} diff --git a/dev/lustre/ui/breadcrumb_stories.gleam b/dev/lustre/ui/breadcrumb_stories.gleam new file mode 100644 index 0000000..a17f9e8 --- /dev/null +++ b/dev/lustre/ui/breadcrumb_stories.gleam @@ -0,0 +1,46 @@ +// IMPORTS --------------------------------------------------------------------- + +import lustre/attribute +import lustre/dev/fable +import lustre/element/html +import lustre/ui/breadcrumb +import lustre/ui/theme + +// STORIES --------------------------------------------------------------------- + +pub fn all() { + fable.chapter("Breadcrumb", [ + basic_breadcrumb_story(), + breadcrumb_with_collapsed_items_story(), + ]) +} + +fn basic_breadcrumb_story() { + use <- fable.story("Basic Breadcrumb Navigation") + use _ <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + breadcrumb.element([], [ + breadcrumb.item([], [html.a([attribute.href("/")], [html.text("Home")])]), + breadcrumb.chevron([]), + breadcrumb.item([], [ + html.a([attribute.href("/documents")], [html.text("Documents")]), + ]), + breadcrumb.chevron([]), + breadcrumb.current([], [html.text("My Document")]), + ]) +} + +fn breadcrumb_with_collapsed_items_story() { + use <- fable.story("Breadcrumb with Collapsed Items") + use _ <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + breadcrumb.element([], [ + breadcrumb.item([], [html.a([attribute.href("/")], [html.text("Home")])]), + breadcrumb.slash([]), + breadcrumb.ellipsis([], "Collapsed navigation items"), + breadcrumb.slash([]), + breadcrumb.current([], [html.text("My Document")]), + ]) +} diff --git a/dev/lustre/ui/button_stories.gleam b/dev/lustre/ui/button_stories.gleam new file mode 100644 index 0000000..7989ab1 --- /dev/null +++ b/dev/lustre/ui/button_stories.gleam @@ -0,0 +1,32 @@ +// IMPORTS --------------------------------------------------------------------- + +import lustre/dev/fable +import lustre/element/html +import lustre/ui/button +import lustre/ui/primitives/icon +import lustre/ui/theme + +// STORIES --------------------------------------------------------------------- + +pub fn all() { + fable.chapter("Button", [basic_button_story(), command_palette_button_story()]) +} + +fn basic_button_story() { + use <- fable.story("Basic Button with Icon and Text") + use _ <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + button.element([], [icon.bookmark([]), html.text(" Save")]) +} + +fn command_palette_button_story() { + use <- fable.story("Command Palette Button with Keyboard Shortcut") + use _ <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + button.element([button.solid()], [ + html.text("Open"), + button.shortcut_badge([], ["⌘", "k"]), + ]) +} diff --git a/dev/lustre/ui/card_stories.gleam b/dev/lustre/ui/card_stories.gleam new file mode 100644 index 0000000..bfd0865 --- /dev/null +++ b/dev/lustre/ui/card_stories.gleam @@ -0,0 +1,49 @@ +// IMPORTS --------------------------------------------------------------------- + +import lustre/dev/fable +import lustre/element/html +import lustre/ui/button +import lustre/ui/card +import lustre/ui/theme + +// STORIES --------------------------------------------------------------------- + +pub fn all() { + fable.chapter("Card", [basic_card_story(), dialog_card_story()]) +} + +fn basic_card_story() { + use <- fable.story("Basic Content Card with Header and Content") + use _ <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + card.element([], [ + card.header([], [html.h2([], [html.text("Easy Chocolate Chip Cookies")])]), + card.content([], [ + html.p([], [ + html.text( + "A simple recipe for delicious chocolate chip cookies that are crisp at the edges and chewy in the middle.", + ), + ]), + ]), + ]) +} + +fn dialog_card_story() { + use <- fable.story("Dialog-Style Card with Footer Actions") + use _ <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + card.element([], [ + card.header([], [html.text("Delete recipe?")]), + card.content([], [ + html.p([], [ + html.text("Are you sure that you want to delete this recipe?"), + ]), + ]), + card.footer([], [ + button.element([button.clear()], [html.text("Cancel")]), + button.element([button.solid(), button.danger()], [html.text("Delete")]), + ]), + ]) +} diff --git a/dev/lustre/ui/combobox_stories.gleam b/dev/lustre/ui/combobox_stories.gleam new file mode 100644 index 0000000..5f13554 --- /dev/null +++ b/dev/lustre/ui/combobox_stories.gleam @@ -0,0 +1,35 @@ +// IMPORTS --------------------------------------------------------------------- + +import lustre/dev/fable +import lustre/ui/combobox +import lustre/ui/theme + +// STORIES --------------------------------------------------------------------- + +pub fn all() { + let assert Ok(_) = combobox.register() + + fable.chapter("Combobox", [basic_story()]) +} + +fn basic_story() { + use <- fable.story("Typeahead filter") + use value <- fable.input("Value", "gleam") + use controls <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + combobox.element( + [ + combobox.value(fable.get(controls, value)), + combobox.on_change(fable.set(value, _)), + ], + [ + combobox.option(value: "gleam", label: "Gleam"), + combobox.option(value: "go", label: "Go"), + combobox.option(value: "javascript", label: "JavaScript"), + combobox.option(value: "kotlin", label: "Kotlin"), + combobox.option(value: "rust", label: "Rust"), + combobox.option(value: "typescript", label: "TypeScript"), + ], + ) +} diff --git a/dev/lustre/ui/divider_stories.gleam b/dev/lustre/ui/divider_stories.gleam new file mode 100644 index 0000000..2febd01 --- /dev/null +++ b/dev/lustre/ui/divider_stories.gleam @@ -0,0 +1,52 @@ +// IMPORTS --------------------------------------------------------------------- + +import lustre/dev/fable +import lustre/element/html +import lustre/ui/divider +import lustre/ui/theme + +// STORIES --------------------------------------------------------------------- + +pub fn all() { + fable.chapter("Divider", [basic_divider_story(), text_label_divider_story()]) +} + +fn basic_divider_story() { + use <- fable.story("Basic Divider with No Content") + use _ <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + html.div([], [ + html.p([], [ + html.text( + "This is some content before the divider. The divider below has no content and serves as a simple horizontal rule.", + ), + ]), + divider.element([], []), + html.p([], [ + html.text( + "This is some content after the divider. Notice how the divider creates a clear separation between content sections.", + ), + ]), + ]) +} + +fn text_label_divider_story() { + use <- fable.story("Divider with Text Label") + use _ <- fable.scene + use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) + + html.div([], [ + html.p([], [html.text("Sign in with your email and password")]), + html.div([], [ + // Form fields would go here in a real implementation + html.p([], [html.text("(Form fields placeholder)")]), + ]), + divider.element([], [html.text("OR")]), + html.p([], [html.text("Continue with social login")]), + html.div([], [ + // Social login buttons would go here + html.p([], [html.text("(Social login buttons placeholder)")]), + ]), + ]) +} diff --git a/dev/lustre_ui_storybook.gleam b/dev/lustre_ui_storybook.gleam new file mode 100644 index 0000000..3ae72e9 --- /dev/null +++ b/dev/lustre_ui_storybook.gleam @@ -0,0 +1,34 @@ +// IMPORTS --------------------------------------------------------------------- + +import lustre +import lustre/dev/fable +import lustre/element +import lustre/element/html +import lustre/ui/accordion_stories +import lustre/ui/alert_stories +import lustre/ui/badge_stories +import lustre/ui/breadcrumb_stories +import lustre/ui/button_stories +import lustre/ui/card_stories +import lustre/ui/combobox +import lustre/ui/combobox_stories +import lustre/ui/divider_stories + +// MAIN ------------------------------------------------------------------------ + +pub fn main() { + let book = + fable.book("Lustre UI", [ + accordion_stories.all(), + alert_stories.all(), + badge_stories.all(), + breadcrumb_stories.all(), + button_stories.all(), + card_stories.all(), + combobox_stories.all(), + divider_stories.all(), + fable.external_stylesheet("/priv/static/lustre_ui.css"), + ]) + + fable.start(book) +} diff --git a/gleam.toml b/gleam.toml index 0d45311..6908786 100644 --- a/gleam.toml +++ b/gleam.toml @@ -18,15 +18,17 @@ internal_modules = [ ] [dependencies] -gleam_community_colour = ">= 1.4.0 and < 2.0.0" -gleam_community_maths = ">= 1.1.1 and < 2.0.0" -gleam_stdlib = ">= 0.40.0 and < 2.0.0" -lustre = ">= 4.5.0 and < 5.0.0" -decipher = ">= 1.2.1 and < 2.0.0" -gleam_json = ">= 2.0.0 and < 3.0.0" +gleam_community_colour = ">= 2.0.0 and < 3.0.0" +gleam_community_maths = ">= 2.0.0 and < 3.0.0" +gleam_json = ">= 3.0.0 and < 4.0.0" +gleam_stdlib = ">= 0.60.0 and < 2.0.0" +lustre = ">= 5.0.0 and < 6.0.0" [dev-dependencies] birdie = ">= 1.1.6 and < 2.0.0" +esgleam = ">= 0.7.0 and < 1.0.0" gleeunit = "~> 1.0" globlin = ">= 2.0.2 and < 3.0.0" +lustre_fable = { git = "https://github.com/lustre-labs/fable.git", ref = "main" } simplifile = ">= 2.2.0 and < 3.0.0" +lustre_dev_tools = ">= 1.8.2 and < 2.0.0" diff --git a/index.html b/index.html new file mode 100644 index 0000000..7337e9d --- /dev/null +++ b/index.html @@ -0,0 +1,18 @@ + + + + + + + 🚧 lustre_ui + + + + + +
+ + diff --git a/manifest.toml b/manifest.toml index 17f3365..9586104 100644 --- a/manifest.toml +++ b/manifest.toml @@ -3,40 +3,70 @@ packages = [ { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, - { name = "birdie", version = "1.2.4", build_tools = ["gleam"], requirements = ["argv", "edit_distance", "filepath", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_stdlib", "justin", "rank", "simplifile", "trie_again"], otp_app = "birdie", source = "hex", outer_checksum = "769AE13AB5B5B84E724E9966037DCCB5BD63B2F43C52EF80B4BF3351F64E469E" }, - { name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" }, - { name = "decipher", version = "1.2.1", build_tools = ["gleam"], requirements = ["birl", "gleam_json", "gleam_stdlib", "stoiridh_version"], otp_app = "decipher", source = "hex", outer_checksum = "4F82516A5FF09BD7DF352DE38F1691C2254508066152F5DEA8665B216A9C9909" }, + { name = "birdie", version = "1.3.1", build_tools = ["gleam"], requirements = ["argv", "edit_distance", "filepath", "glance", "gleam_community_ansi", "gleam_stdlib", "justin", "rank", "simplifile", "term_size", "trie_again"], otp_app = "birdie", source = "hex", outer_checksum = "F811C9EDAF920EF48597A26E788907AAF80D9239A5E8C8CCFBD0DD1BB10184D7" }, + { name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" }, { name = "edit_distance", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "edit_distance", source = "hex", outer_checksum = "A1E485C69A70210223E46E63985FA1008B8B2DDA9848B7897469171B29020C05" }, - { name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" }, - { name = "glance", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "E155BA1A787FD11827048355021C0390D2FE9A518485526F631A9D472858CC6D" }, - { name = "gleam_community_ansi", version = "1.4.1", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "4CD513FC62523053E62ED7BAC2F36136EC17D6A8942728250A9A00A15E340E4B" }, - { name = "gleam_community_colour", version = "1.4.1", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "386CB9B01B33371538672EEA8A6375A0A0ADEF41F17C86DDCB81C92AD00DA610" }, - { name = "gleam_community_maths", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_maths", source = "hex", outer_checksum = "6C4ED7BC7E7DF6977719B5F2CFE717EE8280D1CF6EA81D55FD9953758C7FD14E" }, - { name = "gleam_erlang", version = "0.33.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "A1D26B80F01901B59AABEE3475DD4C18D27D58FA5C897D922FCB9B099749C064" }, - { name = "gleam_json", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "0A57FB5666E695FD2BEE74C0428A98B0FC11A395D2C7B4CDF5E22C5DD32C74C6" }, - { name = "gleam_otp", version = "0.15.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "E9ED3DF7E7285DA0C440F46AE8236ADC8475E8CCBEE4899BF09A8468DA3F9187" }, - { name = "gleam_regexp", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "A3655FDD288571E90EE9C4009B719FEF59FA16AFCDF3952A76A125AF23CF1592" }, - { name = "gleam_stdlib", version = "0.46.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "53940A91251A6BE9AEBB959D46E1CB45B510551D81342A52213850947732D4AB" }, - { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, - { name = "glexer", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "25E87F25706749E40C3CDC72D2E52AEA12260B23D14FD9E09A1B524EF393485E" }, + { name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" }, + { name = "esgleam", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_regexp", "gleam_stdlib", "simplifile"], otp_app = "esgleam", source = "hex", outer_checksum = "00D1579DF4A8FEA70B0A2C6FADCD990C912317DB467F1A5616930CEBE312B2F0" }, + { name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" }, + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, + { name = "fs", version = "11.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "fs", source = "hex", outer_checksum = "DD00A61D89EAC01D16D3FC51D5B0EB5F0722EF8E3C1A3A547CD086957F3260A9" }, + { name = "glance", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "FAA3DAC74AF71D47C67D88EB32CE629075169F878D148BB1FF225439BE30070A" }, + { name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" }, + { name = "gleam_community_colour", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "F0ACE69E3A47E913B03D3D0BB23A5563A91A4A7D20956916286068F4A9F817FE" }, + { name = "gleam_community_maths", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_yielder"], otp_app = "gleam_community_maths", source = "hex", outer_checksum = "C9EFF6BCF3C9D113EF9837655B0128CA8CAC295131E9F3468F93C1D78EC3E013" }, + { name = "gleam_crypto", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "917BC8B87DBD584830E3B389CBCAB140FFE7CB27866D27C6D0FB87A9ECF35602" }, + { name = "gleam_deque", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_deque", source = "hex", outer_checksum = "64D77068931338CF0D0CB5D37522C3E3CCA7CB7D6C5BACB41648B519CC0133C7" }, + { name = "gleam_erlang", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "7E6A5234F927C4B24F8054AB1E4572206C41F9E6D5C6C02273CB7531E7E5CED0" }, + { name = "gleam_fetch", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_javascript", "gleam_stdlib"], otp_app = "gleam_fetch", source = "hex", outer_checksum = "2CBF9F2E1C71AEBBFB13A9D5720CD8DB4263EB02FE60C5A7A1C6E17B0151C20C" }, + { name = "gleam_http", version = "4.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "0A62451FC85B98062E0907659D92E6A89F5F3C0FBE4AB8046C99936BF6F91DBC" }, + { name = "gleam_httpc", version = "4.1.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C670EBD46FC1472AD5F1F74F1D3938D1D0AC1C7531895ED1D4DDCB6F07279F43" }, + { name = "gleam_javascript", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "EF6C77A506F026C6FB37941889477CD5E4234FCD4337FF0E9384E297CB8F97EB" }, + { name = "gleam_json", version = "3.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "5BA154440B22D9800955B1AB854282FA37B97F30F409D76B0824D0A60C934188" }, + { name = "gleam_otp", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "7020E652D18F9ABAC9C877270B14160519FA0856EE80126231C505D719AD68DA" }, + { name = "gleam_package_interface", version = "3.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_package_interface", source = "hex", outer_checksum = "8F2D19DE9876D9401BB0626260958A6B1580BB233489C32831FE74CE0ACAE8B4" }, + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, + { name = "gleam_stdlib", version = "0.60.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "621D600BB134BC239CB2537630899817B1A42E60A1D46C5E9F3FAE39F88C800B" }, + { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, + { name = "gleeunit", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D33B7736CF0766ED3065F64A1EBB351E72B2E8DE39BAFC8ADA0E35E92A6A934F" }, + { name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" }, + { name = "glint", version = "1.2.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "2214C7CEFDE457CEE62140C3D4899B964E05236DA74E4243DFADF4AF29C382BB" }, + { name = "glisten", version = "8.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "17B3CC2E5093662404DDCF7C837D1CA093E5C436CE5F8A532F8EA0D12B5B2172" }, { name = "globlin", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_regexp", "gleam_stdlib"], otp_app = "globlin", source = "hex", outer_checksum = "923BC16814DF95C4BB28111C266F0B873499480E6E125D5A16DBDF732E62CEB4" }, + { name = "gramps", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "D213EEE41B467853E1FB9AAC204D2CB1AB301C84E8F7C1DF3307128221AB53BF" }, + { name = "houdini", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "houdini", source = "hex", outer_checksum = "5BA517E5179F132F0471CB314F27FE210A10407387DA1EA4F6FD084F74469FC2" }, + { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, - { name = "lustre", version = "4.6.3", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "BDF833368F6C8F152F948D5B6A79866E9881CB80CB66C0685B3327E7DCBFB12F" }, - { name = "ranger", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "B8F3AFF23A3A5B5D9526B8D18E7C43A7DFD3902B151B97EC65397FE29192B695" }, + { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, + { name = "lustre", version = "5.2.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "DCD121F8E6B7E179B27D9A8AEB6C828D8380E26DF2E16D078511EDAD1CA9F2A7" }, + { name = "lustre_dev_tools", version = "1.9.0", build_tools = ["gleam"], requirements = ["argv", "filepath", "fs", "gleam_community_ansi", "gleam_crypto", "gleam_deque", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_json", "gleam_otp", "gleam_package_interface", "gleam_regexp", "gleam_stdlib", "glint", "glisten", "mist", "repeatedly", "simplifile", "term_size", "tom", "wisp"], otp_app = "lustre_dev_tools", source = "hex", outer_checksum = "2132E6B2B7E89ED87C138FFE1F2CD70D859258D67222F26B5793CDACE9B07D75" }, + { name = "lustre_fable", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_regexp", "gleam_stdlib", "justin", "lustre", "modem", "rsvp"], source = "git", repo = "https://github.com/lustre-labs/fable.git", commit = "11abbc709d123f2966052812a81d86b5a7f29641" }, + { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, + { name = "mist", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "7B5F043DDF8010C580CD7131B1DBB3DE2411612E6F103EA7ECE49583F5613438" }, + { name = "modem", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "lustre"], otp_app = "modem", source = "hex", outer_checksum = "8CAACFCCE88C46A36CE71158B13F541F7A8E36BF63F59A2E554070BBCD09FE9F" }, + { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, { name = "rank", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "rank", source = "hex", outer_checksum = "5660E361F0E49CBB714CC57CC4C89C63415D8986F05B2DA0C719D5642FAD91C9" }, - { name = "simplifile", version = "2.2.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0DFABEF7DC7A9E2FF4BB27B108034E60C81BEBFCB7AB816B9E7E18ED4503ACD8" }, - { name = "stoiridh_version", version = "0.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "stoiridh_version", source = "hex", outer_checksum = "EEF8ADAB9755BD33EB202F169376F1A7797AEF90823FDCA671D8590D04FBF56B" }, + { name = "repeatedly", version = "2.1.2", build_tools = ["gleam"], requirements = [], otp_app = "repeatedly", source = "hex", outer_checksum = "93AE1938DDE0DC0F7034F32C1BF0D4E89ACEBA82198A1FE21F604E849DA5F589" }, + { name = "rsvp", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_fetch", "gleam_http", "gleam_httpc", "gleam_javascript", "gleam_json", "gleam_stdlib", "lustre"], otp_app = "rsvp", source = "hex", outer_checksum = "0C0732577712E7CB0E55F057637E62CD36F35306A5E830DC4874B83DA8CE4638" }, + { name = "simplifile", version = "2.3.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0A868DAC6063D9E983477981839810DC2E553285AB4588B87E3E9C96A7FB4CB4" }, + { name = "snag", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "7E9F06390040EB5FAB392CE642771484136F2EC103A92AE11BA898C8167E6E17" }, + { name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" }, + { name = "term_size", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "term_size", source = "hex", outer_checksum = "D00BD2BC8FB3EBB7E6AE076F3F1FF2AC9D5ED1805F004D0896C784D06C6645F1" }, + { name = "tom", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "0910EE688A713994515ACAF1F486A4F05752E585B9E3209D8F35A85B234C2719" }, { name = "trie_again", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "trie_again", source = "hex", outer_checksum = "5B19176F52B1BD98831B57FDC97BD1F88C8A403D6D8C63471407E78598E27184" }, + { name = "wisp", version = "1.8.0", build_tools = ["gleam"], requirements = ["directories", "exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "0FE9049AFFB7C8D5FC0B154EEE2704806F4D51B97F44925D69349B3F4F192957" }, ] [requirements] birdie = { version = ">= 1.1.6 and < 2.0.0" } -decipher = { version = ">= 1.2.1 and < 2.0.0" } -gleam_community_colour = { version = ">= 1.4.0 and < 2.0.0" } -gleam_community_maths = { version = ">= 1.1.1 and < 2.0.0" } -gleam_json = { version = ">= 2.0.0 and < 3.0.0" } -gleam_stdlib = { version = ">= 0.40.0 and < 2.0.0" } +esgleam = { version = ">= 0.7.0 and < 1.0.0" } +gleam_community_colour = { version = ">= 2.0.0 and < 3.0.0" } +gleam_community_maths = { version = ">= 2.0.0 and < 3.0.0" } +gleam_json = { version = ">= 3.0.0 and < 4.0.0" } +gleam_stdlib = { version = ">= 0.60.0 and < 2.0.0" } gleeunit = { version = "~> 1.0" } globlin = { version = ">= 2.0.2 and < 3.0.0" } -lustre = { version = ">= 4.5.0 and < 5.0.0" } +lustre = { version = ">= 5.0.0 and < 6.0.0" } +lustre_dev_tools = { version = ">= 1.8.2 and < 2.0.0" } +lustre_fable = { git = "https://github.com/lustre-labs/fable.git", ref = "main" } simplifile = { version = ">= 2.2.0 and < 3.0.0" } diff --git a/pages/01-getting-started.md b/pages/01-getting-started.md new file mode 100644 index 0000000..c4836b6 --- /dev/null +++ b/pages/01-getting-started.md @@ -0,0 +1,225 @@ +# 01 Getting started + +Lustre/ui is a component library for building interactive user interfaces built +around the central idea of a _theme_. The library provides a set of styled +components that draw from a common pool of design tokens and then gives you a +way to customise those tokens to fit your own style or brand. + +To see how it all comes together, let's build a simple one-button counter. This +isn't a _lustre_ tutorial, so if you're brand new to lustre you might want to +head over to the main [quickstart guide](https://hexdocs.pm/lustre/guide/01-quickstart.html) +first. + +## Setup + +Let's start by creating a new Gleam project, and adding the dependencies we'll +need. + +```sh +# Create a new gleam project and enter it +gleam new lustre_ui_quickstart && cd lustre_ui_quickstart + +# Add dependencies +gleam add lustre lustre_ui + +# Add lustre_dev_tools to access the development server +gleam add lustre_dev_tools --dev +``` + +Before we look at lustre/ui, we'll put together the application logic so we can +focus on styling for the rest of the guide. + +```gleam +import lustre +import lustre/element/html +import lustre/event + +pub fn main() { + let app = lustre.simple(init, update, view) + let assert Ok(_) = lustre.start(app, "#app", Nil) + + Nil +} + +type Model { + Model(count: Int) +} + +fn init(_) { + Model(0) +} + +type Msg { + Increment +} + +fn update(model, msg) { + case msg { + Increment -> Model(..model, count: model.count + 1 + } +} + +fn view(model) { + let count = int.to_string(model.count) + + html.button([event.on_click(Increment)], [html.text(count)]) +} +``` + +To confirm everything is working, run `gleam run -m lustre/dev start` to start +the development server and open `localhost:1234` in your browser. Clicking the +button should update the count. + +## Adding lustre/ui styles + +In order for lustre/ui to work correctly we need to do two things: + +1. Include the lustre/ui stylesheet in your app. This stylesheet contains all + the necessary CSS to style each component and can be found in your project's + build directory at `build/packages/lustre_ui/priv/static/lustre_ui.css`. + +2. Construct a new theme and dynamically inject its styles into your application. + +Let's start with the base static stylesheet. How you serve lustre/ui's stylesheet +will depend on your application and how you deploy it: common approaches include +copying the CSS file from the package into your own application's `priv/static` +directory, or serving it from a server using something like wisp's +[`serve_static`](https://hexdocs.pm/wisp/wisp.html#serve_static) function. + +For simplicity during development, we can add a `` tag to the generated +HTML document and point it directly to the stylesheet: + +```diff + + + + + + + 🚧 lustre_ui_quickstart + +- +- ++ + + + +
+ + +``` + +If you refresh the page (or restart the development server if you stopped it) you +might already notice some changes. Lustre/ui includes a **CSS reset** which is +a common way for styling frameworks to ensure consistent base styles across +browsers. + +This isn't quite enough to get going yet, though. Lustre/ui's stylesheet contains +all the styles for each component, but the look and feel of your application is +dictated by your _theme_. + +## Configuring your theme + +A theme is the collection of design tokens lustre/ui uses to style each component: +it configures things like padding, colours, and border radius. By making the theme +flexible, lustre/ui can author many components without dictating a strict visual +style! + +The library ships with a default theme, which we'll use for now by storing it in +our model. + +```diff ++ import lustre/ui/theme.{type Theme} + + type Model { ++ Model(count: Int, theme: Theme) + } + + fn init(_) { + Model(0, theme.default()) + } +``` + +Lustre/ui gives us two ways to render a theme into a stylesheet that defines all +your design tokens as CSS variables. For more control over where you insert the +stylehseet, you can use `theme.to_style`: this function is useful to injecting +the tokens into the `` of a server-rendered document. + +For SPAs and client applications, `theme.inject` can be used to wrap your `view` +function and automatically insert the stylesheet: + + +```diff + fn view(model) { ++ use <- theme.inject(model.theme) + let count = int.to_string(model.count) + + html.button([event.on_click(Increment)], [html.text(count)]) + } +``` + +## Rendering lustre/ui elements! + +We've set up everything we need to start using lustre/ui in our pages and elements. +Among other things, lustre/ui includes a [button element](https://hexdocs.pm/lustre_ui/lustre/ui/button.html) +we can use instead of the unstyled HTML button: + +```diff ++ import lustre/ui/button + + fn view(model) { + use <- theme.inject(model.theme) + let count = int.to_string(model.count) + ++ button.element([event.on_click(Increment)], [html.text(count)]) + } +``` + +All modules in lustre/ui follow the same pattern: the main element or container +in the module is always called "element" to encourage qualified usage. That means +we have `button.element`, or `checkbox.element`, or `combobox.element`. + +A module may also contain other elements intended to be used as children. In the +button's case we can add a number badge to show the count: + +```diff ++ import lustre/ui/button + + fn view(model) { + use <- theme.inject(model.theme) +- let count = int.to_string(model.count) + ++ button.element([event.on_click(Increment)], [ + html.text("Increment"), + button.count_badge([], model.count) + ]) + } +``` + +Elements in lustre/ui are designed to be flexible rather than prescriptive so +often children can be supplied in any order or combination and the element will +adapt accordingly. + +## Customising your theme + +The default theme is a great to drop in when you're just starting out, but once +your application has grown a bit you might want to start customising it to better +suit your own design. + +It's possible to construct a theme from scratch, but the `lustre/ui/theme` module +also exposes a number of builders to modify an existing theme. Let's tweak the +default theme by changing the primary colour and removing the rounded borders of +all elements: + +```diff ++ import lustre/ui/colour + + fn init(_) { ++ let theme = ++ theme.default() ++ |> theme.with_primary_scale(colour.sage()) ++ |> theme.with_radius(theme.constant_size(0.0)) + ++ Model(0, theme) + } +``` diff --git a/pages/02-elements-vs-components.md b/pages/02-elements-vs-components.md new file mode 100644 index 0000000..5707775 --- /dev/null +++ b/pages/02-elements-vs-components.md @@ -0,0 +1,236 @@ +# 02 Components vs elements + +In many frontend frameworks, the word "component" is a general term used to +describe any construct in the framework that can render something. There's often +an implicit but _optional_ ability for components to encapsulate state too. As an +example, the following two snippets are both considered React **components**: + +```jsx +export function ButtonComponent({ onClick, children }) { + return ( + + ) +} +``` + +```jsx +import { useState } from 'react' + +export function CounterComponent() { + const [count, setCount] = useState(0) + + return ( +
+ setCount(count - 1)}>Decr +

{count}

+ setCount(count + 1)}>Incr +
+ ) +} +``` + +State locality and encapsulation is a positive feature for these frameworks, and +to work out if a component does contain its own state you have to peak at the +implementation. + +In Lustre we do things a bit differently, and centralising state in your application's +`Model` is a key part of what makes the framework feel robust and approachable. +Lustre _does_ have an abstraction for stateful components though, and so for +clarity we make an explicit difference between stateless _elements_ (also known +as "view functions") and stateful _components_. + +If we take the React components from above and translate them to Lustre, we get +something like: + +```gleam +import lustre/attribute +import lustre/element/html +import lustre/event + +pub fn button_element(on_click, children) { + html.button([attribute.class("my-button"), event.on_click(on_click)], children) +} +``` + +```gleam +import lustre +import lustre/element +import lustre/element/html + +pub fn register() { + lustre.register("counter-component", lustre.simple(init, update, view)) +} + +pub fn counter_component() { + element.element("counter-component", [], []) +} + +fn init(_) { + 0 +} + +type Msg { + Incr + Decr +} + +fn update(model, msg) { + case msg { + Incr -> model + 1 + Decr -> model - 1 + } +} + +fn view(model) { + let count = int.to_string(model) + + html.div([], [ + button_element(Decr, [html.text("Decr")]), + html.p([], [html.text(count)]), + button_element(Incr, [html.text("Incr")]), + ]) +} +``` + +Woah, that's quite a bit more! Components in Lustre are complete applications +registered as custom elements, and then rendered like all other HTML elements. +Because of the set up, the number of components a typical application have will +be far fewer than other frontend frameworks and the decision to encapsulate state +in a component tends to be given more weight even as the project grows. + +Throughout Lustre's documentation - both in the core library and other packages +such as this one - the word **component** will always refer to stateful components, +and everything else will always be referred to as an "element" or "view function". +We encourage you to adopt this naming when writing your own applications or +content about Lustre, so everyone stays on the same page! + +## Why does lustre/ui need components? + +Elm - one of the frameworks Lustre is heavily inspired by - has this to say about +components: + +> Folks coming from React expect everything to be components. Actively trying to +> make components is a recipe for disaster in Elm. The root issue is that components +> are objects. It would be odd to start using Elm and wonder "how do I structure +> my application with objects?" There are no objects in Elm! + +Given that both Elm and Gleam are immutable functional programming languages, you +might question why Lustre (and lustre/ui) need components at all. In our experience +with a number of large production Elm codebases, a number of problems tend to +arise when components are not available: + +- Developers tend to naturally gravitate to quasi-components over time with modules + that contain their own `Model`, `update` and `view` to make related functionality + more manageable. + +- When more than one of the same "component" is on the page, the application model + must come up with an ad-hoc system to distinguish messages and state for each + component such as `Dict String ComponentModel`. + +- Storing component state far away from where it's (exclusively) used leads to + problems. It becomes difficult to understand the responsibilities of different + view functions when unrelated state needs to be passed down through these elements. + +There's also a reason unique to Lustre that makes components an interesting +option: + +- Components create a hard boundary between the component and its parent which + is particularly useful when taking advantage of _server components_. That boundary + makes it possible to blend server and client components in a render tree without + sending unnecessary data over the wire. + +## How to use components in lustre/ui + +You can always register a component in lustre/ui because the module will expose +a `register` function. Some examples of components available in lustre/ui include: + +- [`accordion`](https://hexdocs.pm/lustre_ui/lustre/ui/accordion.html) +- [`combobox`](https://hexdocs.pm/lustre_ui/lustre/ui/combobox.html) + +If you are using Lustre and lustre/ui to build a Single Page Application (SPA) on +the **client**, it's important to call the `register` function of any component +you intend to use. + +Typically this is done before you start your application: + +```gleam +import lustre +import lustre/ui/combobox + +pub fn main() { + let app = lustre.simple(init, update, view) + + let assert Ok(_) = combobox.register() + let assert Ok(_) = lustre.start(app, "#app", Nil) + + Nil +} +``` + +Lustre components are built on top of the [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) +standard, which means the browser needs to know about the components you want to +use before they can be rendered properly. + +If you are rendering components on the **server** either with server-side rendering +or static site generation, you should include a supplementary bundle as a ` + + + + " + + pub fn handle_request(req: Request, ctx: Context) -> Response { + use _req <- web.middleware(req, ctx) + wisp.html_response(string_tree.from_string(html), 200) + } +``` diff --git a/priv/static/lustre_ui.css b/priv/static/lustre_ui.css index 1bb6aa9..26ea7d4 100644 --- a/priv/static/lustre_ui.css +++ b/priv/static/lustre_ui.css @@ -1,1130 +1,1151 @@ @layer reset, primitives, components; @layer components { - .lustre-ui-input { - /* VARIABLES ------------------------------------------------------------ */ +.lustre-ui-input { + /* VARIABLES ------------------------------------------------------------ */ - --border: rgb(var(--lustre-ui-accent)); - --border-focus: rgb(var(--lustre-ui-primary-solid)); - --border-width: 1px; - --padding-y: var(--lustre-ui-spacing-sm); - --radius: var(--lustre-ui-radius-sm); + --border: rgb(var(--lustre-ui-accent)); + --border-focus: rgb(var(--lustre-ui-primary-solid)); + --border-width: 1px; + --padding-y: var(--lustre-ui-spacing-sm); + --radius: var(--lustre-ui-radius-sm); - /* BASE STYLES ---------------------------------------------------------- */ + /* BASE STYLES ---------------------------------------------------------- */ - background-color: rgb(var(--lustre-ui-base-bg-subtle)); - padding: var(--padding-y); - outline-offset: 0; - border-radius: var(--radius); - outline: var(--border-width) solid var(--border); + background-color: rgb(var(--lustre-ui-base-bg-subtle)); + padding: var(--padding-y); + outline-offset: 0; + border-radius: var(--radius); + outline: var(--border-width) solid var(--border); - &:focus { - outline-color: var(--border-focus); - } + &:focus { + outline-color: var(--border-focus); + } - &:invalid { - outline-color: rgb(var(--lustre-ui-danger-solid)); - } + &:invalid { + outline-color: rgb(var(--lustre-ui-danger-solid)); } +} - .lustre-ui-input-container { - /* BASE STYLES ---------------------------------------------------------- */ +.lustre-ui-input-container { + /* BASE STYLES ---------------------------------------------------------- */ - position: relative; - display: flex; - align-items: center; + position: relative; + display: flex; + align-items: center; - /* */ + /* */ - &:has(* + input) > input { - padding-left: 2rem; - } + &:has(* + input) > input { + padding-left: 2rem; + } - &:has(input + *) > input { - padding-right: 2rem; - } + &:has(input + *) > input { + padding-right: 2rem; + } - &:has(* + input) > :not(input) { - left: 0.5rem; - } + &:has(* + input) > :not(input) { + left: 0.5rem; + } - & > input + * { - left: unset !important; - right: 0.5rem; - } + & > input + * { + left: unset !important; + right: 0.5rem; + } - & > :not(input) { - position: absolute; - pointer-events: none; - width: 1rem; - height: 1rem; - } + & > :not(input) { + position: absolute; + pointer-events: none; + width: 1rem; + height: 1rem; } } +} + @layer components { - .lustre-ui-divider { - /* VARIABLES ------------------------------------------------------------ */ - - --colour: rgb(var(--lustre-ui-accent)); - --gap: var(--lustre-ui-spacing-sm); - --margin: 0; - --size-x: 2px; - --size-y: 0; - - /* BASE STYLES ---------------------------------------------------------- */ - - &:empty { - align-self: stretch; - border-color: var(--colour); - border-right-width: var(--size-y); - border-style: solid; - border-top-width: var(--size-x); - margin: var(--margin) 0; - } +.lustre-ui-divider { + /* Frameworks like Tailwind like to set the default height of
elements + to 0 which messes up our styles that expect the browser defaults. This rolls + back any of those changes. + */ + + height: initial; + width: initial; + + /* VARIABLES ------------------------------------------------------------ */ - &:not(:empty) { - align-items: center; - align-self: stretch; - display: flex; - gap: var(--gap); - justify-items: center; - margin: var(--margin) 0; - - &::before, - &::after { - background-color: var(--colour); - content: ""; - display: block; - flex-grow: 1; - height: var(--size-x); - width: var(--size-y); - } + --colour: rgb(var(--lustre-ui-accent)); + --gap: var(--lustre-ui-spacing-sm); + --margin: 0; + --size-x: 2px; + --size-y: 0; + + /* BASE STYLES ---------------------------------------------------------- */ + + &:empty { + align-self: stretch; + border-color: var(--colour); + border-right-width: var(--size-y); + border-style: solid; + border-top-width: var(--size-x); + margin: var(--margin) 0; + } + + &:not(:empty) { + align-items: center; + align-self: stretch; + display: flex; + gap: var(--gap); + justify-items: center; + margin: var(--margin) 0; + + &::before, + &::after { + background-color: var(--colour); + content: ""; + display: block; + flex-grow: 1; + height: var(--size-x); + width: var(--size-y); } } } +} + @layer components { - lustre-ui-combobox { - /* VARIABLES ------------------------------------------------------------ */ +lustre-ui-combobox { + /* VARIABLES ------------------------------------------------------------ */ - --padding-x: var(--lustre-ui-spacing-sm); - --padding-y: var(--lustre-ui-spacing-sm); - --border-width: 1px; - --radius: var(--lustre-ui-radius-sm); - - /* BASE STYLES ---------------------------------------------------------- */ - - display: inline-block; - - &::part(combobox-trigger) { - align-items: center; - border-radius: var(--radius); - border: rgb(var(--lustre-ui-accent)) solid var(--border-width); - color: rgb(var(--lustre-ui-text)); - display: flex; - gap: var(--padding-x); - outline-offset: 2px; - outline: 2px solid transparent; - padding: var(--padding-y) var(--padding-x); - width: 100%; - } + --padding-x: var(--lustre-ui-spacing-sm); + --padding-y: var(--lustre-ui-spacing-sm); + --border-width: 1px; + --radius: var(--lustre-ui-radius-sm); - &:state(trigger-focus)::part(combobox-trigger) { - border-color: rgb(var(--lustre-ui-primary-solid)); - } + /* BASE STYLES ---------------------------------------------------------- */ - &:state(empty)::part(combobox-trigger) { - color: rgb(var(--lustre-ui-text-subtle)); - } + display: inline-block; - &::part(combobox-trigger-label) { - flex: 1 1 0%; - text-align: left; - } + &::part(combobox-trigger) { + align-items: center; + border-radius: var(--radius); + border: rgb(var(--lustre-ui-accent)) solid var(--border-width); + color: rgb(var(--lustre-ui-text)); + display: flex; + gap: var(--padding-x); + outline-offset: 2px; + outline: 2px solid transparent; + padding: var(--padding-y) var(--padding-x); + width: 100%; + } - &::part(combobox-trigger-icon) { - border-radius: var(--radius); - height: 1.5rem; - padding: 0.25rem; - transform: rotate(0deg); - transition-duration: 150ms; - transition-property: transform; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - width: 1.5rem; - } + &:state(trigger-focus)::part(combobox-trigger) { + border-color: rgb(var(--lustre-ui-primary-solid)); + } - &:state(expanded)::part(combobox-trigger-icon) { - transform: rotate(-180deg); - } + &:state(empty)::part(combobox-trigger) { + color: rgb(var(--lustre-ui-text-subtle)); + } - &:is(:hover, :state(trigger-focus), :state(expanded))::part( - combobox-trigger-icon - ) { - background-color: rgb(var(--lustre-ui-tint-subtle)); - } + &::part(combobox-trigger-label) { + flex: 1 1 0%; + text-align: left; + } - &::part(combobox-options) { - background-color: rgb(var(--lustre-ui-bg)); - border: rgb(var(--lustre-ui-accent)) solid var(--border-width); - border-radius: var(--radius); - } + &::part(combobox-trigger-icon) { + border-radius: var(--radius); + height: 1.5rem; + padding: 0.25rem; + transform: rotate(0deg); + transition-duration: 150ms; + transition-property: transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + width: 1.5rem; + } - &::part(combobox-input) { - border: none; - } + &:state(expanded)::part(combobox-trigger-icon) { + transform: rotate(-180deg); + } - &::part(combobox-option) { - align-items: baseline; - cursor: pointer; - display: flex; - padding: var(--padding-y) var(--padding-x); - gap: var(--padding-x); - } + &:is(:hover, :state(trigger-focus), :state(expanded))::part( + combobox-trigger-icon + ) { + background-color: rgb(var(--lustre-ui-tint-subtle)); + } - &::part(combobox-option last) { - border-bottom-left-radius: var(--radius); - border-bottom-right-radius: var(--radius); - } + &::part(combobox-options) { + background-color: rgb(var(--lustre-ui-bg)); + border: rgb(var(--lustre-ui-accent)) solid var(--border-width); + border-radius: var(--radius); + } - &::part(combobox-option intent) { - background-color: rgb(var(--lustre-ui-tint-subtle)); - } + &::part(combobox-input) { + border: none; } -} -@layer components { - .lustre-ui-checkbox { - /* VARIABLES ------------------------------------------------------------ */ + &::part(combobox-option) { + align-items: baseline; + cursor: pointer; + display: flex; + padding: var(--padding-y) var(--padding-x); + gap: var(--padding-x); + } - --background: transparent; - --background-hover: transparent; - --border: rgb(var(--lustre-ui-accent)); - --border-hover: rgb(var(--lustre-ui-accent-strong)); - --border-width: 1px; - --size: 1rem; - --check-color: rgb(var(--lustre-ui-solid-text)); + &::part(combobox-option last) { + border-bottom-left-radius: var(--radius); + border-bottom-right-radius: var(--radius); + } + + &::part(combobox-option intent) { + background-color: rgb(var(--lustre-ui-tint-subtle)); + } +} - /* BASE STYLES ---------------------------------------------------------- */ +} - appearance: none; - background-color: var(--background); - border: var(--border-width) solid var(--border); - border-radius: var(--lustre-ui-radius-sm); - cursor: pointer; - display: grid; - height: var(--size); - margin: 0; - place-content: center; - width: var(--size); - - &::before { - background-color: var(--check-color); - clip-path: polygon( - 16% 44%, - 10% 55%, - 45% 90%, - 90% 16%, - 80% 10%, - 45% 60% - ); - content: ""; - height: calc(var(--size) * 0.6); - transform: scale(0); - transform-origin: center; - transition: 150ms transform cubic-bezier(0.4, 0, 0.2, 1); - width: calc(var(--size) * 0.6); - } +@layer components { +.lustre-ui-checkbox { + /* VARIABLES ------------------------------------------------------------ */ + + --background: transparent; + --background-hover: transparent; + --border: rgb(var(--lustre-ui-accent)); + --border-hover: rgb(var(--lustre-ui-accent-strong)); + --border-width: 1px; + --size: 1rem; + --check-color: rgb(var(--lustre-ui-solid-text)); + + /* BASE STYLES ---------------------------------------------------------- */ + + appearance: none; + background-color: var(--background); + border: var(--border-width) solid var(--border); + border-radius: var(--lustre-ui-radius-sm); + cursor: pointer; + display: grid; + height: var(--size); + margin: 0; + place-content: center; + width: var(--size); + + &::before { + background-color: var(--check-color); + clip-path: polygon( + 16% 44%, + 10% 55%, + 45% 90%, + 90% 16%, + 80% 10%, + 45% 60% + ); + content: ""; + height: calc(var(--size) * 0.6); + transform: scale(0); + transform-origin: center; + transition: 150ms transform cubic-bezier(0.4, 0, 0.2, 1); + width: calc(var(--size) * 0.6); + } - &:checked { - background-color: rgb(var(--lustre-ui-solid)); - border-color: transparent; - } + &:checked { + background-color: rgb(var(--lustre-ui-solid)); + border-color: transparent; + } - &:checked::before { - transform: scale(1); - } + &:checked::before { + transform: scale(1); } } +} + @layer components { - .lustre-ui-card { - /* VARIABLES ------------------------------------------------------------ */ +.lustre-ui-card { + /* VARIABLES ------------------------------------------------------------ */ - --background: rgb(var(--lustre-ui-bg)); - --border: rgb(var(--lustre-ui-accent-subtle)); - --border-width: 1px; - --padding-x: var(--lustre-ui-spacing-xl); - --padding-y: var(--lustre-ui-spacing-xl); - --radius: var(--lustre-ui-radius-md); + --background: rgb(var(--lustre-ui-bg)); + --border: rgb(var(--lustre-ui-accent-subtle)); + --border-width: 1px; + --padding-x: var(--lustre-ui-spacing-md); + --padding-y: var(--lustre-ui-spacing-md); + --radius: var(--lustre-ui-radius-md); - /* BASE STYLES ---------------------------------------------------------- */ + /* BASE STYLES ---------------------------------------------------------- */ - background: var(--background); - border: var(--border-width) solid var(--border); - border-radius: var(--radius); + background: var(--background); + border: var(--border-width) solid var(--border); + border-radius: var(--radius); - & > * { - padding-top: 0; - padding: var(--padding-y) var(--padding-x); - } + & > * { + padding-top: 0; + padding: var(--padding-y) var(--padding-x); + } - & > .card-header { - display: flex; - flex-direction: column; - } + & > .card-header { + display: flex; + flex-direction: column; + } - & > .card-footer { - align-items: center; - display: flex; - } + & > .card-footer { + align-items: center; + display: flex; } } +} + @layer components { - .lustre-ui-button { - /* VARIABLES ------------------------------------------------------------ */ +.lustre-ui-button { + /* VARIABLES ------------------------------------------------------------ */ + + --background: rgb(var(--lustre-ui-tint)); + --background-hover: rgb(var(--lustre-ui-tint-strong)); + --border: transparent; + --border-hover: transparent; + --border-width: 1px; + --height: 2rem; + --height-min: 2rem; + --padding-x: var(--lustre-ui-spacing-sm); + --radius: var(--lustre-ui-radius-sm); + --text: rgb(var(--lustre-ui-text)); + + /* BASE STYLES ---------------------------------------------------------- */ + + align-items: center; + background-color: var(--background); + border: var(--border-width) solid var(--border); + border-radius: var(--radius); + color: var(--text); + display: inline-flex; + gap: var(--padding-x); + justify-content: center; + min-height: var(--height-min); + padding-left: var(--padding-x); + padding-right: var(--padding-x); + white-space: nowrap; + + &:disabled { + pointer-events: none; + opacity: 0.5; + cursor: default; + } - --background: rgb(var(--lustre-ui-tint)); - --background-hover: rgb(var(--lustre-ui-tint-strong)); - --border: transparent; - --border-hover: transparent; - --border-width: 1px; - --height: 2rem; - --height-min: 2rem; - --padding-x: var(--lustre-ui-spacing-sm); - --radius: var(--lustre-ui-radius-sm); - --text: rgb(var(--lustre-ui-text)); + &:hover, + &:focus { + outline-offset: 2px; + outline: 2px solid transparent; + background-color: var(--background-hover); + border-color: var(--border-hover); + } - /* BASE STYLES ---------------------------------------------------------- */ + &:active { + transform: translateY(1px); + } - align-items: center; - background-color: var(--background); - border: var(--border-width) solid var(--border); - border-radius: var(--radius); - color: var(--text); - display: inline-flex; - gap: var(--padding-x); - justify-content: center; - min-height: var(--height-min); - padding-left: var(--padding-x); - padding-right: var(--padding-x); - white-space: nowrap; - - &:disabled { - pointer-events: none; - opacity: 0.5; - cursor: default; + @media (prefers-reduced-motion) { + &:active { + transform: unset; } + } - &:hover, - &:focus { - outline-offset: 2px; - outline: 2px solid transparent; - background-color: var(--background-hover); - border-color: var(--border-hover); - } + &:is(a) { + text-decoration: none; - &:active { - transform: translateY(1px); + &:visited { + color: var(--text); } + } + + /* SIZE VARIANTS -------------------------------------------------------- */ - &:is(a) { - text-decoration: none; + &.button-icon { + --height: 2rem; + width: var(--height); - &:visited { - color: var(--text); - } + & > svg { + height: 100%; + width: 100%; } + } - /* SIZE VARIANTS -------------------------------------------------------- */ + &.button-small { + --height: 2rem; + --padding-x: var(--lustre-ui-spacing-sm); + } - &.button-icon { - --height: 2rem; - width: var(--height); + &.button-medium { + --height: 2.25rem; + --padding-x: var(--lustre-ui-spacing-md); + } - & > svg { - height: 100%; - width: 100%; - } - } + &.button-large { + --height: 2.5rem; + --padding-x: var(--lustre-ui-spacing-lg); + } - &.button-small { - --height: 2rem; - --padding-x: var(--lustre-ui-spacing-sm); - } + /* COLOUR VARIANTS ------------------------------------------------------ */ - &.button-medium { - --height: 2.25rem; - --padding-x: var(--lustre-ui-spacing-md); - } + &.button-clear { + --background: transparent; + --background-hover: rgb(var(--lustre-ui-tint)); + --border: transparent; + --border-hover: transparent; + --text: rgb(var(--lustre-ui-text)); + } - &.button-large { - --height: 2.5rem; - --padding-x: var(--lustre-ui-spacing-lg); - } + &.button-outline { + --background: transparent; + --background-hover: transparent; + --border: rgb(var(--lustre-ui-accent)); + --border-hover: rgb(var(--lustre-ui-accent-strong)); + --text: rgb(var(--lustre-ui-text)); + } - /* COLOUR VARIANTS ------------------------------------------------------ */ + &.button-soft { + --background: rgb(var(--lustre-ui-tint)); + --background-hover: rgb(var(--lustre-ui-tint-strong)); + --border: transparent; + --border-hover: transparent; + --text: rgb(var(--lustre-ui-text)); + } - &.button-clear { - --background: transparent; - --background-hover: rgb(var(--lustre-ui-tint)); - --border: transparent; - --border-hover: transparent; - --text: rgb(var(--lustre-ui-text)); - } + &.button-solid { + --background: rgb(var(--lustre-ui-solid)); + --background-hover: rgb(var(--lustre-ui-solid) / 0.8); + --border: transparent; + --border-hover: transparent; + --text: rgb(var(--lustre-ui-solid-text)); + } - &.button-outline { - --background: transparent; - --background-hover: transparent; - --border: rgb(var(--lustre-ui-accent)); - --border-hover: rgb(var(--lustre-ui-accent-strong)); - --text: rgb(var(--lustre-ui-text)); - } + /* CHILDREN ------------------------------------------------------------- */ - &.button-soft { - --background: rgb(var(--lustre-ui-tint)); - --background-hover: rgb(var(--lustre-ui-tint-strong)); - --border: transparent; - --border-hover: transparent; - --text: rgb(var(--lustre-ui-text)); - } + & .button-badge { + display: inline-flex; + align-items: center; + font-size: 0.625em; + border: 1px solid var(--text); + border-radius: var(--radius); + padding: 0 1ch; + margin: 0 calc(-1 * var(--padding-x) / 2); - &.button-solid { - --background: rgb(var(--lustre-ui-solid)); - --background-hover: rgb(var(--lustre-ui-solid) / 0.8); - --border: transparent; - --border-hover: transparent; - --text: rgb(var(--lustre-ui-solid-text)); + & .key + .key::before { + content: var(--separator, ""); } - /* CHILDREN ------------------------------------------------------------- */ - - & .button-badge { - display: inline-flex; - align-items: center; - font-size: 0.625em; - border: 1px solid var(--text); - border-radius: var(--radius); - padding: 0 1ch; - margin: 0 calc(-1 * var(--padding-x) / 2); - - & .key + .key::before { - content: var(--separator, ""); - } - - & .icon { - width: 1em; - height: 1em; - } + & .icon { + width: 1em; + height: 1em; } } } +} + @layer components { - .lustre-ui-breadcrumb { - /* VARIABLES ------------------------------------------------------------ */ +.lustre-ui-breadcrumb { + /* VARIABLES ------------------------------------------------------------ */ + + --gap: var(--lustre-ui-spacing-xs); + --text-hover: rgb(var(--lustre-ui-text)); + --text: rgb(var(--lustre-ui-text-subtle)); - --gap: var(--lustre-ui-spacing-xs); - --text-hover: rgb(var(--lustre-ui-text)); - --text: rgb(var(--lustre-ui-text-subtle)); + /* BASE STYLES ---------------------------------------------------------- */ - /* BASE STYLES ---------------------------------------------------------- */ + align-items: center; + color: var(--text); + display: flex; + flex-wrap: wrap; + gap: var(--gap); + /* CHILDREN ------------------------------------------------------------- */ + + & > .breadcrumb-item { align-items: center; - color: var(--text); - display: flex; - flex-wrap: wrap; + display: inline-flex; gap: var(--gap); - /* CHILDREN ------------------------------------------------------------- */ + &[aria-current="page"] { + color: var(--text-hover); + } - & > .breadcrumb-item { - align-items: center; - display: inline-flex; - gap: var(--gap); + &:hover { + color: var(--text-hover); + } + } - &[aria-current="page"] { - color: var(--text-hover); - } + & > .breadcrumb-separator { + user-select: none; + pointer-events: none; - &:hover { - color: var(--text-hover); - } + & > svg { + height: 1rem; + width: 1rem; } + } - & > .breadcrumb-separator { - user-select: none; - pointer-events: none; + & > .breadcrumb-ellipsis { + align-items: center; + display: flex; + height: 2rem; + justify-content: center; + user-select: none; + width: 2rem; - & > svg { - height: 1rem; - width: 1rem; - } + & > svg { + height: 1rem; + width: 1rem; } - & > .breadcrumb-ellipsis { - align-items: center; - display: flex; - height: 2rem; - justify-content: center; - user-select: none; - width: 2rem; - - & > svg { - height: 1rem; - width: 1rem; - } - - & > *:not(svg) { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border-width: 0; - } + & > *:not(svg) { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; } } } -@layer components { - .lustre-ui-badge { - /* VARIABLES ------------------------------------------------------------ */ - - --background: transparent; - --background-hover: transparent; - --border: rgb(var(--lustre-ui-accent)); - --border-hover: rgb(var(--lustre-ui-accent-strong)); - --padding-x: var(--lustre-ui-spacing-sm); - --padding-y: var(--lustre-ui-spacing-xs); - --radius: var(--lustre-ui-radius-sm); - --text: rgb(var(--lustre-ui-text)); - - /* BASE STYLES ---------------------------------------------------------- */ - - border: 1px solid var(--border); - border-radius: var(--radius); - background-color: var(--background); - color: var(--text); - display: inline-flex; - align-items: center; - padding: var(--padding-y) var(--padding-x); +} - /* Badges that can be interacted with receive additional visual styles to +@layer components { +.lustre-ui-badge { + /* VARIABLES ------------------------------------------------------------ */ + + --background: transparent; + --background-hover: transparent; + --border: rgb(var(--lustre-ui-accent)); + --border-hover: rgb(var(--lustre-ui-accent-strong)); + --padding-x: var(--lustre-ui-spacing-sm); + --padding-y: var(--lustre-ui-spacing-xs); + --radius: var(--lustre-ui-radius-sm); + --text: rgb(var(--lustre-ui-text)); + + /* BASE STYLES ---------------------------------------------------------- */ + + border: 1px solid var(--border); + border-radius: var(--radius); + background-color: var(--background); + color: var(--text); + display: inline-flex; + align-items: center; + padding: var(--padding-y) var(--padding-x); + + /* Badges that can be interacted with receive additional visual styles to indicate that to the user. */ - &:is(a, button, [tabindex]):is(:hover, :focus) { - background-color: var(--background-hover); - border-color: var(--border-hover); - cursor: pointer; - } - - &:active { - transform: translateY(1px); - } - - &:empty { - padding: max(var(--padding-y), var(--padding-x)); - border-radius: 100%; - } - - /* VARIANTS ------------------------------------------------------------- */ - - &.badge-outline { - --background: transparent; - --background-hover: transparent; - --border: rgb(var(--lustre-ui-accent)); - --border-hover: rgb(var(--lustre-ui-accent-strong)); - --text: rgb(var(--lustre-ui-text)); - } + &:is(a, button, [tabindex]):is(:hover, :focus) { + background-color: var(--background-hover); + border-color: var(--border-hover); + cursor: pointer; + } - &.badge-soft { - --background: rgb(var(--lustre-ui-tint)); - --background-hover: rgb(var(--lustre-ui-tint-strong)); - --border: transparent; - --border-hover: transparent; - --text: rgb(var(--lustre-ui-text)); - } + &:active { + transform: translateY(1px); + } - &.badge-solid { - --background: rgb(var(--lustre-ui-solid)); - --background-hover: rgb(var(--lustre-ui-solid) / 0.8); - --border: transparent; - --border-hover: transparent; - --text: rgb(var(--lustre-ui-solid-text)); - } + &:empty { + padding: max(var(--padding-y), var(--padding-x)); + border-radius: 100%; } -} -@layer components { - .lustre-ui-alert { - /* VARIABLES ------------------------------------------------------------ */ + /* VARIANTS ------------------------------------------------------------- */ - --background: rgb(var(--lustre-ui-bg)); + &.badge-outline { + --background: transparent; + --background-hover: transparent; --border: rgb(var(--lustre-ui-accent)); - --indicator-size: 16px; - --padding-x: var(--lustre-ui-spacing-lg); - --padding-y: var(--lustre-ui-spacing-md); - --radius: 0; + --border-hover: rgb(var(--lustre-ui-accent-strong)); --text: rgb(var(--lustre-ui-text)); - --title-margin: var(--padding-y); - --title-weight: 500; + } - /* BASE STYLES ---------------------------------------------------------- */ + &.badge-soft { + --background: rgb(var(--lustre-ui-tint)); + --background-hover: rgb(var(--lustre-ui-tint-strong)); + --border: transparent; + --border-hover: transparent; + --text: rgb(var(--lustre-ui-text)); + } - display: flex; - flex-direction: column; - background-color: var(--background); - border-radius: var(--radius); - border: 1px solid var(--border); - color: var(--text); - padding: var(--padding-y) var(--padding-x); - position: relative; - width: full; + &.badge-solid { + --background: rgb(var(--lustre-ui-solid)); + --background-hover: rgb(var(--lustre-ui-solid) / 0.8); + --border: transparent; + --border-hover: transparent; + --text: rgb(var(--lustre-ui-solid-text)); + } +} - /* CHILDREN ------------------------------------------------------------- */ +} - & > .alert-indicator { - position: absolute; - width: var(--indicator-size); - height: var(--indicator-size); - } +@layer components { +.lustre-ui-alert { + /* VARIABLES ------------------------------------------------------------ */ + + --background: rgb(var(--lustre-ui-bg)); + --border: rgb(var(--lustre-ui-accent)); + --indicator-size: 16px; + --padding-x: var(--lustre-ui-spacing-lg); + --padding-y: var(--lustre-ui-spacing-md); + --radius: 0; + --text: rgb(var(--lustre-ui-text)); + --title-margin: var(--padding-y); + --title-weight: 500; + + /* BASE STYLES ---------------------------------------------------------- */ + + display: flex; + flex-direction: column; + background-color: var(--background); + border-radius: var(--radius); + border: 1px solid var(--border); + color: var(--text); + padding: var(--padding-y) var(--padding-x); + position: relative; + width: full; + + /* CHILDREN ------------------------------------------------------------- */ + + & > .alert-indicator { + position: absolute; + width: var(--indicator-size); + height: var(--indicator-size); + } - & > .alert-title { - font-weight: var(--title-weight); - margin-block-end: var(--title-margin); - overflow: clip; - text-overflow: ellipsis; - } + & > .alert-title { + font-weight: var(--title-weight); + margin-block-end: var(--title-margin); + overflow: clip; + text-overflow: ellipsis; + } - /* If an indicator is present, it's important it doesn't end up placed over + /* If an indicator is present, it's important it doesn't end up placed over the top of any content we have. To make sure things are placed correctly, we calculate padding on these elements to push them further to the right by an amount based on the the indicator's size. */ - &:has(.alert-indicator) > :not(.alert-indicator) { - padding-inline-start: calc( - var(--indicator-size) + var(--padding-x) - ); - } + &:has(.alert-indicator) > :not(.alert-indicator) { + padding-inline-start: calc(var(--indicator-size) + var(--padding-x)); } } +} + @layer components { - lustre-ui-accordion { - --padding-x: var(--lustre-ui-spacing-sm); - --padding-y: var(--lustre-ui-spacing-sm); - --border: rbg(var(--lustre-ui-accent)); - --border-focus: rgb(var(--lustre-ui-primary-solid)); - --border-width: 1px; - --text: rgb(var(--lustre-ui-text)); +lustre-ui-accordion { + --padding-x: var(--lustre-ui-spacing-sm); + --padding-y: var(--lustre-ui-spacing-sm); + --border: rgb(var(--lustre-ui-accent)); + --border-focus: rgb(var(--lustre-ui-primary-solid)); + --border-width: 1px; + --text: rgb(var(--lustre-ui-text)); - display: block; + display: block; - &::part(accordion-trigger) { - align-items: center; - border-bottom: var(--border) solid var(--border-width); - color: var(--text); - display: flex; - gap: var(--padding-x); - outline-offset: 2px; - outline: 2px solid transparent; - padding: var(--padding-y) var(--padding-x); - width: 100%; - } + &::part(accordion-trigger) { + align-items: center; + border-bottom: var(--border) solid var(--border-width); + color: var(--text); + display: flex; + gap: var(--padding-x); + outline-offset: 2px; + outline: 2px solid transparent; + padding: var(--padding-y) var(--padding-x); + width: 100%; + } - &::part(accordion-trigger):focus { - border-color: var(--border-focus); - } + &::part(accordion-trigger):focus { + border-color: var(--border-focus); + } - &::part(accordion-trigger-label) { - flex: 1 1 0%; - text-align: left; - } + &::part(accordion-trigger-label) { + flex: 1 1 0%; + text-align: left; + } - &::part(accordion-trigger-icon) { - border-radius: var(--radius); - height: 1.5rem; - padding: 0.25rem; - transform: rotate(0deg); - transition-duration: 150ms; - transition-property: color transform; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - width: 1.5rem; - } + &::part(accordion-trigger-icon) { + border-radius: var(--radius); + height: 1.5rem; + padding: 0.25rem; + transform: rotate(0deg); + transition-duration: 150ms; + transition-property: color transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + width: 1.5rem; + } - &::part(accordion-trigger-icon expanded) { - transform: rotate(-180deg); - } + &::part(accordion-trigger-icon expanded) { + transform: rotate(-180deg); + } - &::part(accordion-content) { - display: block; - padding: var(--padding-y) var(--padding-x); - } + &::part(accordion-content) { + display: block; + padding: var(--padding-y) var(--padding-x); } } +} + @layer reset { - /* +/* ! tailwindcss v3.4.15 | MIT License | https://tailwindcss.com */ - *, - :after, - :before { - box-sizing: border-box; - border: 0 solid #e2e8f0; - } +*, +:after, +:before { + box-sizing: border-box; + border: 0 solid #e2e8f0; +} - :host, - html { - line-height: 1.5; - -webkit-text-size-adjust: 100%; - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - font-feature-settings: normal; - font-variation-settings: normal; - -webkit-tap-highlight-color: transparent; - } +:host, +html { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + font-feature-settings: normal; + font-variation-settings: normal; + -webkit-tap-highlight-color: transparent; +} - body { - margin: 0; - line-height: inherit; - } +body { + margin: 0; + line-height: inherit; +} - hr { - height: 0; - color: inherit; - border-top-width: 1px; - } +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} - abbr:where([title]) { - -webkit-text-decoration: underline dotted; - text-decoration: underline dotted; - } +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} - h1, - h2, - h3, - h4, - h5, - h6 { - font-size: inherit; - font-weight: inherit; - } +a { + color: inherit; + text-decoration: inherit; +} - a { - color: inherit; - text-decoration: inherit; - } +b, +strong { + font-weight: bolder; +} - b, - strong { - font-weight: bolder; - } +code, +kbd, +pre, +samp { + font-family: + ui-monospace, + SFMono-Regular, + Menlo, + Monaco, + Consolas, + Liberation Mono, + Courier New, + monospace; + font-feature-settings: normal; + font-variation-settings: normal; + font-size: 1em; +} - code, - kbd, - pre, - samp { - font-family: - ui-monospace, - SFMono-Regular, - Menlo, - Monaco, - Consolas, - Liberation Mono, - Courier New, - monospace; - font-feature-settings: normal; - font-variation-settings: normal; - font-size: 1em; - } +small { + font-size: 80%; +} - small { - font-size: 80%; - } +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} - sub, - sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; - } +sub { + bottom: -0.25em; +} - sub { - bottom: -0.25em; - } +sup { + top: -0.5em; +} - sup { - top: -0.5em; - } +table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; +} - table { - text-indent: 0; - border-color: inherit; - border-collapse: collapse; - } +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + font-size: 100%; + font-weight: inherit; + line-height: inherit; + letter-spacing: inherit; + color: inherit; + margin: 0; + padding: 0; +} - button, - input, - optgroup, - select, - textarea { - font-family: inherit; - font-feature-settings: inherit; - font-variation-settings: inherit; - font-size: 100%; - font-weight: inherit; - line-height: inherit; - letter-spacing: inherit; - color: inherit; - margin: 0; - padding: 0; - } +button, +select { + text-transform: none; +} - button, - select { - text-transform: none; - } +button, +input:where([type="button"]), +input:where([type="reset"]), +input:where([type="submit"]) { + -webkit-appearance: button; + background-color: transparent; + background-image: none; +} - button, - input:where([type="button"]), - input:where([type="reset"]), - input:where([type="submit"]) { - -webkit-appearance: button; - background-color: transparent; - background-image: none; - } +:-moz-focusring { + outline: auto; +} - :-moz-focusring { - outline: auto; - } +:-moz-ui-invalid { + box-shadow: none; +} - :-moz-ui-invalid { - box-shadow: none; - } +progress { + vertical-align: baseline; +} - progress { - vertical-align: baseline; - } +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} - ::-webkit-inner-spin-button, - ::-webkit-outer-spin-button { - height: auto; - } +[type="search"] { + -webkit-appearance: textfield; + outline-offset: -2px; +} - [type="search"] { - -webkit-appearance: textfield; - outline-offset: -2px; - } +::-webkit-search-decoration { + -webkit-appearance: none; +} - ::-webkit-search-decoration { - -webkit-appearance: none; - } +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit; +} - ::-webkit-file-upload-button { - -webkit-appearance: button; - font: inherit; - } +summary { + display: list-item; +} - summary { - display: list-item; - } +blockquote, +dd, +dl, +figure, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +p, +pre { + margin: 0; +} - blockquote, - dd, - dl, - figure, - h1, - h2, - h3, - h4, - h5, - h6, - hr, - p, - pre { - margin: 0; - } +fieldset { + margin: 0; +} - fieldset { - margin: 0; - } +fieldset, +legend { + padding: 0; +} - fieldset, - legend { - padding: 0; - } +menu, +ol, +ul { + list-style: none; + margin: 0; + padding: 0; +} - menu, - ol, - ul { - list-style: none; - margin: 0; - padding: 0; - } +dialog { + padding: 0; +} - dialog { - padding: 0; - } +textarea { + resize: vertical; +} - textarea { - resize: vertical; - } +input::-moz-placeholder, +textarea::-moz-placeholder { + opacity: 1; + color: #94a3b8; +} - input::-moz-placeholder, - textarea::-moz-placeholder { - opacity: 1; - color: #94a3b8; - } +input::placeholder, +textarea::placeholder { + opacity: 1; + color: #94a3b8; +} - input::placeholder, - textarea::placeholder { - opacity: 1; - color: #94a3b8; - } +[role="button"], +button { + cursor: pointer; +} - [role="button"], - button { - cursor: pointer; - } +:disabled { + cursor: default; +} - :disabled { - cursor: default; - } +audio, +canvas, +embed, +iframe, +img, +object, +svg, +video { + display: block; + vertical-align: middle; +} - audio, - canvas, - embed, - iframe, - img, - object, - svg, - video { - display: block; - vertical-align: middle; - } +img, +video { + max-width: 100%; + height: auto; +} - img, - video { - max-width: 100%; - height: auto; - } +[hidden]:where(:not([hidden="until-found"])) { + display: none; +} - [hidden]:where(:not([hidden="until-found"])) { - display: none; - } } @layer primitives { - lustre-ui-popover { - --gap: var(--lustre-ui-spacing-sm); +lustre-ui-popover { + --gap: var(--lustre-ui-spacing-sm); - display: inline; - position: relative; + display: inline; + position: relative; - & > [slot] { - display: contents; - } - - &::part(popover-content) { - left: 0; - top: 0; - display: block; - position: absolute; - transition-duration: 150ms; - transition-property: opacity, transform; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - z-index: 999; - } - - &[anchor|="top"] { - --translate-x: 0; - - &::part(popover-content) { - padding-bottom: var(--gap); - transform: translate(var(--translate-x), -100%); - } - - &[equal-width]::part(popover-content) { - --translate-x: 0 !important; + & > [slot] { + display: contents; + } - left: 0 !important; - right: 0; - } + &::part(popover-content) { + left: 0; + top: 0; + display: block; + position: absolute; + transition-duration: 150ms; + transition-property: opacity, transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + z-index: 999; + } - &[anchor="top-left"]::part(popover-content) { - left: 0; - } + &[anchor|="top"] { + --translate-x: 0; - &[anchor="top-middle"]::part(popover-content) { - --translate-x: -50%; - left: 50%; - } + &::part(popover-content) { + padding-bottom: var(--gap); + transform: translate(var(--translate-x), -100%); + } - &[anchor="top-right"]::part(popover-content) { - --translate-x: -100%; - left: 100%; - } + &[equal-width]::part(popover-content) { + --translate-x: 0 !important; - &:state(will-expand)::part(popover-content) { - transform: translate(var(--translate-x), -90%); - } + left: 0 !important; + right: 0; + } - &:state(collapsing)::part(popover-content) { - transform: translate(var(--translate-x), -90%); - } + &[anchor="top-left"]::part(popover-content) { + left: 0; } - &[anchor|="right"] { - --translate-y: 0; + &[anchor="top-middle"]::part(popover-content) { + --translate-x: -50%; + left: 50%; + } - &::part(popover-content) { - padding-left: var(--gap); - left: 100%; - transform: translate(0, var(--translate-y)); - } + &[anchor="top-right"]::part(popover-content) { + --translate-x: -100%; + left: 100%; + } - &[anchor="right-top"]::part(popover-content) { - top: 0; - } + &:state(will-expand)::part(popover-content) { + transform: translate(var(--translate-x), -90%); + } - &[anchor="right-middle"]::part(popover-content) { - --translate-y: -50%; - top: 50%; - } + &:state(collapsing)::part(popover-content) { + transform: translate(var(--translate-x), -90%); + } + } - &[anchor="right-bottom"]::part(popover-content) { - --translate-y: -100%; - top: 100%; - } + &[anchor|="right"] { + --translate-y: 0; - &:state(will-expand)::part(popover-content) { - transform: translate(-10%, var(--translate-y)); - } + &::part(popover-content) { + padding-left: var(--gap); + left: 100%; + transform: translate(0, var(--translate-y)); + } - &:state(collapsing)::part(popover-content) { - transform: translate(-10%, var(--translate-y)); - } + &[anchor="right-top"]::part(popover-content) { + top: 0; } - &[anchor|="bottom"] { - --translate-x: 0; + &[anchor="right-middle"]::part(popover-content) { + --translate-y: -50%; + top: 50%; + } - &::part(popover-content) { - top: unset; - padding-top: var(--gap); - transform: translate(var(--translate-x), 0); - } + &[anchor="right-bottom"]::part(popover-content) { + --translate-y: -100%; + top: 100%; + } - &[equal-width]::part(popover-content) { - --translate-x: 0 !important; + &:state(will-expand)::part(popover-content) { + transform: translate(-10%, var(--translate-y)); + } - left: 0 !important; - right: 0; - } + &:state(collapsing)::part(popover-content) { + transform: translate(-10%, var(--translate-y)); + } + } - &[anchor="bottom-left"]::part(popover-content) { - left: 0; - } + &[anchor|="bottom"] { + --translate-x: 0; - &[anchor="bottom-middle"]::part(popover-content) { - --translate-x: -50%; - left: 50%; - } + &::part(popover-content) { + top: unset; + padding-top: var(--gap); + transform: translate(var(--translate-x), 0); + } - &[anchor="bottom-right"]::part(popover-content) { - --translate-x: -100%; - left: 100%; - } + &[equal-width]::part(popover-content) { + --translate-x: 0 !important; - &:state(will-expand)::part(popover-content) { - transform: translate(var(--translate-x), -10%); - } + left: 0 !important; + right: 0; + } - &:state(collapsing)::part(popover-content) { - transform: translate(var(--translate-x), -10%); - } + &[anchor="bottom-left"]::part(popover-content) { + left: 0; } - &[anchor|="left"] { - --translate-y: 0; + &[anchor="bottom-middle"]::part(popover-content) { + --translate-x: -50%; + left: 50%; + } - &::part(popover-content) { - padding-right: var(--gap); - transform: translate(-100%, var(--translate-y)); - } + &[anchor="bottom-right"]::part(popover-content) { + --translate-x: -100%; + left: 100%; + } - &[anchor="left-top"]::part(popover-content) { - top: 0; - } + &:state(will-expand)::part(popover-content) { + transform: translate(var(--translate-x), -10%); + } - &[anchor="left-middle"]::part(popover-content) { - --translate-y: -50%; - top: 50%; - } + &:state(collapsing)::part(popover-content) { + transform: translate(var(--translate-x), -10%); + } + } - &[anchor="left-bottom"]::part(popover-content) { - --translate-y: -100%; - top: 100%; - } + &[anchor|="left"] { + --translate-y: 0; - &:state(will-expand)::part(popover-content) { - transform: translate(-90%, var(--translate-y)); - } + &::part(popover-content) { + padding-right: var(--gap); + transform: translate(-100%, var(--translate-y)); + } - &:state(collapsing)::part(popover-content) { - transform: translate(-90%, var(--translate-y)); - } + &[anchor="left-top"]::part(popover-content) { + top: 0; } - &:state(will-expand)::part(popover-content) { - opacity: 0; + &[anchor="left-middle"]::part(popover-content) { + --translate-y: -50%; + top: 50%; } - &:state(expanded)::part(popover-content) { - opacity: 100; + &[anchor="left-bottom"]::part(popover-content) { + --translate-y: -100%; + top: 100%; } - &:state(will-collapse)::part(popover-content) { - opacity: 100; + &:state(will-expand)::part(popover-content) { + transform: translate(-90%, var(--translate-y)); } &:state(collapsing)::part(popover-content) { - opacity: 0; + transform: translate(-90%, var(--translate-y)); } + } - &:state(collapsed)::part(popover-content) { - display: none; - } + &:state(will-expand)::part(popover-content) { + opacity: 0; + } + + &:state(expanded)::part(popover-content) { + opacity: 100; + } + + &:state(will-collapse)::part(popover-content) { + opacity: 100; } + + &:state(collapsing)::part(popover-content) { + opacity: 0; + } + + &:state(collapsed)::part(popover-content) { + display: none; + } +} + } @layer primitives { - :where(.lustre-ui-icon) { - width: 1rem; - height: 1rem; - } +:where(.lustre-ui-icon) { + width: 1rem; + height: 1rem; +} + } @layer primitives { - lustre-ui-collapse { - --duration: 150ms; +lustre-ui-collapse { + --duration: 150ms; - display: block; + display: block; - &::part(collapse-content) { - transition-duration: var(--duration); - transition-property: height; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - overflow-y: hidden; - } + &::part(collapse-content) { + transition-duration: var(--duration); + transition-property: height; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + overflow-y: hidden; } } + +} + diff --git a/priv/static/lustre_ui.min.css b/priv/static/lustre_ui.min.css new file mode 100644 index 0000000..5cedd16 --- /dev/null +++ b/priv/static/lustre_ui.min.css @@ -0,0 +1 @@ +@layer reset,primitives,components;@layer components{.lustre-ui-input{--border: rgb(var(--lustre-ui-accent));--border-focus: rgb(var(--lustre-ui-primary-solid));--border-width: 1px;--padding-y: var(--lustre-ui-spacing-sm);--radius: var(--lustre-ui-radius-sm);background-color:rgb(var(--lustre-ui-base-bg-subtle));padding:var(--padding-y);outline-offset:0;border-radius:var(--radius);outline:var(--border-width) solid var(--border);&:focus{outline-color:var(--border-focus)}&:invalid{outline-color:rgb(var(--lustre-ui-danger-solid))}}.lustre-ui-input-container{position:relative;display:flex;align-items:center;&:has(*+input)>input{padding-left:2rem}&:has(input+*)>input{padding-right:2rem}&:has(*+input)>:not(input){left:.5rem}>input+*{left:unset!important;right:.5rem}>:not(input){position:absolute;pointer-events:none;width:1rem;height:1rem}}}@layer components{.lustre-ui-divider{height:initial;width:initial;--colour: rgb(var(--lustre-ui-accent));--gap: var(--lustre-ui-spacing-sm);--margin: 0;--size-x: 2px;--size-y: 0;&:empty{align-self:stretch;border-color:var(--colour);border-right-width:var(--size-y);border-style:solid;border-top-width:var(--size-x);margin:var(--margin) 0}&:not(:empty){align-items:center;align-self:stretch;display:flex;gap:var(--gap);justify-items:center;margin:var(--margin) 0;&:before,&:after{background-color:var(--colour);content:"";display:block;flex-grow:1;height:var(--size-x);width:var(--size-y)}}}}@layer components{lustre-ui-combobox{--padding-x: var(--lustre-ui-spacing-sm);--padding-y: var(--lustre-ui-spacing-sm);--border-width: 1px;--radius: var(--lustre-ui-radius-sm);display:inline-block;&::part(combobox-trigger){align-items:center;border-radius:var(--radius);border:rgb(var(--lustre-ui-accent)) solid var(--border-width);color:rgb(var(--lustre-ui-text));display:flex;gap:var(--padding-x);outline-offset:2px;outline:2px solid transparent;padding:var(--padding-y) var(--padding-x);width:100%}&:state(trigger-focus)::part(combobox-trigger){border-color:rgb(var(--lustre-ui-primary-solid))}&:state(empty)::part(combobox-trigger){color:rgb(var(--lustre-ui-text-subtle))}&::part(combobox-trigger-label){flex:1 1 0%;text-align:left}&::part(combobox-trigger-icon){border-radius:var(--radius);height:1.5rem;padding:.25rem;transform:rotate(0);transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);width:1.5rem}&:state(expanded)::part(combobox-trigger-icon){transform:rotate(-180deg)}&:is(:hover,:state(trigger-focus),:state(expanded))::part(combobox-trigger-icon){background-color:rgb(var(--lustre-ui-tint-subtle))}&::part(combobox-options){background-color:rgb(var(--lustre-ui-bg));border:rgb(var(--lustre-ui-accent)) solid var(--border-width);border-radius:var(--radius)}&::part(combobox-input){border:none}&::part(combobox-option){align-items:baseline;cursor:pointer;display:flex;padding:var(--padding-y) var(--padding-x);gap:var(--padding-x)}&::part(combobox-option last){border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius)}&::part(combobox-option intent){background-color:rgb(var(--lustre-ui-tint-subtle))}}}@layer components{.lustre-ui-checkbox{--background: transparent;--background-hover: transparent;--border: rgb(var(--lustre-ui-accent));--border-hover: rgb(var(--lustre-ui-accent-strong));--border-width: 1px;--size: 1rem;--check-color: rgb(var(--lustre-ui-solid-text));appearance:none;background-color:var(--background);border:var(--border-width) solid var(--border);border-radius:var(--lustre-ui-radius-sm);cursor:pointer;display:grid;height:var(--size);margin:0;place-content:center;width:var(--size);&:before{background-color:var(--check-color);clip-path:polygon(16% 44%,10% 55%,45% 90%,90% 16%,80% 10%,45% 60%);content:"";height:calc(var(--size) * .6);transform:scale(0);transform-origin:center;transition:.15s transform cubic-bezier(.4,0,.2,1);width:calc(var(--size) * .6)}&:checked{background-color:rgb(var(--lustre-ui-solid));border-color:transparent}&:checked:before{transform:scale(1)}}}@layer components{.lustre-ui-card{--background: rgb(var(--lustre-ui-bg));--border: rgb(var(--lustre-ui-accent-subtle));--border-width: 1px;--padding-x: var(--lustre-ui-spacing-md);--padding-y: var(--lustre-ui-spacing-md);--radius: var(--lustre-ui-radius-md);background:var(--background);border:var(--border-width) solid var(--border);border-radius:var(--radius);>*{padding-top:0;padding:var(--padding-y) var(--padding-x)}>.card-header{display:flex;flex-direction:column}>.card-footer{align-items:center;display:flex}}}@layer components{.lustre-ui-button{--background: rgb(var(--lustre-ui-tint));--background-hover: rgb(var(--lustre-ui-tint-strong));--border: transparent;--border-hover: transparent;--border-width: 1px;--height: 2rem;--height-min: 2rem;--padding-x: var(--lustre-ui-spacing-sm);--radius: var(--lustre-ui-radius-sm);--text: rgb(var(--lustre-ui-text));align-items:center;background-color:var(--background);border:var(--border-width) solid var(--border);border-radius:var(--radius);color:var(--text);display:inline-flex;gap:var(--padding-x);justify-content:center;min-height:var(--height-min);padding-left:var(--padding-x);padding-right:var(--padding-x);white-space:nowrap;&:disabled{pointer-events:none;opacity:.5;cursor:default}&:hover,&:focus{outline-offset:2px;outline:2px solid transparent;background-color:var(--background-hover);border-color:var(--border-hover)}&:active{transform:translateY(1px)}@media (prefers-reduced-motion){&:active{transform:unset}}&:is(a){text-decoration:none;&:visited{color:var(--text)}}&.button-icon{--height: 2rem;width:var(--height);>svg{height:100%;width:100%}}&.button-small{--height: 2rem;--padding-x: var(--lustre-ui-spacing-sm)}&.button-medium{--height: 2.25rem;--padding-x: var(--lustre-ui-spacing-md)}&.button-large{--height: 2.5rem;--padding-x: var(--lustre-ui-spacing-lg)}&.button-clear{--background: transparent;--background-hover: rgb(var(--lustre-ui-tint));--border: transparent;--border-hover: transparent;--text: rgb(var(--lustre-ui-text))}&.button-outline{--background: transparent;--background-hover: transparent;--border: rgb(var(--lustre-ui-accent));--border-hover: rgb(var(--lustre-ui-accent-strong));--text: rgb(var(--lustre-ui-text))}&.button-soft{--background: rgb(var(--lustre-ui-tint));--background-hover: rgb(var(--lustre-ui-tint-strong));--border: transparent;--border-hover: transparent;--text: rgb(var(--lustre-ui-text))}&.button-solid{--background: rgb(var(--lustre-ui-solid));--background-hover: rgb(var(--lustre-ui-solid) / .8);--border: transparent;--border-hover: transparent;--text: rgb(var(--lustre-ui-solid-text))}.button-badge{display:inline-flex;align-items:center;font-size:.625em;border:1px solid var(--text);border-radius:var(--radius);padding:0 1ch;margin:0 calc(-1 * var(--padding-x) / 2);.key+.key:before{content:var(--separator, "")}.icon{width:1em;height:1em}}}}@layer components{.lustre-ui-breadcrumb{--gap: var(--lustre-ui-spacing-xs);--text-hover: rgb(var(--lustre-ui-text));--text: rgb(var(--lustre-ui-text-subtle));align-items:center;color:var(--text);display:flex;flex-wrap:wrap;gap:var(--gap);>.breadcrumb-item{align-items:center;display:inline-flex;gap:var(--gap);&[aria-current=page]{color:var(--text-hover)}&:hover{color:var(--text-hover)}}>.breadcrumb-separator{user-select:none;pointer-events:none;>svg{height:1rem;width:1rem}}>.breadcrumb-ellipsis{align-items:center;display:flex;height:2rem;justify-content:center;user-select:none;width:2rem;>svg{height:1rem;width:1rem}>*:not(svg){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}}}}@layer components{.lustre-ui-badge{--background: transparent;--background-hover: transparent;--border: rgb(var(--lustre-ui-accent));--border-hover: rgb(var(--lustre-ui-accent-strong));--padding-x: var(--lustre-ui-spacing-sm);--padding-y: var(--lustre-ui-spacing-xs);--radius: var(--lustre-ui-radius-sm);--text: rgb(var(--lustre-ui-text));border:1px solid var(--border);border-radius:var(--radius);background-color:var(--background);color:var(--text);display:inline-flex;align-items:center;padding:var(--padding-y) var(--padding-x);&:is(a,button,[tabindex]):is(:hover,:focus){background-color:var(--background-hover);border-color:var(--border-hover);cursor:pointer}&:active{transform:translateY(1px)}&:empty{padding:max(var(--padding-y),var(--padding-x));border-radius:100%}&.badge-outline{--background: transparent;--background-hover: transparent;--border: rgb(var(--lustre-ui-accent));--border-hover: rgb(var(--lustre-ui-accent-strong));--text: rgb(var(--lustre-ui-text))}&.badge-soft{--background: rgb(var(--lustre-ui-tint));--background-hover: rgb(var(--lustre-ui-tint-strong));--border: transparent;--border-hover: transparent;--text: rgb(var(--lustre-ui-text))}&.badge-solid{--background: rgb(var(--lustre-ui-solid));--background-hover: rgb(var(--lustre-ui-solid) / .8);--border: transparent;--border-hover: transparent;--text: rgb(var(--lustre-ui-solid-text))}}}@layer components{.lustre-ui-alert{--background: rgb(var(--lustre-ui-bg));--border: rgb(var(--lustre-ui-accent));--indicator-size: 16px;--padding-x: var(--lustre-ui-spacing-lg);--padding-y: var(--lustre-ui-spacing-md);--radius: 0;--text: rgb(var(--lustre-ui-text));--title-margin: var(--padding-y);--title-weight: 500;display:flex;flex-direction:column;background-color:var(--background);border-radius:var(--radius);border:1px solid var(--border);color:var(--text);padding:var(--padding-y) var(--padding-x);position:relative;width:full;>.alert-indicator{position:absolute;width:var(--indicator-size);height:var(--indicator-size)}>.alert-title{font-weight:var(--title-weight);margin-block-end:var(--title-margin);overflow:clip;text-overflow:ellipsis}&:has(.alert-indicator)>:not(.alert-indicator){padding-inline-start:calc(var(--indicator-size) + var(--padding-x))}}}@layer components{lustre-ui-accordion{--padding-x: var(--lustre-ui-spacing-sm);--padding-y: var(--lustre-ui-spacing-sm);--border: rgb(var(--lustre-ui-accent));--border-focus: rgb(var(--lustre-ui-primary-solid));--border-width: 1px;--text: rgb(var(--lustre-ui-text));display:block;&::part(accordion-trigger){align-items:center;border-bottom:var(--border) solid var(--border-width);color:var(--text);display:flex;gap:var(--padding-x);outline-offset:2px;outline:2px solid transparent;padding:var(--padding-y) var(--padding-x);width:100%}&::part(accordion-trigger):focus{border-color:var(--border-focus)}&::part(accordion-trigger-label){flex:1 1 0%;text-align:left}&::part(accordion-trigger-icon){border-radius:var(--radius);height:1.5rem;padding:.25rem;transform:rotate(0);transition-duration:.15s;transition-property:color transform;transition-timing-function:cubic-bezier(.4,0,.2,1);width:1.5rem}&::part(accordion-trigger-icon expanded){transform:rotate(-180deg)}&::part(accordion-content){display:block;padding:var(--padding-y) var(--padding-x)}}}@layer reset{*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#94a3b8}input::placeholder,textarea::placeholder{opacity:1;color:#94a3b8}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}}@layer primitives{lustre-ui-popover{--gap: var(--lustre-ui-spacing-sm);display:inline;position:relative;>[slot]{display:contents}&::part(popover-content){left:0;top:0;display:block;position:absolute;transition-duration:.15s;transition-property:opacity,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);z-index:999}&[anchor|=top]{--translate-x: 0;&::part(popover-content){padding-bottom:var(--gap);transform:translate(var(--translate-x),-100%)}&[equal-width]::part(popover-content){--translate-x: 0 !important;left:0!important;right:0}&[anchor=top-left]::part(popover-content){left:0}&[anchor=top-middle]::part(popover-content){--translate-x: -50%;left:50%}&[anchor=top-right]::part(popover-content){--translate-x: -100%;left:100%}&:state(will-expand)::part(popover-content){transform:translate(var(--translate-x),-90%)}&:state(collapsing)::part(popover-content){transform:translate(var(--translate-x),-90%)}}&[anchor|=right]{--translate-y: 0;&::part(popover-content){padding-left:var(--gap);left:100%;transform:translateY(var(--translate-y))}&[anchor=right-top]::part(popover-content){top:0}&[anchor=right-middle]::part(popover-content){--translate-y: -50%;top:50%}&[anchor=right-bottom]::part(popover-content){--translate-y: -100%;top:100%}&:state(will-expand)::part(popover-content){transform:translate(-10%,var(--translate-y))}&:state(collapsing)::part(popover-content){transform:translate(-10%,var(--translate-y))}}&[anchor|=bottom]{--translate-x: 0;&::part(popover-content){top:unset;padding-top:var(--gap);transform:translate(var(--translate-x))}&[equal-width]::part(popover-content){--translate-x: 0 !important;left:0!important;right:0}&[anchor=bottom-left]::part(popover-content){left:0}&[anchor=bottom-middle]::part(popover-content){--translate-x: -50%;left:50%}&[anchor=bottom-right]::part(popover-content){--translate-x: -100%;left:100%}&:state(will-expand)::part(popover-content){transform:translate(var(--translate-x),-10%)}&:state(collapsing)::part(popover-content){transform:translate(var(--translate-x),-10%)}}&[anchor|=left]{--translate-y: 0;&::part(popover-content){padding-right:var(--gap);transform:translate(-100%,var(--translate-y))}&[anchor=left-top]::part(popover-content){top:0}&[anchor=left-middle]::part(popover-content){--translate-y: -50%;top:50%}&[anchor=left-bottom]::part(popover-content){--translate-y: -100%;top:100%}&:state(will-expand)::part(popover-content){transform:translate(-90%,var(--translate-y))}&:state(collapsing)::part(popover-content){transform:translate(-90%,var(--translate-y))}}&:state(will-expand)::part(popover-content){opacity:0}&:state(expanded)::part(popover-content){opacity:100}&:state(will-collapse)::part(popover-content){opacity:100}&:state(collapsing)::part(popover-content){opacity:0}&:state(collapsed)::part(popover-content){display:none}}}@layer primitives{:where(.lustre-ui-icon){width:1rem;height:1rem}}@layer primitives{lustre-ui-collapse{--duration: .15s;display:block;&::part(collapse-content){transition-duration:var(--duration);transition-property:height;transition-timing-function:cubic-bezier(.4,0,.2,1);overflow-y:hidden}}} diff --git a/priv/static/lustre_ui_components.min.mjs b/priv/static/lustre_ui_components.min.mjs new file mode 100644 index 0000000..508f954 --- /dev/null +++ b/priv/static/lustre_ui_components.min.mjs @@ -0,0 +1,15 @@ +var p=class{withFields(t){let n=Object.keys(this).map(r=>r in t?t[r]:this[r]);return new this.constructor(...n)}},L1=class{static fromArray(t,n){let r=n||new b;for(let i=t.length-1;i>=0;--i)r=new U1(t[i],r);return r}[Symbol.iterator](){return new i7(this)}toArray(){return[...this]}atLeastLength(t){let n=this;for(;t-- >0&&n;)n=n.tail;return n!==void 0}hasLength(t){let n=this;for(;t-- >0&&n;)n=n.tail;return t===-1&&n instanceof b}countLength(){let t=this,n=0;for(;t;)t=t.tail,n++;return n-1}};function _(e,t){return new U1(e,t)}function a(e,t){return L1.fromArray(e,t)}var i7=class{#e;constructor(t){this.#e=t}next(){if(this.#e instanceof b)return{done:!0};{let{head:t,tail:n}=this.#e;return this.#e=n,{value:t,done:!1}}}},b=class extends L1{},U1=class extends L1{constructor(t,n){super(),this.head=t,this.tail=n}},X1=class{bitSize;byteSize;bitOffset;rawBuffer;constructor(t,n,r){if(!(t instanceof Uint8Array))throw globalThis.Error("BitArray can only be constructed from a Uint8Array");if(this.bitSize=n??t.length*8,this.byteSize=Math.trunc((this.bitSize+7)/8),this.bitOffset=r??0,this.bitSize<0)throw globalThis.Error(`BitArray bit size is invalid: ${this.bitSize}`);if(this.bitOffset<0||this.bitOffset>7)throw globalThis.Error(`BitArray bit offset is invalid: ${this.bitOffset}`);if(t.length!==Math.trunc((this.bitOffset+this.bitSize+7)/8))throw globalThis.Error("BitArray buffer length is invalid");this.rawBuffer=t}byteAt(t){if(!(t<0||t>=this.byteSize))return J2(this.rawBuffer,this.bitOffset,t)}equals(t){if(this.bitSize!==t.bitSize)return!1;let n=Math.trunc(this.bitSize/8);if(this.bitOffset===0&&t.bitOffset===0){for(let i=0;i>i!==t.rawBuffer[n]>>i)return!1}}else{for(let i=0;i>s!==o>>s)return!1}}return!0}get buffer(){if(zt("buffer","Use BitArray.byteAt() or BitArray.rawBuffer instead"),this.bitOffset!==0||this.bitSize%8!==0)throw new globalThis.Error("BitArray.buffer does not support unaligned bit arrays");return this.rawBuffer}get length(){if(zt("length","Use BitArray.bitSize or BitArray.byteSize instead"),this.bitOffset!==0||this.bitSize%8!==0)throw new globalThis.Error("BitArray.length does not support unaligned bit arrays");return this.rawBuffer.length}};function J2(e,t,n){if(t===0)return e[n]??0;{let r=e[n]<>8-t;return r|i}}var t2=class{constructor(t){this.value=t}},At={};function zt(e,t){At[e]||(console.warn(`Deprecated BitArray.${e} property used in JavaScript FFI code. ${t}.`),At[e]=!0)}function H2(e,t,n){if(n??=e.bitSize,Bt(e,t,n),t===n)return new X1(new Uint8Array);if(t===0&&n===e.bitSize)return e;t+=e.bitOffset,n+=e.bitOffset;let r=Math.trunc(t/8),o=Math.trunc((n+7)/8)-r,s;return r===0&&o===e.rawBuffer.byteLength?s=e.rawBuffer:s=new Uint8Array(e.rawBuffer.buffer,e.rawBuffer.byteOffset+r,o),new X1(s,n-t,t%8)}function ze(e,t,n,r,i){if(Bt(e,t,n),t===n)return 0;t+=e.bitOffset,n+=e.bitOffset;let o=t%8===0,s=n%8===0;if(o&&s)return x4(e,t/8,n/8,r,i);let u=n-t,l=Math.trunc(t/8),c=Math.trunc((n-1)/8);if(l==c){let C=255>>t%8,f=(8-n%8)%8,d=(e.rawBuffer[l]&C)>>f;if(i){let m=2**(u-1);d>=m&&(d-=m*2)}return d}return u<=53?b4(e.rawBuffer,t,n,r,i):w4(e.rawBuffer,t,n,r,i)}function x4(e,t,n,r,i){return n-t<=6?h4(e.rawBuffer,t,n,r,i):L4(e.rawBuffer,t,n,r,i)}function h4(e,t,n,r,i){let o=n-t,s=0;if(r)for(let u=t;u=t;u--)s*=256,s+=e[u];if(i){let u=2**(o*8-1);s>=u&&(s-=u*2)}return s}function L4(e,t,n,r,i){let o=n-t,s=0n;if(r)for(let u=t;u=t;u--)s*=256n,s+=BigInt(e[u]);if(i){let u=1n<=u&&(s-=u*2n)}return Number(s)}function b4(e,t,n,r,i){let o=t%8===0,s=n-t,u=Math.trunc(t/8),l=0;if(r){if(!o){let c=8-t%8;l=e[u++]&(1<=8;)l*=256,l+=e[u++],s-=8;s>0&&(l*=2**s,l+=e[u]>>8-s)}else if(o){let c=n-t,C=1;for(;c>=8;)l+=e[u++]*C,C*=256,c-=8;l+=(e[u]>>8-c)*C}else{let c=t%8,C=8-c,f=n-t,d=1;for(;f>=8;){let m=e[u]<>C;l+=(m&255)*d,d*=256,f-=8,u++}if(f>0){let m=f-Math.max(0,f-C),x=(e[u]&(1<>C-m;f-=m,f>0&&(x*=2**f,x+=e[u+1]>>8-f),l+=x*d}}if(i){let c=2**(n-t-1);l>=c&&(l-=c*2)}return l}function w4(e,t,n,r,i){let o=t%8===0,s=n-t,u=Math.trunc(t/8),l=0n;if(r){if(!o){let c=8-t%8;l=BigInt(e[u++]&(1<=8;)l*=256n,l+=BigInt(e[u++]),s-=8;s>0&&(l<<=BigInt(s),l+=BigInt(e[u]>>8-s))}else if(o){let c=n-t,C=0n;for(;c>=8;)l+=BigInt(e[u++])<>8-c)<=8;){let m=e[u]<>C;l+=BigInt(m&255)<0){let m=f-Math.max(0,f-C),x=(e[u]&(1<>C-m;f-=m,f>0&&(x<<=f,x+=e[u+1]>>8-f),l+=BigInt(x)<=c&&(l-=c*2n)}return Number(l)}function Bt(e,t,n){if(t<0||t>e.bitSize||ne.bitSize){let r=`Invalid bit array slice: start = ${t}, end = ${n}, bit size = ${e.bitSize}`;throw new globalThis.Error(r)}}var g2=class e extends p{static isResult(t){return t instanceof e}},h=class extends g2{constructor(t){super(),this[0]=t}isOk(){return!0}},M=class extends g2{constructor(t){super(),this[0]=t}isOk(){return!1}};function P(e,t){let n=[e,t];for(;n.length;){let r=n.pop(),i=n.pop();if(r===i)continue;if(!Tt(r)||!Tt(i)||!k4(r,i)||H4(r,i)||M4(r,i)||y4(r,i)||Z4(r,i)||V4(r,i)||v4(r,i))return!1;let s=Object.getPrototypeOf(r);if(s!==null&&typeof s.equals=="function")try{if(r.equals(i))continue;return!1}catch{}let[u,l]=g4(r);for(let c of u(r))n.push(l(r,c),l(i,c))}return!0}function g4(e){if(e instanceof Map)return[t=>t.keys(),(t,n)=>t.get(n)];{let t=e instanceof globalThis.Error?["message"]:[];return[n=>[...t,...Object.keys(n)],(n,r)=>n[r]]}}function H4(e,t){return e instanceof Date&&(e>t||en===t[r]))}function y4(e,t){return Array.isArray(e)&&e.length!==t.length}function Z4(e,t){return e instanceof Map&&e.size!==t.size}function V4(e,t){return e instanceof Set&&(e.size!=t.size||[...e].some(n=>!t.has(n)))}function v4(e,t){return e instanceof RegExp&&(e.source!==t.source||e.flags!==t.flags)}function Tt(e){return typeof e=="object"&&e!==null}function k4(e,t){return typeof e!="object"&&typeof t!="object"&&(!e||!t)||[Promise,WeakSet,WeakMap,Function].some(r=>e instanceof r)?!1:e.constructor===t.constructor}function Te(e,t,n,r,i,o,s){let u=new globalThis.Error(o);u.gleam_error=e,u.file=t,u.module=n,u.line=r,u.function=i,u.fn=i;for(let l in s)u[l]=s[l];return u}var Q=class extends p{},r1=class extends p{},K1=class extends p{};var F=class extends p{constructor(t){super(),this[0]=t}},l1=class extends p{};function $9(e){if(e instanceof h){let t=e[0];return new F(t)}else return new l1}var Ot=new WeakMap,o7=new DataView(new ArrayBuffer(8)),s7=0;function u7(e){let t=Ot.get(e);if(t!==void 0)return t;let n=s7++;return s7===2147483647&&(s7=0),Ot.set(e,n),n}function l7(e,t){return e^t+2654435769+(e<<6)+(e>>2)|0}function a7(e){let t=0,n=e.length;for(let r=0;r>16^t)^n}function j4(e){return a7(e.toString())}function E4(e){let t=Object.getPrototypeOf(e);if(t!==null&&typeof t.hashCode=="function")try{let r=e.hashCode(e);if(typeof r=="number")return r}catch{}if(e instanceof Promise||e instanceof WeakSet||e instanceof WeakMap)return u7(e);if(e instanceof Date)return qt(e.getTime());let n=0;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),Array.isArray(e)||e instanceof Uint8Array)for(let r=0;r{n=n+F1(r)|0});else if(e instanceof Map)e.forEach((r,i)=>{n=n+l7(F1(r),F1(i))|0});else{let r=Object.keys(e);for(let i=0;i>>t&S4}function Ne(e,t){return 1<>1&1431655765,e=(e&858993459)+(e>>2&858993459),e=e+(e>>4)&252645135,e+=e>>8,e+=e>>16,e&127}function p7(e,t){return T4(e&t-1)}function P1(e,t,n){let r=e.length,i=new Array(r);for(let o=0;o=A4){let c=new Array(32),C=X2(n,t);c[C]=d7(f7,t+s9,n,r,i,o);let f=0,d=e.bitmap;for(let m=0;m<32;m++){if(d&1){let x=e.array[f++];c[m]=x}d=d>>>1}return{type:o9,size:l+1,array:c}}else{let c=B4(e.array,u,{type:b1,k:r,v:i});return o.val=!0,{type:A1,bitmap:e.bitmap|s,array:c}}}}function N4(e,t,n,r,i,o){if(n===e.hash){let s=_7(e,r);if(s!==-1)return e.array[s].v===i?e:{type:n2,hash:n,array:P1(e.array,s,{type:b1,k:r,v:i})};let u=e.array.length;return o.val=!0,{type:n2,hash:n,array:P1(e.array,u,{type:b1,k:r,v:i})}}return K2({type:A1,bitmap:Ne(e.hash,t),array:[e]},t,n,r,i,o)}function _7(e,t){let n=e.array.length;for(let r=0;r{n=n.set(i,r)}),n}static new(){return new e(void 0,0)}constructor(t,n){this.root=t,this.size=n}get(t,n){if(this.root===void 0)return n;let r=Oe(this.root,0,F1(t),t);return r===void 0?n:r.v}set(t,n){let r={val:!1},i=this.root===void 0?f7:this.root,o=K2(i,0,F1(t),t,n,r);return o===this.root?this:new e(o,r.val?this.size+1:this.size)}delete(t){if(this.root===void 0)return this;let n=m7(this.root,0,F1(t),t);return n===this.root?this:n===void 0?e.new():new e(n,this.size-1)}has(t){return this.root===void 0?!1:Oe(this.root,0,F1(t),t)!==void 0}entries(){if(this.root===void 0)return[];let t=[];return this.forEach((n,r)=>t.push([r,n])),t}forEach(t){Dt(this.root,t)}hashCode(){let t=0;return this.forEach((n,r)=>{t=t+l7(F1(n),F1(r))|0}),t}equals(t){if(!(t instanceof e)||this.size!==t.size)return!1;try{return this.forEach((n,r)=>{if(!P(t.get(r,!n),n))throw Nt}),!0}catch(n){if(n===Nt)return!1;throw n}}},Nt=Symbol();function R4(e,t){return!P(x9(t,e),new M(void 0))}function $7(e,t){return R4(t,e)}function Q1(e,t,n){return Gt(t,n,e)}function Ft(e,t){return Rt(t,e)}function G4(e,t,n){for(;;){let r=e,i=t,o=n;if(r instanceof b)return i;{let s=r.tail,u=r.head[0],l=r.head[1];e=s,t=o(i,u,l),n=o}}}function qe(e,t,n){return G4(Q2(e),t,n)}function W4(e,t){let n=(r,i,o)=>e(i,o)?Q1(r,i,o):r;return qe(t,u9(),n)}function Pt(e,t){return W4(t,e)}function r2(){return void 0}var d1=class extends p{},i2=class extends p{};function ee(e,t){for(;;){let n=e,r=t;if(n instanceof b)return r;{let i=n.head;e=n.tail,t=_(i,r)}}}function R(e){return ee(e,a([]))}function q9(e){if(e instanceof b)return new M(void 0);{let t=e.head;return new h(t)}}function X4(e,t,n){for(;;){let r=e,i=t,o=n;if(r instanceof b)return R(o);{let s=r.head,u=r.tail,l;i(s)?l=_(s,o):l=o;let C=l;e=u,t=i,n=C}}}function De(e,t){return X4(e,t,a([]))}function K4(e,t,n){for(;;){let r=e,i=t,o=n;if(r instanceof b)return R(o);{let s=r.head,u=r.tail,l,c=i(s);if(c instanceof h){let f=c[0];l=_(f,o)}else l=o;let C=l;e=u,t=i,n=C}}}function h7(e,t){return K4(e,t,a([]))}function Q4(e,t,n){for(;;){let r=e,i=t,o=n;if(r instanceof b)return R(o);{let s=r.head;e=r.tail,t=i,n=_(i(s),o)}}}function y1(e,t){return Q4(e,t,a([]))}function e8(e,t,n){for(;;){let r=e,i=t,o=n;if(r instanceof b)return new h(R(o));{let s=r.head,u=r.tail,l=i(s);if(l instanceof h){let c=l[0];e=u,t=i,n=_(c,o)}else{let c=l[0];return new M(c)}}}}function Ue(e,t){return e8(e,t,a([]))}function t8(e,t){for(;;){let n=e,r=t;if(n instanceof b)return r;{let i=n.head;e=n.tail,t=_(i,r)}}}function I9(e,t){return t8(R(e),t)}function Fe(e,t){return _(t,e)}function m1(e,t,n){for(;;){let r=e,i=t,o=n;if(r instanceof b)return i;{let s=r.head;e=r.tail,t=o(i,s),n=o}}}function te(e,t,n){if(e instanceof b)return t;{let r=e.head,i=e.tail;return n(te(i,t,n),r)}}function n8(e,t,n,r){for(;;){let i=e,o=t,s=n,u=r;if(i instanceof b)return o;{let l=i.head;e=i.tail,t=s(o,l,u),n=s,r=u+1}}}function Wt(e,t,n){return n8(e,t,n,0)}function ne(e,t){for(;;){let n=e,r=t;if(n instanceof b)return new M(void 0);{let i=n.head,o=n.tail;if(r(i))return new h(i);e=o,t=r}}}function r8(e,t,n,r,i,o){for(;;){let s=e,u=t,l=n,c=r,C=i,f=o,d=_(C,l);if(s instanceof b)return c instanceof d1?_(R(d),f):_(d,f);{let m=s.head,x=s.tail,g=u(C,m);if(c instanceof d1)if(g instanceof Q)e=x,t=u,n=d,r=c,i=m,o=f;else if(g instanceof r1)e=x,t=u,n=d,r=c,i=m,o=f;else{let $;c instanceof d1?$=_(R(d),f):$=_(d,f);let H=$;if(x instanceof b)return _(a([m]),H);{let w=x.head,y=x.tail,v,L=u(m,w);L instanceof Q?v=new d1:L instanceof r1?v=new d1:v=new i2;let Z=v;e=y,t=u,n=a([m]),r=Z,i=w,o=H}}else if(g instanceof Q){let $;c instanceof d1?$=_(R(d),f):$=_(d,f);let H=$;if(x instanceof b)return _(a([m]),H);{let w=x.head,y=x.tail,v,L=u(m,w);L instanceof Q?v=new d1:L instanceof r1?v=new d1:v=new i2;let Z=v;e=y,t=u,n=a([m]),r=Z,i=w,o=H}}else if(g instanceof r1){let $;c instanceof d1?$=_(R(d),f):$=_(d,f);let H=$;if(x instanceof b)return _(a([m]),H);{let w=x.head,y=x.tail,v,L=u(m,w);L instanceof Q?v=new d1:L instanceof r1?v=new d1:v=new i2;let Z=v;e=y,t=u,n=a([m]),r=Z,i=w,o=H}}else e=x,t=u,n=d,r=c,i=m,o=f}}}function i8(e,t,n,r){for(;;){let i=e,o=t,s=n,u=r;if(i instanceof b)return ee(o,u);if(o instanceof b)return ee(i,u);{let l=i.head,c=i.tail,C=o.head,f=o.tail,d=s(l,C);d instanceof Q?(e=c,t=o,n=s,r=_(l,u)):d instanceof r1?(e=i,t=f,n=s,r=_(C,u)):(e=i,t=f,n=s,r=_(C,u))}}}function o8(e,t,n){for(;;){let r=e,i=t,o=n;if(r instanceof b)return R(o);{let s=r.tail;if(s instanceof b){let u=r.head;return R(_(R(u),o))}else{let u=r.head,l=s.head,c=s.tail,C=i8(u,l,i,a([]));e=c,t=i,n=_(C,o)}}}}function s8(e,t,n,r){for(;;){let i=e,o=t,s=n,u=r;if(i instanceof b)return ee(o,u);if(o instanceof b)return ee(i,u);{let l=i.head,c=i.tail,C=o.head,f=o.tail,d=s(l,C);d instanceof Q?(e=i,t=f,n=s,r=_(C,u)):d instanceof r1?(e=c,t=o,n=s,r=_(l,u)):(e=c,t=o,n=s,r=_(l,u))}}}function u8(e,t,n){for(;;){let r=e,i=t,o=n;if(r instanceof b)return R(o);{let s=r.tail;if(s instanceof b){let u=r.head;return R(_(R(u),o))}else{let u=r.head,l=s.head,c=s.tail,C=s8(u,l,i,a([]));e=c,t=i,n=_(C,o)}}}}function l8(e,t,n){for(;;){let r=e,i=t,o=n;if(r instanceof b)return a([]);if(i instanceof d1){if(r.tail instanceof b)return r.head;e=o8(r,o,a([])),t=new i2,n=o}else if(r.tail instanceof b){let u=r.head;return R(u)}else e=u8(r,o,a([])),t=new d1,n=o}}function o2(e,t){if(e instanceof b)return a([]);{let n=e.tail;if(n instanceof b){let r=e.head;return a([r])}else{let r=e.head,i=n.head,o=n.tail,s,u=t(r,i);u instanceof Q?s=new d1:u instanceof r1?s=new d1:s=new i2;let l=s,c=r8(o,t,a([r]),l,i,a([]));return l8(c,new d1,t)}}}var L9=class extends p{constructor(t,n,r){super(),this.expected=t,this.found=n,this.path=r}},B1=class extends p{constructor(t){super(),this.function=t}};function R1(e,t){let n=t.function(e),r=n[0],i=n[1];return i instanceof b?new h(r):new M(i)}function S(e){return new B1(t=>[e,a([])])}function C8(e){return[e,a([])]}function Z1(e,t){return new B1(n=>{let r=e.function(n),i=r[0],o=r[1];return[t(i),o]})}function l9(e,t){return new B1(n=>{let r=e.function(n),i=r[0],o=r[1],u=t(i).function(n),l=u,c=u[0];return o instanceof b?l:[c,o]})}function f8(e,t,n){for(;;){let r=e,i=t,o=n;if(o instanceof b)return i;{let s=o.head,u=o.tail,l=s.function(r),c=l;if(l[1]instanceof b)return c;e=r,t=i,n=u}}}function Pe(e,t){return new B1(n=>{let r=e.function(n),i=r;return r[1]instanceof b?i:f8(n,i,t)})}var O1=new B1(C8);function Xt(e,t){return a([new L9(e,N9(t),a([]))])}function Kt(e,t,n){let r=n(e);return r instanceof h?[r[0],a([])]:[r[0],a([new L9(t,N9(e),a([]))])]}function p8(e){return P(!0,e)?[!0,a([])]:P(!1,e)?[!1,a([])]:[!1,Xt("Bool",e)]}function d8(e){return Kt(e,"Int",t3)}function N1(e,t){return new B1(n=>[e,Xt(t,n)])}function re(e,t){return new B1(n=>{let r=t(n);return r instanceof h?[r[0],a([])]:[r[0],a([new L9(e,N9(n),a([]))])]})}var Re=new B1(p8),Qt=new B1(d8);function _8(e){return Kt(e,"String",n3)}var $1=new B1(_8);function b7(e,t){let n=Pe($1,a([Z1(Qt,b9)])),r=y1(t,o=>{let s=o,u=R1(s,n);return u instanceof h?u[0]:"<"+N9(s)+">"}),i=y1(e[1],o=>{let s=o;return new L9(s.expected,s.found,I9(r,o.path))});return[e[0],i]}function m8(e,t,n,r,i){for(;;){let o=e,s=t,u=n,l=r,c=i;if(o instanceof b){let C=u(l);return b7(C,R(s))}else{let C=o.head,f=o.tail,d=e3(l,C);if(d instanceof h){let m=d[0];if(m instanceof F){let x=m[0];e=f,t=_(C,s),n=u,r=x,i=c}else return c(l,_(C,s))}else{let m=d[0],$=[u(l)[0],a([new L9(m,N9(l),a([]))])];return b7($,R(s))}}}}function e9(e,t,n){return new B1(r=>{let i=m8(e,a([]),t.function,r,(C,f)=>{let x=[t.function(C)[0],a([new L9("Field","Nothing",a([]))])];return b7(x,R(f))}),o=i[0],s=i[1],u=n(o).function(r),l=u[0],c=u[1];return[l,I9(s,c)]})}function i1(e,t,n){return e9(a([e]),t,n)}var $8=void 0,r3={};function b9(e){return e.toString()}function w7(e){let t=e.toString().replace("+","");if(t.indexOf(".")>=0)return t;{let n=t.indexOf("e");return n>=0?t.slice(0,n)+".0"+t.slice(n):t+".0"}}function y2(e){if(e==="")return 0;let t=x8(e);if(t){let n=0;for(let r of t)n++;return n}else return e.match(/./gsu).length}var i3;function x8(e){if(globalThis.Intl&&Intl.Segmenter)return i3||=new Intl.Segmenter,i3.segment(e)[Symbol.iterator]()}function w1(e){return e.toLowerCase()}function ie(e,t){return e.indexOf(t)>=0}function Z2(e,t){return e.startsWith(t)}var o3=[" "," ",` +`,"\v","\f","\r","\x85","\u2028","\u2029"].join(""),In=new RegExp(`^[${o3}]*`),Dn=new RegExp(`[${o3}]*$`);function u9(){return z1.new()}function x7(e){return e.size}function Q2(e){return L1.fromArray(e.entries())}function Rt(e,t){return t.delete(e)}function x9(e,t){let n=e.get(t,r3);return n===r3?new M($8):new h(n)}function Gt(e,t,n){return n.set(e,t)}function N9(e){if(typeof e=="string")return"String";if(typeof e=="boolean")return"Bool";if(e instanceof g2)return"Result";if(e instanceof L1)return"List";if(e instanceof X1)return"BitArray";if(e instanceof z1)return"Dict";if(Number.isInteger(e))return"Int";if(Array.isArray(e))return"Array";if(typeof e=="number")return"Float";if(e===null)return"Nil";if(e===void 0)return"Nil";{let t=typeof e;return t.charAt(0).toUpperCase()+t.slice(1)}}function e3(e,t){if(e instanceof z1||e instanceof WeakMap||e instanceof Map){let r={},i=e.get(t,r);return i===r?new h(new l1):new h(new F(i))}let n=Number.isInteger(t);if(n&&t>=0&&t<8&&e instanceof L1){let r=0;for(let i of e){if(r===t)return new h(new F(i));r++}return new M("Indexable")}return n&&Array.isArray(e)||e&&typeof e=="object"||e&&Object.getPrototypeOf(e)===Object.prototype?t in e?new h(new F(e[t])):new h(new l1):new M(n?"Indexable":"Dict")}function t3(e){return Number.isInteger(e)?new h(e):new M(0)}function n3(e){return typeof e=="string"?new h(e):new M("")}function h8(e,t){for(;;){let n=e,r=t;if(n instanceof b)return r;{let i=n.head;e=n.tail,t=i+r}}}function oe(e){return h8(e,0)}function T1(e,t){return e===t?new r1:et(n)))}var $3=void 0;function u2(e,t){return new s2(Q1(e.dict,t,$3))}function H9(e){let t=m1(e,u9(),(n,r)=>Q1(n,r,$3));return new s2(t)}var z8=z1.new(),T8=v1(),Xe=()=>z8,le=()=>T8,I1=()=>globalThis?.document,Ke="http://www.w3.org/1999/xhtml",Qe=1,e5=3,t5=11,x3=!!globalThis.HTMLElement?.prototype?.moveBefore;var E=a([]),n5=new l1;var B8=new K1,O8=new Q,N8=new r1;function r5(e,t){return e.name===t.name?N8:e.namer5(o,i));return U8(r,E)}}var k7=0;function Z3(e,t){return new r9(k7,e,t)}var V3=1;var j7=2;function v3(e,t,n,r,i,o,s,u){return new g1(j7,e,t,n,r,i,o,s,u)}var E7=0;var S7=new v7(E7);var A7=2;function j(e,t){return Z3(e,t)}function M9(e){return j("class",e)}function y9(e,t){return e===""||t===""?M9(""):j("style",e+":"+t+";")}function P8(e,t){for(;;){let n=e,r=t;if(n instanceof b)return r;{let i=n.head[0];if(i==="")e=n.tail,t=r;else{let o=n.head[1];if(o==="")e=n.tail,t=r;else{let s=n.tail,u=i,l=o;e=s,t=r+u+":"+l+";"}}}}}function k2(e){return j("style",P8(e,""))}function k3(e){return j("autocomplete",e)}function Z9(e){return j("name",e)}function z7(e){return j("value",e)}var U9=class extends p{constructor(t,n,r){super(),this.synchronous=t,this.before_paint=n,this.after_paint=r}};var j2=new U9(a([]),a([]),a([]));function Y(){return j2}function j3(e){let t=r=>{let i=r.dispatch;return e(i)},n=j2;return new U9(a([t]),n.before_paint,n.after_paint)}function Ce(e){let t=r=>{let i=r.root(),o=r.dispatch;return e(o,i)},n=j2;return new U9(n.synchronous,a([t]),n.after_paint)}function fe(e){let t=r=>{let i=r.root(),o=r.dispatch;return e(o,i)},n=j2;return new U9(n.synchronous,n.before_paint,a([t]))}function E3(e,t){let n=i=>i.emit(e,t),r=j2;return new U9(a([n]),r.before_paint,r.after_paint)}function s1(e){return m1(e,j2,(t,n)=>new U9(m1(n.synchronous,t.synchronous,Fe),m1(n.before_paint,t.before_paint,Fe),m1(n.after_paint,t.after_paint,Fe)))}function H1(){return null}function E2(e,t){let n=e?.get(t);return n!=null?new h(n):new M(void 0)}function P9(e,t,n){return e??=new Map,e.set(t,n),e}function T7(e,t){return e?.delete(t),e}var i5=class extends p{},o5=class extends p{constructor(t,n){super(),this.key=t,this.parent=n}},B7=class extends p{constructor(t,n){super(),this.index=t,this.parent=n}};function R8(e,t){for(;;){let n=e,r=t;if(r instanceof b)return!1;{let i=r.head,o=r.tail;if(Z2(n,i))return!0;e=n,t=o}}}function R9(e,t,n){return n===""?new B7(t,e):new o5(n,e)}var O7=new i5,s5=" ";function S3(e,t){for(;;){let n=e,r=t;if(n instanceof i5){if(r instanceof b)return"";{let i=r.tail;return a3(i)}}else if(n instanceof o5){let i=n.key;e=n.parent,t=_(s5,_(i,r))}else{let i=n.index;e=n.parent,t=_(s5,_(b9(i),r))}}}function G8(e){return S3(e,a([]))}function A3(e,t){return t instanceof b?!1:R8(G8(e),t)}var N7=` +`;function q7(e,t){return S3(e,a([N7,t]))}var M1=class extends p{constructor(t,n,r,i,o,s){super(),this.kind=t,this.key=n,this.mapper=r,this.children=i,this.keyed_children=o,this.children_count=s}},G1=class extends p{constructor(t,n,r,i,o,s,u,l,c,C){super(),this.kind=t,this.key=n,this.mapper=r,this.namespace=i,this.tag=o,this.attributes=s,this.children=u,this.keyed_children=l,this.self_closing=c,this.void=C}},W1=class extends p{constructor(t,n,r,i){super(),this.kind=t,this.key=n,this.mapper=r,this.content=i}},S2=class extends p{constructor(t,n,r,i,o,s,u){super(),this.kind=t,this.key=n,this.mapper=r,this.namespace=i,this.tag=o,this.attributes=s,this.inner_html=u}};function J8(e,t){return t===""?e==="area"||e==="base"||e==="br"||e==="col"||e==="embed"||e==="hr"||e==="img"||e==="input"||e==="link"||e==="meta"||e==="param"||e==="source"||e==="track"?!0:e==="wbr":!1}function U(e){return e instanceof M1?1+e.children_count:1}var I7=0;function u5(e,t,n,r,i){return new M1(I7,e,t,n,r,i)}var D7=1;function de(e,t,n,r,i,o,s,u,l){return new G1(D7,e,t,n,r,y3(i),o,s,u,l||J8(r,n))}var U7=2;function F7(e,t,n){return new W1(U7,e,t,n)}var T3=3;function B3(e,t,n,r,i){for(;;){let o=e,s=t,u=n,l=r,c=i;if(s instanceof b)return[R(l),c];{let C=s.head;if(C instanceof M1){let f=C;if(f.key===""){let d=s.tail,m=o+"::"+b9(u),x=B3(m,f.children,0,E,H1()),g=x[0],$=x[1],H,w=f;H=new M1(w.kind,w.key,w.mapper,g,$,w.children_count);let v=_(H,l),L=u+1;e=o,t=d,n=L,r=v,i=c}else{let d=C;if(d.key!==""){let m=s.tail,x=o+"::"+d.key,g=A2(x,d),$=_(g,l),H=P9(c,x,g),w=u+1;e=o,t=m,n=w,r=$,i=H}else{let m=C,x=s.tail,g=_(m,l),$=u+1;e=o,t=x,n=$,r=g,i=c}}}else{let f=C;if(f.key!==""){let d=s.tail,m=o+"::"+f.key,x=A2(m,f),g=_(x,l),$=P9(c,m,x),H=u+1;e=o,t=d,n=H,r=g,i=$}else{let d=C,m=s.tail,x=_(d,l),g=u+1;e=o,t=m,n=g,r=x,i=c}}}}}function A2(e,t){if(t instanceof M1){let n=t.children,r=B3(e,n,0,E,H1()),i=r[0],o=r[1],s=t;return new M1(s.kind,e,s.mapper,i,o,s.children_count)}else if(t instanceof G1){let n=t;return new G1(n.kind,e,n.mapper,n.namespace,n.tag,n.attributes,n.children,n.keyed_children,n.self_closing,n.void)}else if(t instanceof W1){let n=t;return new W1(n.kind,e,n.mapper,n.content)}else{let n=t;return new S2(n.kind,e,n.mapper,n.namespace,n.tag,n.attributes,n.inner_html)}}var P7=(e,t)=>e===t,c2=(e,t)=>{if(e===t)return!0;if(e==null||t==null)return!1;let n=typeof e;return n!==typeof t||n!=="object"||e.constructor!==t.constructor?!1:Array.isArray(e)?X8(e,t):K8(e,t)},X8=(e,t)=>{let n=e.length;if(n!==t.length)return!1;for(;n--;)if(!c2(e[n],t[n]))return!1;return!0},K8=(e,t)=>{let n=Object.keys(e),r=n.length;if(Object.keys(t).length!==r)return!1;for(;r--;){let i=n[r];if(!Object.hasOwn(t,i)||!c2(e[i],t[i]))return!1}return!0};var p9=class extends p{constructor(t,n,r){super(),this.handlers=t,this.dispatched_paths=n,this.next_dispatched_paths=r}};function G7(){return new p9(H1(),E,E)}function I3(e){return new p9(e.handlers,e.next_dispatched_paths,E)}function D3(e,t,n){return T7(e,q7(t,n))}function _e(e,t,n){let r=D3(e.handlers,t,n),i=e;return new p9(r,i.dispatched_paths,i.next_dispatched_paths)}function O3(e,t,n){return m1(n,e,(r,i)=>{if(i instanceof g1){let o=i.name;return D3(r,t,o)}else return r})}function l5(e,t,n,r){let i=_(t,e.next_dispatched_paths),o,s=e;o=new p9(s.handlers,s.dispatched_paths,i);let u=o,l=E2(u.handlers,t+N7+n);if(l instanceof h){let c=l[0];return[u,R1(r,c)]}else return[u,new M(a([]))]}function c5(e,t){return A3(t,e.dispatched_paths)}function U3(e,t,n,r,i){return P9(e,q7(n,r),Z1(i,o=>{let s=o;return new v2(s.prevent_default,s.stop_propagation,t(o.message))}))}function me(e,t,n,r,i){let o=U3(e.handlers,t,n,r,i),s=e;return new p9(o,s.dispatched_paths,s.next_dispatched_paths)}function N3(e,t,n,r){return m1(r,e,(i,o)=>{if(o instanceof g1){let s=o.name,u=o.handler;return U3(i,t,n,s,u)}else return i})}function G9(e,t){let n=P7(e,a1);return P7(t,a1)?e:n?t:i=>e(t(i))}function q3(e,t,n,r){for(;;){let i=e,o=t,s=n,u=r;if(u instanceof b)return i;{let l=u.head,c=u.tail;e=F3(i,o,s,l),t=o,n=s+U(l),r=c}}}function F3(e,t,n,r){if(r instanceof M1){let i=r.children;return q3(e,t,n+1,i)}else if(r instanceof G1){let i=r.attributes,o=r.children,s=R9(t,n,r.key),l=O3(e,s,i);return q3(l,s,0,o)}else{if(r instanceof W1)return e;{let i=r.attributes,o=R9(t,n,r.key);return O3(e,o,i)}}}function V9(e,t,n,r){let i=F3(e.handlers,t,n,r),o=e;return new p9(i,o.dispatched_paths,o.next_dispatched_paths)}function R7(e,t,n,r,i){for(;;){let o=e,s=t,u=n,l=r,c=i;if(c instanceof b)return o;{let C=c.head,f=c.tail;e=P3(o,s,u,l,C),t=s,n=u,r=l+U(C),i=f}}}function P3(e,t,n,r,i){if(i instanceof M1){let o=i.children,s=G9(t,i.mapper),u=r+1;return R7(e,s,n,u,o)}else if(i instanceof G1){let o=i.attributes,s=i.children,u=R9(n,r,i.key),l=G9(t,i.mapper),C=N3(e,l,u,o);return R7(C,l,u,0,s)}else{if(i instanceof W1)return e;{let o=i.attributes,s=R9(n,r,i.key),u=G9(t,i.mapper);return N3(e,u,s,o)}}}function W9(e,t,n,r,i){let o=P3(e.handlers,t,n,r,i),s=e;return new p9(o,s.dispatched_paths,s.next_dispatched_paths)}function R3(e,t,n,r,i){let o=R7(e.handlers,t,n,r,i),s=e;return new p9(o,s.dispatched_paths,s.next_dispatched_paths)}function _1(e,t,n){return de("",a1,"",e,t,n,H1(),!1,!1)}function T2(e,t,n,r){return de("",a1,e,t,n,r,H1(),!1,!1)}function B2(e){return F7("",a1,e)}function G3(){return F7("",a1,"")}function Q8(e,t){for(;;){let n=e,r=t;if(n instanceof b)return r;{let i=n.head;e=n.tail,t=r+U(i)}}}function v9(e){return u5("",a1,e,H1(),Q8(e,0))}function a2(e){return B2(e)}function D1(e,t){return _1("div",e,t)}function W3(e,t){return _1("li",e,t)}function Y3(e,t){return _1("p",e,t)}function a5(e,t){return _1("span",e,t)}function J3(e,t){return T2("http://www.w3.org/2000/svg","svg",e,t)}function C5(e,t){return _1("button",e,t)}function X3(e){return _1("input",e,E)}function Y1(e,t){return _1("slot",e,t)}var N2=class extends p{constructor(t,n,r,i){super(),this.index=t,this.removed=n,this.changes=r,this.children=i}},W7=class extends p{constructor(t,n){super(),this.kind=t,this.content=n}},Y7=class extends p{constructor(t,n){super(),this.kind=t,this.inner_html=n}},J7=class extends p{constructor(t,n,r){super(),this.kind=t,this.added=n,this.removed=r}},X7=class extends p{constructor(t,n,r,i){super(),this.kind=t,this.key=n,this.before=r,this.count=i}},K7=class extends p{constructor(t,n,r){super(),this.kind=t,this.key=n,this.count=r}},Q7=class extends p{constructor(t,n,r,i){super(),this.kind=t,this.from=n,this.count=r,this.with=i}},et=class extends p{constructor(t,n,r){super(),this.kind=t,this.children=n,this.before=r}},tt=class extends p{constructor(t,n,r){super(),this.kind=t,this.from=n,this.count=r}};function nt(e,t,n,r){return new N2(e,t,n,r)}var rt=0;function K3(e){return new W7(rt,e)}var it=1;function Q3(e){return new Y7(it,e)}var ot=2;function xe(e,t){return new J7(ot,e,t)}var f5=3;function e0(e,t,n){return new X7(f5,e,t,n)}var st=4;function t0(e,t){return new K7(st,e,t)}var p5=5;function f2(e,t,n){return new Q7(p5,e,t,n)}var d5=6;function ut(e,t){return new et(d5,e,t)}var _5=7;function n0(e,t){return new tt(_5,e,t)}var m5=class extends p{constructor(t,n){super(),this.patch=t,this.events=n}},lt=class extends p{constructor(t,n,r){super(),this.added=t,this.removed=n,this.events=r}};function r6(e,t,n,r){return n==="input"?t===""?c5(e,r):!1:n==="select"?t===""?c5(e,r):!1:n==="textarea"&&t===""?c5(e,r):!1}function i0(e,t,n,r,i,o,s,u){for(;;){let l=e,c=t,C=n,f=r,d=i,m=o,x=s,g=u;if(m instanceof b){if(d instanceof b)return new lt(x,g,f);{let $=d.head;if($ instanceof g1){let H=$,w=d.tail,y=$.name,v=_(H,g),L=_e(f,c,y);e=l,t=c,n=C,r=L,i=w,o=m,s=x,u=v}else{let H=$,w=d.tail,y=_(H,g);e=l,t=c,n=C,r=f,i=w,o=m,s=x,u=y}}}else if(d instanceof b){let $=m.head;if($ instanceof g1){let H=$,w=m.tail,y=$.name,v=$.handler,L=_(H,x),Z=me(f,C,c,y,v);e=l,t=c,n=C,r=Z,i=d,o=w,s=L,u=g}else{let H=$,w=m.tail,y=_(H,x);e=l,t=c,n=C,r=f,i=d,o=w,s=y,u=g}}else{let $=m.head,H=m.tail,w=d.head,y=d.tail,v=r5(w,$);if(v instanceof Q)if(w instanceof g1){let L=w.name,Z=_(w,g),V=_e(f,c,L);e=l,t=c,n=C,r=V,i=y,o=m,s=x,u=Z}else{let L=_(w,g);e=l,t=c,n=C,r=f,i=y,o=m,s=x,u=L}else if(v instanceof r1)if($ instanceof r9)if(w instanceof r9){let L,Z=$.name;Z==="value"||Z==="checked"||Z==="selected"?L=l||w.value!==$.value:L=w.value!==$.value;let V=L,A;V?A=_($,x):A=x;let O=A;e=l,t=c,n=C,r=f,i=y,o=H,s=O,u=g}else if(w instanceof g1){let L=w.name,Z=_($,x),V=_(w,g),A=_e(f,c,L);e=l,t=c,n=C,r=A,i=y,o=H,s=Z,u=V}else{let L=_($,x),Z=_(w,g);e=l,t=c,n=C,r=f,i=y,o=H,s=L,u=Z}else if($ instanceof ce)if(w instanceof ce){let L,Z=$.name;Z==="scrollLeft"||Z==="scrollRight"?L=!0:Z==="value"?L=l||!c2(w.value,$.value):Z==="checked"?L=l||!c2(w.value,$.value):Z==="selected"?L=l||!c2(w.value,$.value):L=!c2(w.value,$.value);let V=L,A;V?A=_($,x):A=x;let O=A;e=l,t=c,n=C,r=f,i=y,o=H,s=O,u=g}else if(w instanceof g1){let L=w.name,Z=_($,x),V=_(w,g),A=_e(f,c,L);e=l,t=c,n=C,r=A,i=y,o=H,s=Z,u=V}else{let L=_($,x),Z=_(w,g);e=l,t=c,n=C,r=f,i=y,o=H,s=L,u=Z}else if(w instanceof g1){let L=$.name,Z=$.handler,V=!P(w.prevent_default,$.prevent_default)||!P(w.stop_propagation,$.stop_propagation)||w.immediate!==$.immediate||w.debounce!==$.debounce||w.throttle!==$.throttle,A;V?A=_($,x):A=x;let O=A,q=me(f,C,c,L,Z);e=l,t=c,n=C,r=q,i=y,o=H,s=O,u=g}else{let L=$.name,Z=$.handler,V=_($,x),A=_(w,g),O=me(f,C,c,L,Z);e=l,t=c,n=C,r=O,i=y,o=H,s=V,u=A}else if($ instanceof g1){let L=$.name,Z=$.handler,V=_($,x),A=me(f,C,c,L,Z);e=l,t=c,n=C,r=A,i=d,o=H,s=V,u=g}else{let L=_($,x);e=l,t=c,n=C,r=f,i=d,o=H,s=L,u=g}}}}function ct(e,t,n,r,i,o,s,u,l,c,C,f,d,m){for(;;){let x=e,g=t,$=n,H=r,w=i,y=o,v=s,L=u,Z=l,V=c,A=C,O=f,q=d,u1=m;if($ instanceof b){if(x instanceof b)return new m5(new N2(Z,v,A,O),u1);{let f1=x.head,S1=x.tail,t1;f1.key===""||!k1(w,f1.key)?t1=v+U(f1):t1=v;let k=t1,X=V9(u1,V,L,f1);e=S1,t=g,n=$,r=H,i=w,o=y,s=k,u=L,l=Z,c=V,C=A,f=O,d=q,m=X}}else if(x instanceof b){let f1=R3(u1,q,V,L,$),S1=ut($,L-y),t1=_(S1,A);return new m5(new N2(Z,v,t1,O),f1)}else{let f1=$.head,S1=x.head;if(S1.key!==f1.key){let t1=$.tail,K=x.tail,k=E2(g,f1.key),X=E2(H,S1.key),N=k1(w,S1.key);if(k instanceof h)if(X instanceof h)if(N)e=K,t=g,n=$,r=H,i=w,o=y-U(S1),s=v,u=L,l=Z,c=V,C=A,f=O,d=q,m=u1;else{let D=k[0],z=U(f1),T=L-y,I=e0(f1.key,T,z),B=_(I,A),G=u2(w,f1.key),W=y+z;e=_(D,x),t=g,n=$,r=H,i=G,o=W,s=v,u=L,l=Z,c=V,C=B,f=O,d=q,m=u1}else{let D=U(S1),z=y-D,T=V9(u1,V,L,S1),I=t0(S1.key,D),B=_(I,A);e=K,t=g,n=$,r=H,i=w,o=z,s=v,u=L,l=Z,c=V,C=B,f=O,d=q,m=T}else if(X instanceof h){let D=L-y,z=U(f1),T=W9(u1,q,V,L,f1),I=ut(a([f1]),D),B=_(I,A);e=x,t=g,n=t1,r=H,i=w,o=y+z,s=v,u=L+z,l=Z,c=V,C=B,f=O,d=q,m=T}else{let D=U(S1),z=U(f1),T=f2(L-y,D,f1),I,G=V9(u1,V,L,S1);I=W9(G,q,V,L,f1);let W=I;e=K,t=g,n=t1,r=H,i=w,o=y-D+z,s=v,u=L+z,l=Z,c=V,C=_(T,A),f=O,d=q,m=W}}else{let t1=x.head;if(t1 instanceof M1){let K=$.head;if(K instanceof M1){let k=K,X=$.tail,N=t1,D=x.tail,z=L+1,T=N.children_count,I=k.children_count,B=G9(q,k.mapper),G=ct(N.children,N.keyed_children,k.children,k.keyed_children,le(),y,0,z,-1,V,E,O,B,u1),W;if(G.patch.removed>0){let m9=z+I-y,T9=n0(m9,G.patch.removed);W=I9(G.patch.changes,_(T9,A))}else W=I9(G.patch.changes,A);let J1=W;e=D,t=g,n=X,r=H,i=w,o=y+I-T,s=v,u=z+I,l=Z,c=V,C=J1,f=G.patch.children,d=q,m=G.events}else{let k=K,X=$.tail,N=t1,D=x.tail,z=U(N),T=U(k),I=f2(L-y,z,k),B,W=V9(u1,V,L,N);B=W9(W,q,V,L,k);let h1=B;e=D,t=g,n=X,r=H,i=w,o=y-z+T,s=v,u=L+T,l=Z,c=V,C=_(I,A),f=O,d=q,m=h1}}else if(t1 instanceof G1){let K=$.head;if(K instanceof G1){let k=K,X=t1;if(X.namespace===k.namespace&&X.tag===k.tag){let N=$.tail,D=x.tail,z=G9(q,k.mapper),T=R9(V,L,k.key),I=r6(u1,k.namespace,k.tag,T),B=i0(I,T,z,u1,X.attributes,k.attributes,E,E),G=B.added,W=B.removed,h1=B.events,J1;W instanceof b?G instanceof b?J1=E:J1=a([xe(G,W)]):J1=a([xe(G,W)]);let m9=J1,T9=ct(X.children,X.keyed_children,k.children,k.keyed_children,le(),0,0,0,L,T,m9,E,z,h1),B9,e2=T9.patch;e2.children instanceof b?e2.changes instanceof b?e2.removed===0?B9=O:B9=_(T9.patch,O):B9=_(T9.patch,O):B9=_(T9.patch,O);let $4=B9;e=D,t=g,n=N,r=H,i=w,o=y,s=v,u=L+1,l=Z,c=V,C=A,f=$4,d=q,m=T9.events}else{let N=K,D=$.tail,z=t1,T=x.tail,I=U(z),B=U(N),G=f2(L-y,I,N),W,J1=V9(u1,V,L,z);W=W9(J1,q,V,L,N);let m9=W;e=T,t=g,n=D,r=H,i=w,o=y-I+B,s=v,u=L+B,l=Z,c=V,C=_(G,A),f=O,d=q,m=m9}}else{let k=K,X=$.tail,N=t1,D=x.tail,z=U(N),T=U(k),I=f2(L-y,z,k),B,W=V9(u1,V,L,N);B=W9(W,q,V,L,k);let h1=B;e=D,t=g,n=X,r=H,i=w,o=y-z+T,s=v,u=L+T,l=Z,c=V,C=_(I,A),f=O,d=q,m=h1}}else if(t1 instanceof W1){let K=$.head;if(K instanceof W1){let k=K;if(t1.content===k.content){let N=$.tail;e=x.tail,t=g,n=N,r=H,i=w,o=y,s=v,u=L+1,l=Z,c=V,C=A,f=O,d=q,m=u1}else{let N=K,D=$.tail,z=x.tail,T=nt(L,0,a([K3(N.content)]),E);e=z,t=g,n=D,r=H,i=w,o=y,s=v,u=L+1,l=Z,c=V,C=A,f=_(T,O),d=q,m=u1}}else{let k=K,X=$.tail,N=t1,D=x.tail,z=U(N),T=U(k),I=f2(L-y,z,k),B,W=V9(u1,V,L,N);B=W9(W,q,V,L,k);let h1=B;e=D,t=g,n=X,r=H,i=w,o=y-z+T,s=v,u=L+T,l=Z,c=V,C=_(I,A),f=O,d=q,m=h1}}else{let K=$.head;if(K instanceof S2){let k=K,X=$.tail,N=t1,D=x.tail,z=G9(q,k.mapper),T=R9(V,L,k.key),I=i0(!1,T,z,u1,N.attributes,k.attributes,E,E),B=I.added,G=I.removed,W=I.events,h1;G instanceof b?B instanceof b?h1=E:h1=a([xe(B,G)]):h1=a([xe(B,G)]);let J1=h1,m9;N.inner_html===k.inner_html?m9=J1:m9=_(Q3(k.inner_html),J1);let B9=m9,e2;B9 instanceof b?e2=O:e2=_(nt(L,0,B9,a([])),O);let St=e2;e=D,t=g,n=X,r=H,i=w,o=y,s=v,u=L+1,l=Z,c=V,C=A,f=St,d=q,m=W}else{let k=K,X=$.tail,N=t1,D=x.tail,z=U(N),T=U(k),I=f2(L-y,z,k),B,W=V9(u1,V,L,N);B=W9(W,q,V,L,k);let h1=B;e=D,t=g,n=X,r=H,i=w,o=y-z+T,s=v,u=L+T,l=Z,c=V,C=_(I,A),f=O,d=q,m=h1}}}}}}function at(e,t,n){return ct(a([t]),H1(),a([n]),H1(),le(),0,0,0,0,O7,E,E,a1,I3(e))}var x5=class{offset=0;#e=null;#t=()=>{};#n=!1;#i=!1;constructor(t,n,{useServerEvents:r=!1,exposeKeys:i=!1}={}){this.#e=t,this.#t=n,this.#n=r,this.#i=i}mount(t){$5(this.#e,this.#d(this.#e,0,t))}#r=[];push(t){let n=this.offset;n&&(k9(t.changes,r=>{switch(r.kind){case d5:case f5:r.before=(r.before|0)+n;break;case _5:case p5:r.from=(r.from|0)+n;break}}),k9(t.children,r=>{r.index=(r.index|0)+n})),this.#r.push({node:this.#e,patch:t}),this.#C()}#C(){let t=this;for(;t.#r.length;){let{node:n,patch:r}=t.#r.pop();k9(r.changes,s=>{switch(s.kind){case d5:t.#a(n,s.children,s.before);break;case f5:t.#f(n,s.key,s.before,s.count);break;case st:t.#u(n,s.key,s.count);break;case _5:t.#o(n,s.from,s.count);break;case p5:t.#s(n,s.from,s.count,s.with);break;case rt:t.#_(n,s.content);break;case it:t.#c(n,s.inner_html);break;case ot:t.#p(n,s.added,s.removed);break}}),r.removed&&t.#o(n,n.childNodes.length-r.removed,r.removed);let i=-1,o=null;k9(r.children,s=>{let u=s.index|0,l=o&&i-u===1?o.previousSibling:he(n,u);t.#r.push({node:l,patch:s}),o=l,i=u})}}#a(t,n,r){let i=u0(),o=r|0;k9(n,s=>{let u=this.#d(t,o,s);$5(i,u),o+=U(s)}),Ct(t,i,he(t,r))}#f(t,n,r,i){let o=l0(t,n),s=he(t,r);for(let u=0;u0&&n!==null;){let i=n.nextSibling,o=n[e1].key;o&&t[e1].keyedChildren.delete(o);for(let[s,{timeout:u}]of n[e1].debouncers??[])clearTimeout(u);t.removeChild(n),n=i}}#s(t,n,r,i){this.#o(t,n,r);let o=this.#d(t,n,i);Ct(t,o,he(t,n))}#_(t,n){t.data=n??""}#c(t,n){t.innerHTML=n??""}#p(t,n,r){k9(r,i=>{let o=i.name;t[e1].handlers.has(o)?(t.removeEventListener(o,ft),t[e1].handlers.delete(o),t[e1].throttles.has(o)&&t[e1].throttles.delete(o),t[e1].debouncers.has(o)&&(clearTimeout(t[e1].debouncers.get(o).timeout),t[e1].debouncers.delete(o))):(t.removeAttribute(o),a0[o]?.removed?.(t,o))}),k9(n,i=>{this.#$(t,i)})}#d(t,n,r){switch(r.kind){case D7:{let i=o0(t,n,r);return this.#m(i,r),this.#a(i,r.children),i}case U7:return s0(t,n,r);case I7:{let i=u0(),o=s0(t,n,r);$5(i,o);let s=n+1;return k9(r.children,u=>{$5(i,this.#d(t,s,u)),s+=U(u)}),i}case T3:{let i=o0(t,n,r);return this.#m(i,r),this.#c(i,r.inner_html),i}}}#m(t,{key:n,attributes:r}){this.#i&&n&&t.setAttribute("data-lustre-key",n),k9(r,i=>this.#$(t,i))}#$(t,n){let{debouncers:r,handlers:i,throttles:o}=t[e1],{kind:s,name:u,value:l,prevent_default:c,stop_propagation:C,immediate:f,include:d,debounce:m,throttle:x}=n;switch(s){case k7:{let g=l??"";if(u==="virtual:defaultValue"){t.defaultValue=g;return}g!==t.getAttribute(u)&&t.setAttribute(u,g),a0[u]?.added?.(t,l);break}case V3:t[u]=l;break;case j7:{if(i.has(u)&&t.removeEventListener(u,ft),t.addEventListener(u,ft,{passive:c.kind===E7}),x>0){let g=o.get(u)??{};g.delay=x,o.set(u,g)}else o.delete(u);if(m>0){let g=r.get(u)??{};g.delay=m,r.set(u,g)}else clearTimeout(r.get(u)?.timeout),r.delete(u);i.set(u,g=>{c.kind===A7&&g.preventDefault(),C.kind===A7&&g.stopPropagation();let $=g.type,H=g.currentTarget[e1].path,w=this.#n?i6(g,d??[]):g,y=o.get($);if(y){let L=Date.now(),Z=y.last||0;L>Z+y.delay&&(y.last=L,y.lastEvent=g,this.#t(w,H,$,f))}let v=r.get($);v&&(clearTimeout(v.timeout),v.timeout=setTimeout(()=>{g!==o.get($)?.lastEvent&&this.#t(w,H,$,f)},v.delay)),!y&&!v&&this.#t(w,H,$,f)});break}}}},k9=(e,t)=>{if(Array.isArray(e))for(let n=0;ne.appendChild(t),Ct=(e,t,n)=>e.insertBefore(t,n??null),o0=(e,t,{key:n,tag:r,namespace:i})=>{let o=I1().createElementNS(i||Ke,r);return p2(e,o,t,n),o},s0=(e,t,{key:n,content:r})=>{let i=I1().createTextNode(r??"");return p2(e,i,t,n),i},u0=()=>I1().createDocumentFragment(),he=(e,t)=>e.childNodes[t|0],e1=Symbol("lustre"),p2=(e,t,n=0,r="")=>{let i=`${r||n}`;switch(t.nodeType){case Qe:case t5:t[e1]={key:r,path:i,keyedChildren:new Map,handlers:new Map,throttles:new Map,debouncers:new Map};break;case e5:t[e1]={key:r};break}e&&e[e1]&&r&&e[e1].keyedChildren.set(r,new WeakRef(t)),e&&e[e1]&&e[e1].path&&(t[e1].path=`${e[e1].path}${s5}${i}`)};var l0=(e,t)=>e[e1].keyedChildren.get(t).deref(),ft=e=>{let n=e.currentTarget[e1].handlers.get(e.type);e.type==="submit"&&(e.detail??={},e.detail.formData=[...new FormData(e.target).entries()]),n(e)},i6=(e,t=[])=>{let n={};(e.type==="input"||e.type==="change")&&t.push("target.value"),e.type==="submit"&&t.push("detail.formData");for(let r of t){let i=r.split(".");for(let o=0,s=e,u=n;o({added(t){t[e]=!0},removed(t){t[e]=!1}}),o6=e=>({added(t,n){t[e]=n}}),a0={checked:c0("checked"),selected:c0("selected"),value:o6("value"),autofocus:{added(e){queueMicrotask(()=>e.focus?.())}},autoplay:{added(e){try{e.play?.()}catch(t){console.error(t)}}}};var p0=e=>{let t=d0(null,e,"");if(t===null||t.children instanceof b){let n=C0(e);return e.appendChild(n),G3()}else{if(t.children instanceof U1&&t.children.tail instanceof b)return t.children.head;{let n=C0(e);return e.insertBefore(n,e.firstChild),v9(t.children)}}},C0=e=>{let t=I1().createTextNode("");return p2(e,t),t},d0=(e,t,n)=>{switch(t.nodeType){case Qe:{let r=t.getAttribute("data-lustre-key");p2(e,t,n,r),r&&t.removeAttribute("data-lustre-key");let i=t.localName,o=t.namespaceURI,s=!o||o===Ke;s&&s6.includes(i)&&u6(i,t);let u=l6(t),l=f0(t),c=s?_1(i,u,l):T2(o,i,u,l);return r?A2(r,c):c}case e5:return p2(e,t,n),t.data?B2(t.data):null;case t5:return p2(e,t,n),t.childNodes.length>0?v9(f0(t)):null;default:return null}},s6=["input","select","textarea"],u6=(e,t)=>{let n=t.value,r=t.checked;e==="input"&&t.type==="checkbox"&&!r||e==="input"&&t.type==="radio"&&!r||t.type!=="checkbox"&&t.type!=="radio"&&!n||queueMicrotask(()=>{t.value=n,t.checked=r,t.dispatchEvent(new Event("input",{bubbles:!0})),t.dispatchEvent(new Event("change",{bubbles:!0})),I1().activeElement!==t&&t.dispatchEvent(new Event("blur",{bubbles:!0}))})},f0=e=>{let t=null,n=0,r=e.firstChild,i=null;for(;r;){let o=d0(e,r,n),s=r.nextSibling;if(o){let u=new U1(o,null);i?i=i.tail=u:i=t=u,n+=1}else e.removeChild(r);r=s}return i?(i.tail=E,t):E},l6=e=>{let t=e.attributes.length,n=E;for(;t-- >0;)n=new U1(c6(e.attributes[t]),n);return n},c6=e=>{let t=e.localName,n=e.value;return j(t,n)};var d2=()=>!!I1();var q2=class{constructor(t,[n,r],i,o){this.root=t,this.#e=n,this.#t=i,this.#n=o,this.#C=new x5(this.root,(s,u,l)=>{let[c,C]=l5(this.#r,u,l,s);if(this.#r=c,C.isOk()){let f=C[0];f.stop_propagation&&s.stopPropagation(),f.prevent_default&&s.preventDefault(),this.dispatch(f.message,!1)}}),this.#i=p0(this.root),this.#r=G7(),this.#s=!0,this.#c(r)}root=null;set offset(t){this.#C.offset=t}dispatch(t,n=!1){if(this.#s||=n,this.#a)this.#f.push(t);else{let[r,i]=this.#n(this.#e,t);this.#e=r,this.#c(i)}}emit(t,n){(this.root.host??this.root).dispatchEvent(new CustomEvent(t,{detail:n,bubbles:!0,composed:!0}))}#e;#t;#n;#i;#r;#C;#a=!1;#f=[];#u=E;#o=E;#l=null;#s=!1;#_={dispatch:(t,n)=>this.dispatch(t,n),emit:(t,n)=>this.emit(t,n),select:()=>{},root:()=>this.root};#c(t){for(this.#a=!0;;){for(let n=t.synchronous;n.tail;n=n.tail)n.head(this.#_);if(this.#u=m0(this.#u,t.before_paint),this.#o=m0(this.#o,t.after_paint),!this.#f.length)break;[this.#e,t]=this.#n(this.#e,this.#f.shift())}this.#a=!1,this.#s?(cancelAnimationFrame(this.#l),this.#p()):this.#l||(this.#l=requestAnimationFrame(()=>{this.#p()}))}#p(){this.#s=!1,this.#l=null;let t=this.#t(this.#e),{patch:n,events:r}=at(this.#r,this.#i,t);if(this.#r=r,this.#i=t,this.#C.push(n),this.#u instanceof U1){let i=_0(this.#u);this.#u=E,queueMicrotask(()=>{this.#s=!0,this.#c(i)})}if(this.#o instanceof U1){let i=_0(this.#o);this.#o=E,requestAnimationFrame(()=>{this.#s=!0,this.#c(i)})}}};function _0(e){return{synchronous:e,after_paint:E,before_paint:E}}function m0(e,t){return e instanceof b?t:t instanceof b?e:I9(e,t)}var $0=new WeakMap;async function x0(e){let t=[];for(let r of I1().querySelectorAll("link[rel=stylesheet], style"))r.sheet||t.push(new Promise((i,o)=>{r.addEventListener("load",i),r.addEventListener("error",o)}));if(await Promise.allSettled(t),!e.host.isConnected)return[];e.adoptedStyleSheets=e.host.getRootNode().adoptedStyleSheets;let n=[];for(let r of I1().styleSheets)try{e.adoptedStyleSheets.push(r)}catch{try{let i=$0.get(r);if(!i){i=new CSSStyleSheet;for(let o of r.cssRules)i.insertRule(o.cssText,i.cssRules.length);$0.set(r,i)}e.adoptedStyleSheets.push(i)}catch{let i=r.ownerNode.cloneNode();e.prepend(i),n.push(i)}}return n}var Y9=class extends p{constructor(t){super(),this.message=t}},_2=class extends p{constructor(t,n){super(),this.name=t,this.data=n}};var J9=class extends p{};var i9=({init:e,update:t,view:n,config:r},i)=>{if(!d2())return new M(new I2);if(!i.includes("-"))return new M(new h5(i));if(customElements.get(i))return new M(new X9(i));let[o,s]=e(void 0),u=r.attributes.entries().map(([c])=>c),l=class extends HTMLElement{static get observedAttributes(){return u}static formAssociated=r.is_form_associated;#e;#t=[];#n;constructor(){super(),this.internals=this.attachInternals(),this.internals.shadowRoot?this.#n=this.internals.shadowRoot:this.#n=this.attachShadow({mode:r.open_shadow_root?"open":"closed"}),r.adopt_styles&&this.#i(),this.#e=new q2(this.#n,[o,s],n,t)}adoptedCallback(){r.adopt_styles&&this.#i()}attributeChangedCallback(C,f,d){let m=r.attributes.get(C)(d);m.constructor===h&&this.dispatch(m[0])}formResetCallback(){r.on_form_reset instanceof F&&this.dispatch(r.on_form_reset[0])}formStateRestoreCallback(C,f){switch(f){case"restore":r.on_form_restore instanceof F&&this.dispatch(r.on_form_restore[0](C));break;case"autocomplete":r.on_form_populate instanceof F&&this.dispatch(r.on_form_autofill[0](C));break}}send(C){switch(C.constructor){case Y9:{this.dispatch(C.message,!1);break}case _2:{this.emit(C.name,C.data);break}case J9:break}}dispatch(C,f=!1){this.#e.dispatch(C,f)}emit(C,f){this.#e.emit(C,f)}async#i(){for(;this.#t.length;)this.#t.pop().remove(),this.shadowRoot.firstChild.remove();this.#t=await x0(this.#n),this.#e.offset=this.#t.length}};return r.properties.forEach((c,C)=>{Object.defineProperty(l.prototype,C,{get(){return this[`_${C}`]},set(f){this[`_${C}`]=f;let d=R1(f,c);d.constructor===h&&this.dispatch(d[0])}})}),customElements.define(i,l),new h(void 0)};var L0=(e,t)=>{d2()&&e instanceof ShadowRoot&&e.host.internals.states.add(t)},b0=(e,t)=>{d2()&&e instanceof ShadowRoot&&e.host.internals.states.delete(t)};var Le=class extends p{constructor(t,n,r,i,o,s,u,l){super(),this.open_shadow_root=t,this.adopt_styles=n,this.attributes=r,this.properties=i,this.is_form_associated=o,this.on_form_autofill=s,this.on_form_reset=u,this.on_form_restore=l}},L5=class extends p{constructor(t){super(),this.apply=t}};function w0(e){let t=new Le(!1,!0,Xe(),Xe(),!1,n5,n5,n5);return m1(e,t,(n,r)=>r.apply(n))}function j1(e,t){return new L5(n=>{let r=Q1(n.attributes,e,t),i=n;return new Le(i.open_shadow_root,i.adopt_styles,r,i.properties,i.is_form_associated,i.on_form_autofill,i.on_form_reset,i.on_form_restore)})}function m2(e){return new L5(t=>{let n=t;return new Le(n.open_shadow_root,e,n.attributes,n.properties,n.is_form_associated,n.on_form_autofill,n.on_form_reset,n.on_form_restore)})}function E1(e){return Ce((t,n)=>L0(n,e))}function be(e){return Ce((t,n)=>b0(n,e))}var pt=class e{static start({init:t,update:n,view:r},i,o){if(!d2())return new M(new I2);let s=i instanceof HTMLElement?i:I1().querySelector(i);return s?new h(new e(s,t(o),n,r)):new M(new b5(i))}#e;constructor(t,[n,r],i,o){this.#e=new q2(t,[n,r],o,i)}send(t){switch(t.constructor){case Y9:{this.dispatch(t.message,!1);break}case _2:{this.emit(t.name,t.data);break}case J9:break}}dispatch(t,n){this.#e.dispatch(t,n)}emit(t,n){this.#e.emit(t,n)}},f6=pt.start;var dt=class extends p{constructor(t,n,r,i){super(),this.init=t,this.update=n,this.view=r,this.config=i}};var h5=class extends p{constructor(t){super(),this.name=t}},X9=class extends p{constructor(t){super(),this.name=t}},b5=class extends p{constructor(t){super(),this.selector=t}},I2=class extends p{};function d9(e,t,n,r){return new dt(e,t,n,w0(r))}function $2(e){return e[0]}function _t(e){return e[1]}function n1(e,t){return E3(e,t)}function p6(e){return e==="input"||e==="change"||e==="focus"||e==="focusin"||e==="focusout"||e==="blur"?!0:e==="select"}function J(e,t){return v3(e,Z1(t,n=>new v2(!1,!1,n)),E,S7,S7,p6(e),0,0)}function g0(e){return J("mousedown",S(e))}function H0(e){return J("mouseover",S(e))}function M0(e){return J("input",e9(a(["target","value"]),$1,t=>S(e(t))))}function y0(e){return J("focus",S(e))}function Z0(e){return J("blur",S(e))}var V0=e=>{if(!(e instanceof HTMLSlotElement))return new M(void 0);let t=e.assignedElements();return new h(L1.fromArray(t))},v0=e=>{if(!(e instanceof HTMLElement))return new M(void 0);let t=e.getBoundingClientRect();return new h(new He(t.top,t.right,t.bottom,t.left,t.width,t.height))},k0=(e,t)=>{if(!(e instanceof HTMLElement))return new M(void 0);if(typeof t!="string")return new M(void 0);let n=e.getAttribute(t);return n===null?new M(void 0):new h(n)},j0=e=>{e instanceof Event&&e.preventDefault()},mt=(e,t=document)=>{if(typeof e!="string")return new M(void 0);if(!(t instanceof Document||t instanceof Element))return new M(void 0);let n=t.querySelector(e);return n===null?new M(void 0):new h(n)},ge=e=>{e instanceof HTMLElement&&e.focus()};var He=class extends p{constructor(t,n,r,i,o,s){super(),this.top=t,this.right=n,this.bottom=r,this.left=i,this.width=o,this.height=s}};function j9(e,t){let n=Pe(Z1(e,r=>new h(r)),a([S(new M(void 0))]));return re("HTMLSlotElement.assignedElements()",r=>{let i=V0(r);if(i instanceof h)if(t){let s=i[0],u=Ue(s,c=>R1(c,n)),l=o1(u,c=>h7(c,a1));return se(l,a([]))}else{let s=i[0],u=Ue(s,l=>R1(l,e));return se(u,a([]))}else return new M(a([]))})}function Me(){return re("Element.getBoundingClientRect()",e=>{let t=v0(e);if(t instanceof h){let n=t[0];return new h(n)}else return new M(new He(0,0,0,0,0,0))})}function M5(e){return re("Element.getAttribute()",t=>{let n=k0(t,e);if(n instanceof h){let r=n[0];return new h(r)}else return new M("")})}function C1(e){return j3(t=>j0(e))}function y5(e,t,n){return re("Element.querySelector()",r=>{let i=mt(e,r);if(i instanceof h){let o=i[0],s=R1(o,n);return se(s,t)}else return new M(t)})}function E0(e){return Ce((t,n)=>{let r=mt(e,n);if(r instanceof h){let i=r[0];return ge(i)}else return})}var D2=class extends p{constructor(t,n){super(),this.height=t,this.expanded=n}},V5=class extends p{constructor(t){super(),this[0]=t}},U2=class extends p{constructor(t){super(),this[0]=t}},F2=class extends p{constructor(t,n){super(),this[0]=t,this.event=n}};function S0(e){return j("aria-expanded",(()=>{let t=Je(e);return w1(t)})())}function A0(e){return J("change",e9(a(["detail","expanded"]),Re,t=>S(e(t))))}function d6(e){let t=new D2(0,!1),n=Y();return[t,n]}function _6(e,t){if(t instanceof V5){let n=t[0];return[(()=>{let r=e;return new D2(n,r.expanded)})(),Y()]}else if(t instanceof U2){let n=t[0];return[(()=>{let r=e;return new D2(r.height,n)})(),Y()]}else{let n=t[0],r=t.event,i,o=e;i=new D2(n,o.expanded);let s=i,u=n1("change",V1(a([["expanded",a9(!s.expanded)]]))),l;s.expanded?l=n1("collapse",g9()):l=n1("expand",g9());let f=s1(a([u,l,C1(r)]));return[s,f]}}function m6(){return e9(a(["currentTarget","nextElementSibling","firstElementChild"]),j9((()=>{let e=Me();return Z1(e,t=>t.height)})(),!1),e=>{let t=oe(e);return S(new F2(t,r2()))})}function $6(){return l9(O1,e=>i1("key",$1,t=>t==="Enter"?e9(a(["currentTarget","nextElementSibling","firstElementChild"]),j9((()=>{let n=Me();return Z1(n,r=>r.height)})(),!1),n=>{let r=oe(n);return S(new F2(r,e))}):t===" "?e9(a(["currentTarget","nextElementSibling","firstElementChild"]),j9((()=>{let n=Me();return Z1(n,r=>r.height)})(),!1),n=>{let r=oe(n);return S(new F2(r,e))}):N1(new F2(0,r2()),"")))}function x6(){return Y1(a([j("part","collapse-trigger"),Z9("trigger"),J("click",m6()),J("keydown",$6())]),a([]))}function h6(){return e9(a(["currentTarget","nextElementSibling","firstElementChild"]),j9((()=>{let e=Me();return Z1(e,t=>t.height)})(),!1),e=>{let t=oe(e);return S(new V5(t))})}function L6(e){return D1(a([j("part","collapse-content"),k2(a([["transition-duration","inherit"],["height",e]]))]),a([Y1(a([J("slotchange",h6())]),a([]))]))}var z0="lustre-ui-collapse";function T0(e,t,n){return _1(z0,e,a([D1(a([j("slot","trigger")]),a([t])),n]))}function b6(e){let t;e.expanded?t=w7(e.height)+"px":t="0px";let r=t;return v9(a([x6(),L6(r)]))}function v5(){let e=d9(d6,_6,b6,a([m2(!0),j1("aria-expanded",t=>t==="true"?new h(new U2(!0)):t==="false"?new h(new U2(!1)):t===""?new h(new U2(!1)):new M(void 0))]));return i9(e,z0)}var $t=class extends p{},xt=class extends p{},ht=class extends p{},Lt=class extends p{},bt=class extends p{},wt=class extends p{},gt=class extends p{},Ve=class extends p{},Ht=class extends p{},Mt=class extends p{},yt=class extends p{};var x2=class extends p{},ye=class extends p{},h2=class extends p{},Ze=class extends p{},G2=class extends p{},P2=class extends p{constructor(t){super(),this[0]=t}},j5=class extends p{},E5=class extends p{},R2=class extends p{constructor(t){super(),this.event=t}};function B0(e){return j("aria-expanded",(()=>{let t=Je(e);return w1(t)})())}function O0(e){return j("anchor",e instanceof $t?"top-left":e instanceof xt?"top-middle":e instanceof ht?"top-right":e instanceof Lt?"right-top":e instanceof bt?"right-middle":e instanceof wt?"right-bottom":e instanceof gt?"bottom-left":e instanceof Ve?"bottom-middle":e instanceof Ht?"bottom-right":e instanceof Mt?"left-top":e instanceof yt?"left-middle":"left-bottom")}function N0(){return j("equal-width","")}function q0(e){return y9("--gap",e)}function I0(e){return J("open",S(e))}function D0(e){return J("close",S(e))}function g6(e){let t=new G2,n=s1(a([E1("collapsed")]));return[t,n]}function k5(){return fe((e,t)=>e(new j5))}function H6(e,t){let n=v6(t,"src/lustre/ui/primitives/popover.gleam",162);if(n instanceof P2)return n[0]?e instanceof h2?[new x2,s1(a([k5(),E1("will-expand")]))]:e instanceof G2?[new x2,s1(a([k5(),E1("will-expand")]))]:[e,Y()]:e instanceof x2?[new h2,s1(a([k5(),E1("will-collapse")]))]:e instanceof ye?[new h2,s1(a([k5(),E1("will-collapse")]))]:[e,Y()];if(n instanceof j5)return e instanceof x2?[new ye,E1("expanded")]:e instanceof h2?[new Ze,E1("collapsing")]:[e,Y()];if(n instanceof E5)return e instanceof Ze?[new G2,E1("collapsed")]:[e,Y()];if(e instanceof x2){let r=n.event;return[e,s1(a([n1("close",g9()),n1("change",V1(a([["open",a9(!1)]]))),C1(r)]))]}else if(e instanceof ye){let r=n.event;return[e,s1(a([n1("close",g9()),n1("change",V1(a([["open",a9(!1)]]))),C1(r)]))]}else if(e instanceof h2){let r=n.event;return[e,s1(a([n1("open",g9()),n1("change",V1(a([["open",a9(!0)]]))),C1(r)]))]}else if(e instanceof Ze){let r=n.event;return[e,s1(a([n1("open",g9()),n1("change",V1(a([["open",a9(!0)]]))),C1(r)]))]}else{let r=n.event;return[e,s1(a([n1("open",g9()),n1("change",V1(a([["open",a9(!0)]]))),C1(r)]))]}}function M6(){return l9(O1,e=>i1("key",$1,t=>t==="Enter"?S(new R2(e)):t===" "?S(new R2(e)):N1(new R2(e),"")))}function y6(){return Y1(a([Z9("trigger"),J("click",Z1(O1,e=>new R2(e))),J("keydown",M6())]),a([]))}function Z6(e){return f9(P(e,new G2),a2(""),()=>D1(a([j("part","popover-content"),J("transitionend",S(new E5))]),a([Y1(a([Z9("popover")]),a([]))])))}function V6(e){return D1(a([y9("position","relative")]),a([y6(),Z6(e)]))}var U0="lustre-ui-popover";function S5(){let e=d9(g6,H6,V6,a([m2(!0),j1("aria-expanded",t=>t==="true"?new h(new P2(!0)):t===""?new h(new P2(!0)):t==="false"?new h(new P2(!1)):new M(void 0))]));return i9(e,U0)}function F0(e,t,n){return _1(U0,e,a([D1(a([j("slot","trigger")]),a([t])),D1(a([j("slot","popover")]),a([n]))]))}function v6(e,t,n){let r="\x1B[90m",i="\x1B[39m",o=`${t}:${n}`,s=E9(e);if(globalThis.process?.stderr?.write){let u=`${r}${o}${i} +${s} +`;process.stderr.write(u)}else if(globalThis.Deno){let u=`${r}${o}${i} +${s} +`;globalThis.Deno.stderr.writeSync(new TextEncoder().encode(u))}else{let u=`${o} +${s}`;globalThis.console.log(u)}return e}function k6(e){let t='"';for(let n=0;n"~"&&r<"\xA0"?t+="\\u{"+r.charCodeAt(0).toString(16).toUpperCase().padStart(4,"0")+"}":t+=r}return t+='"',t}function j6(e){let t="dict.from_list([",n=!0,r=[];return e.forEach((i,o)=>{r.push([o,i])}),r.sort(),r.forEach(([i,o])=>{n||(t=t+", "),t=t+"#("+E9(i)+", "+E9(o)+")",n=!1}),t+"])"}function E6(e){let t=globalThis.Object.keys(e).map(n=>{let r=E9(e[n]);return isNaN(parseInt(n))?`${n}: ${r}`:r}).join(", ");return t?`${e.constructor.name}(${t})`:e.constructor.name}function S6(e){let t=Object.getPrototypeOf(e)?.constructor?.name||"Object",n=[];for(let o of Object.keys(e))n.push(`${E9(o)}: ${E9(e[o])}`);let r=n.length?" "+n.join(", ")+" ":"";return`//js(${t==="Object"?"":t+" "}{${r}})`}function E9(e){let t=typeof e;if(e===!0)return"True";if(e===!1)return"False";if(e===null)return"//js(null)";if(e===void 0)return"Nil";if(t==="string")return k6(e);if(t==="bigint"||t==="number")return e.toString();if(globalThis.Array.isArray(e))return`#(${e.map(E9).join(", ")})`;if(e instanceof L1)return`[${e.toArray().map(E9).join(", ")}]`;if(e instanceof t2)return`//utfcodepoint(${String.fromCodePoint(e.value)})`;if(e instanceof X1)return A6(e);if(e instanceof p)return E6(e);if(z6(e))return j6(e);if(e instanceof Set)return`//js(Set(${[...e].map(E9).join(", ")}))`;if(e instanceof RegExp)return`//js(${e})`;if(e instanceof Date)return`//js(Date("${e.toISOString()}"))`;if(e instanceof Function){let n=[];for(let r of Array(e.length).keys())n.push(String.fromCharCode(r+97));return`//fn(${n.join(", ")}) { ... }`}return S6(e)}function A6(e){let t=e.bitOffset+8*Math.trunc(e.bitSize/8),n=H2(e,e.bitOffset,t),r=e.bitSize%8;if(r>0){let i=ze(e,t,e.bitSize,!1,!1),o=Array.from(n.rawBuffer),s=`${i}:size(${r})`;return o.length===0?`<<${s}>>`:`<<${Array.from(n.rawBuffer).join(", ")}, ${s}>>`}else return`<<${Array.from(n.rawBuffer).join(", ")}>>`}function z6(e){try{return e instanceof z1}catch{return!1}}function B6(e,t,n,r){for(;;){let i=e,o=t,s=n,u=r;if(i instanceof b)return[o,R(s),u];{let l=i.tail,c=i.head[0],C=i.head[1],f=A2(c,C),d;c===""?d=o:d=P9(o,c,f);let m=d,x=_(f,s),g=u+U(f);e=l,t=m,n=x,r=g}}}function P0(e){return B6(e,H1(),E,0)}function Zt(e,t,n){let r=P0(n),i=r[0],o=r[1];return de("",a1,"",e,t,o,i,!1,!1)}function R0(e){let t=P0(e),n=t[0],r=t[1],i=t[2];return u5("",a1,r,n,i)}function G0(e,t){return Zt("ul",e,t)}function K9(){return[u9(),u9()]}function Y0(e,t){return $7(e[0],t)}function S9(e,t){return x9(e[0],t)}function A5(e,t){return x9(e[1],t)}function ve(e,t){let n=Q2(e[1]),r=o2(n,(o,s)=>t(o[0],s[0])),i=q9(r);return o1(i,_t)}function ke(e,t){let n=Q2(e[1]),r=o2(n,(o,s)=>t(s[0],o[0])),i=q9(r);return o1(i,_t)}function Vt(e,t,n){let r=S9(e,t),i=o1(r,n);return g7(i,o=>A5(e,o))}function J0(e,t,n){return[Q1(e[0],t,n),Q1(e[1],n,t)]}function z5(e){return m1(e,K9(),(t,n)=>J0(t,n[0],n[1]))}function T5(e){return Wt(e,K9(),(t,n,r)=>J0(t,n,r))}var O6="http://www.w3.org/2000/svg";function K0(e){return T2(O6,"path",e,E)}function vt(e,t){return J3(_(j("viewBox","0 0 15 15"),_(j("fill","none"),_(M9("lustre-ui-icon"),e))),a([K0(a([j("d",t),j("fill","currentColor"),j("fill-rule","evenodd"),j("clip-rule","evenodd")]))]))}function B5(e){return vt(e,"M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z")}function Q0(e){return vt(e,"M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z")}function e4(e){return vt(e,"M10 6.5C10 8.433 8.433 10 6.5 10C4.567 10 3 8.433 3 6.5C3 4.567 4.567 3 6.5 3C8.433 3 10 4.567 10 6.5ZM9.30884 10.0159C8.53901 10.6318 7.56251 11 6.5 11C4.01472 11 2 8.98528 2 6.5C2 4.01472 4.01472 2 6.5 2C8.98528 2 11 4.01472 11 6.5C11 7.56251 10.6318 8.53901 10.0159 9.30884L12.8536 12.1464C13.0488 12.3417 13.0488 12.6583 12.8536 12.8536C12.6583 13.0488 12.3417 13.0488 12.1464 12.8536L9.30884 10.0159Z")}var W2=class extends p{constructor(t,n,r){super(),this.options=t,this.expanded=n,this.mode=r}},Q9=class extends p{},L2=class extends p{},N5=class extends p{},q5=class extends p{constructor(t,n,r){super(),this.all=t,this.lookup_label=n,this.lookup_index=r}},I5=class extends p{constructor(t){super(),this[0]=t}},A9=class extends p{constructor(t){super(),this[0]=t}},D5=class extends p{constructor(t,n){super(),this[0]=t,this.event=n}},U5=class extends p{},je=class extends p{},F5=class extends p{constructor(t,n){super(),this[0]=t,this.event=n}},kt=class extends p{constructor(t){super(),this[0]=t}};function n4(e){let t=new q5(a([]),K9(),K9()),n=new W2(t,v1(),new Q9),r=Y();return[n,r]}function O5(e){let t="[data-lustre-key="+e+"] [part=accordion-trigger]";return E0(t)}function r4(e,t){if(t instanceof I5){let n=t[0],r=z5(n),i=T5(y1(n,$2)),o=new q5(n,r,i),s=m3(e.expanded,$=>Y0(r,$)),u=y1(n,$2),l,c=e.mode,C=y7(s);if(c instanceof Q9)if(C===0)l=s;else if(C===1)l=s;else{let $=ne(u,w=>k1(s,w)),H=o1($,w=>H9(a([w])));l=q1(H,v1())}else if(c instanceof L2)if(C===0){let $=q9(u),H=o1($,w=>H9(a([w])));l=q1(H,v1())}else if(C===1)l=s;else{let $=ne(u,w=>k1(s,w)),H=o1($,w=>H9(a([w])));l=q1(H,v1())}else l=s;let f=l,d,m=e;d=new W2(o,f,m.mode);let x=d,g=Y();return[x,g]}else if(t instanceof A9){let n=t[0],r=y1(e.options.all,$2),i,o=y7(e.expanded);if(n instanceof Q9)if(o===0)i=e.expanded;else if(o===1)i=e.expanded;else{let f=ne(r,m=>k1(e.expanded,m)),d=o1(f,m=>H9(a([m])));i=q1(d,v1())}else if(n instanceof L2)if(o===0){let f=q9(r),d=o1(f,m=>H9(a([m])));i=q1(d,v1())}else if(o===1)i=e.expanded;else{let f=ne(r,m=>k1(e.expanded,m)),d=o1(f,m=>H9(a([m])));i=q1(d,v1())}else i=e.expanded;let s=i,u,l=e;u=new W2(l.options,s,n);let c=u,C=Y();return[c,C]}else if(t instanceof D5){let n=t[0],r=t.event,i=Ye(S9(e.options.lookup_index,n),o=>o1(A5(e.options.lookup_index,o+1),s=>O5(s)));return[e,s1(a([q1(i,Y()),C1(r)]))]}else if(t instanceof U5){let n=o1(ke(e.options.lookup_index,T1),r=>O5(r));return[e,q1(n,Y())]}else if(t instanceof je){let n=o1(ve(e.options.lookup_index,T1),r=>O5(r));return[e,q1(n,Y())]}else if(t instanceof F5){let n=t[0],r=t.event,i=Ye(S9(e.options.lookup_index,n),o=>o1(A5(e.options.lookup_index,o-1),s=>O5(s)));return[e,s1(a([q1(i,Y()),C1(r)]))]}else{let n=t[0],r,i=k1(e.expanded,n),o=e.mode;o instanceof Q9?i?r=v1():r=H9(a([n])):o instanceof L2?i?r=e.expanded:r=H9(a([n])):i?r=_3(e.expanded,n):r=u2(e.expanded,n);let s=r,u,l=e;u=new W2(l.options,s,l.mode);let c=u,C;return k1(s,n)?C=n1("expand",t9(n)):C=n1("collapse",t9(n)),[c,C]}}function I6(){return i1("target",j9(i1("tagName",$1,e=>l9(M5("value"),t=>i1("textContent",$1,n=>S([e,t,n])))),!0),e=>{let n=te(e,[a([]),v1()],(o,s)=>{let u=s[0],l=s[1],c=s[2];return f9(u!=="LUSTRE-UI-ACCORDION-ITEM",o,()=>f9(k1(o[1],l),o,()=>{let C=u2(o[1],l);return[_([l,c],o[0]),C]}))}),r=$2(n),i=new I5(r);return S(i)})}function D6(e){return l9(O1,t=>i1("key",$1,n=>n==="ArrowDown"?S(new D5(e,t)):n==="ArrowUp"?S(new F5(e,t)):n==="End"?S(new U5):n==="Home"?S(new je):N1(new je,"")))}var i4="lustre-ui-accordion";function o4(e){return v9(a([Y1(a([y9("display","none"),J("slotchange",I6())]),a([])),R0(y1(e.options.all,t=>{let n=t[0],r=t[1],i=k1(e.expanded,n),o=T0(a([S0(i),A0(s=>new kt(n))]),C5(a([j("part","accordion-trigger"),j("tabindex","0"),J("keydown",D6(n))]),a([Y3(a([j("part","accordion-trigger-label")]),a([a2(r)])),B5(a([j("part",i?"accordion-trigger-icon expanded":"accordion-trigger-icon")]))])),Y1(a([j("part","accordion-content"),Z9(n)]),a([])));return[n,o]}))]))}function s4(){let e=v5();if(e instanceof h){let t=d9(n4,r4,o4,a([j1("mode",n=>n==="at-most-one"?new h(new A9(new Q9)):n==="exactly-one"?new h(new A9(new L2)):n==="multi"?new h(new A9(new N5)):new M(void 0))]));return i9(t,i4)}else if(e[0]instanceof X9){let n=d9(n4,r4,o4,a([j1("mode",r=>r==="at-most-one"?new h(new A9(new Q9)):r==="exactly-one"?new h(new A9(new L2)):r==="multi"?new h(new A9(new N5)):new M(void 0))]));return i9(n,i4)}else return e}function u4(e){return X3(_(M9("lustre-ui-input"),e))}function l4(e,t){return D1(_(M9("lustre-ui-input-container"),e),t)}var c4="src/lustre/ui/combobox.gleam",jt=class extends p{constructor(t,n,r){super(),this.value=t,this.label=n,this.content=r}},x1=class extends p{constructor(t,n,r,i,o,s,u){super(),this.expanded=t,this.value=n,this.placeholder=r,this.query=i,this.intent=o,this.intent_strategy=s,this.options=u}},w2=class extends p{},P5=class extends p{},Ee=class extends p{constructor(t,n,r,i){super(),this.all=t,this.filtered=n,this.lookup_label=r,this.lookup_index=i}},R5=class extends p{},G5=class extends p{},W5=class extends p{constructor(t){super(),this[0]=t}},Se=class extends p{constructor(t){super(),this[0]=t}},b2=class extends p{constructor(t){super(),this[0]=t}},Ae=class extends p{constructor(t){super(),this[0]=t}},_9=class extends p{constructor(t){super(),this.input=t}},Y5=class extends p{constructor(t){super(),this[0]=t}},Y2=class extends p{},J5=class extends p{constructor(t){super(),this[0]=t}},X5=class extends p{},K5=class extends p{constructor(t){super(),this.event=t}},Q5=class extends p{constructor(t){super(),this.event=t}},e7=class extends p{constructor(t){super(),this.event=t}},t7=class extends p{constructor(t,n){super(),this.event=t,this.trigger=n}},n7=class extends p{constructor(t){super(),this.event=t}},r7=class extends p{constructor(t){super(),this.event=t}},Et=class extends p{constructor(t){super(),this[0]=t}};function a4(e){let t=new x1(!1,"","Select an option...","",new l1,new w2,new Ee(a([]),a([]),K9(),K9())),n=s1(a([E1("empty")]));return[t,n]}function F6(e,t){let n=e.label,r=w1(n);return ie(r,t)}function P6(e,t,n){return f9(e==="",new l1,()=>{let r=w1(e),i=(C,f)=>{let d=w1(C.label),m=w1(f.label),x=Z2(d,r),g=Z2(m,r),$=S9(n.lookup_index,C.value);if(!($ instanceof h))throw Te("let_assert",c4,"lustre/ui/combobox",513,"intent_from_query","Pattern match failed, no pattern matched the value.",{value:$,start:14126,end:14192,pattern_start:14137,pattern_end:14148});let H=$[0],w=S9(n.lookup_index,f.value);if(!(w instanceof h))throw Te("let_assert",c4,"lustre/ui/combobox",514,"intent_from_query","Pattern match failed, no pattern matched the value.",{value:w,start:14197,end:14263,pattern_start:14208,pattern_end:14219});let y=w[0];return g?x?t instanceof w2?T1(H,y):T1(y2(d),y2(m)):new K1:x?new Q:t instanceof w2?T1(H,y):T1(y2(d),y2(m))},o=n.all,s=De(o,C=>F6(C,r)),u=o2(s,i),l=q9(u),c=o1(l,C=>C.value);return $9(c)})}function C4(e,t){let n=Q6(t,"src/lustre/ui/combobox.gleam",316);if(n instanceof R5)return[e,be("trigger-focus")];if(n instanceof G5)return[e,E1("trigger-focus")];if(n instanceof W5){let r=n[0],i=z5(y1(r,m=>[m.value,m.label])),o=T5(y1(r,m=>m.value)),s=De(r,m=>{let x=w1(m.label);return ie(x,w1(e.query))}),u=new Ee(r,s,i,o),l=new l1,c,C=e;c=new x1(C.expanded,C.value,C.placeholder,C.query,l,C.intent_strategy,u);let f=c,d=Y();return[f,d]}else if(n instanceof Se){let r=n[0];return[(()=>{let i=e;return new x1(i.expanded,i.value,r,i.query,i.intent,i.intent_strategy,i.options)})(),Y()]}else if(n instanceof b2){let r=n[0];return[(()=>{let i=e;return new x1(i.expanded,i.value,i.placeholder,i.query,i.intent,r,i.options)})(),Y()]}else if(n instanceof Ae){let r=n[0],i,o=e;i=new x1(o.expanded,r,o.placeholder,o.query,new F(r),o.intent_strategy,o.options);let s=i,u;return r===""?u=E1("empty"):u=be("empty"),[s,u]}else if(n instanceof _9){let r=n.input,i=fe((o,s)=>ge(r));return[e,i]}else if(n instanceof Y5){let r=n[0],i=De(e.options.all,m=>{let x=w1(m.label);return ie(x,w1(r))}),o,s=e.options;o=new Ee(s.all,i,s.lookup_label,s.lookup_index);let u=o,l=P6(r,e.intent_strategy,e.options),c,C=e;c=new x1(C.expanded,C.value,C.placeholder,r,l,C.intent_strategy,u);let f=c,d=Y();return[f,d]}else if(n instanceof Y2){let r,i=e;r=new x1(!1,i.value,i.placeholder,i.query,new l1,i.intent_strategy,i.options);let o=r,s=be("expanded");return[o,s]}else if(n instanceof J5){let r=n[0];return[(()=>{let i=e;return new x1(i.expanded,i.value,i.placeholder,i.query,new F(r),i.intent_strategy,i.options)})(),Y()]}else if(n instanceof X5){let r,i=e;r=new x1(!0,i.value,i.placeholder,i.query,i.intent,i.intent_strategy,i.options);let o=r,s=E1("expanded");return[o,s]}else if(n instanceof K5){let r=n.event,i,o=e.intent;if(o instanceof F){let f=o[0],d=Vt(e.options.lookup_index,f,x=>u3(x,1)),m=H7(d,new h(f));i=$9(m)}else{let f=ve(e.options.lookup_index,T1);i=$9(f)}let s=i,u,l=e;u=new x1(l.expanded,l.value,l.placeholder,l.query,s,l.intent_strategy,l.options);let c=u,C=C1(r);return[c,C]}else if(n instanceof Q5){let r=n.event,i,o=e.options.lookup_index,s=ke(o,T1);i=$9(s);let u=i,l,c=e;l=new x1(c.expanded,c.value,c.placeholder,c.query,u,c.intent_strategy,c.options);let C=l,f=C1(r);return[C,f]}else if(n instanceof e7){let r=n.event,i,o=e.intent;if(o instanceof F){let u=o[0];i=s1(a([n1("change",V1(a([["value",t9(u)]]))),C1(r)]))}else i=C1(r);return[e,i]}else if(n instanceof t7){let r=n.event,i=n.trigger,o,s=e;o=new x1(!1,s.value,s.placeholder,s.query,new l1,s.intent_strategy,s.options);let u=o,l=s1(a([be("expanded"),C1(r),fe((c,C)=>ge(i))]));return[u,l]}else if(n instanceof n7){let r=n.event,i,o=e.options.lookup_index,s=ve(o,T1);i=$9(s);let u=i,l,c=e;l=new x1(c.expanded,c.value,c.placeholder,c.query,u,c.intent_strategy,c.options);let C=l,f=C1(r);return[C,f]}else if(n instanceof r7){let r=n.event,i,o=e.intent;if(o instanceof F){let f=o[0],d=Vt(e.options.lookup_index,f,x=>l3(x,1)),m=H7(d,new h(f));i=$9(m)}else{let f=ke(e.options.lookup_index,T1);i=$9(f)}let s=i,u,l=e;u=new x1(l.expanded,l.value,l.placeholder,l.query,s,l.intent_strategy,l.options);let c=u,C=C1(r);return[c,C]}else{let r=n[0],i,s=(d=>S9(e.options.lookup_label,d))(r);i=$9(s);let u=i,l,c=e;l=new x1(c.expanded,c.value,c.placeholder,c.query,u,c.intent_strategy,c.options);let C=l,f=n1("change",V1(a([["value",t9(r)]])));return[C,f]}}function R6(){return i1("target",j9(i1("tagName",$1,e=>l9(M5("value"),t=>i1("textContent",$1,n=>S([e,t,n])))),!0),e=>{let n=te(e,[a([]),v1()],(o,s)=>{let u=s[0],l=s[1],c=s[2];return f9(u!=="LUSTRE-UI-COMBOBOX-OPTION",o,()=>f9(k1(o[1],l),o,()=>{let C=u2(o[1],l);return[_(new jt(l,c,a([])),o[0]),C]}))}),r=$2(n),i=new W5(r);return S(i)})}function G6(e){return i1("currentTarget",y5("input",r2(),O1),t=>e?S(new _9(t)):N1(new _9(t),""))}function W6(e){return i1("key",$1,t=>i1("currentTarget",y5("input",r2(),O1),n=>t==="Enter"?e?S(new _9(n)):N1(new _9(n),""):t===" "?e?S(new _9(n)):N1(new _9(n),""):N1(new _9(n),"")))}function Y6(e,t,n){let r,o=(u=>S9(n.lookup_label,u))(e);r=q1(o,t);let s=r;return C5(a([j("part","combobox-trigger"),j("tabindex","0"),y0(new G5),Z0(new R5)]),a([a5(a([j("part","combobox-trigger-label"),M9(s===""?"empty":"")]),a([a2(s)])),B5(a([j("part","combobox-trigger-icon")]))]))}function J6(){return l9(O1,e=>i1("key",$1,t=>t==="ArrowDown"?S(new K5(e)):t==="ArrowEnd"?S(new Q5(e)):t==="Enter"?S(new e7(e)):t==="Escape"?i1("target",y5("button",r2(),O1),n=>S(new t7(e,n))):t==="Home"?S(new n7(e)):t==="ArrowUp"?S(new r7(e)):t==="Tab"?S(new Y2):N1(new Y2,"")))}function X6(e){return l4(a([j("part","combobox-input")]),a([e4(a([])),u4(a([k2(a([["width","100%"],["border-bottom-left-radius","0px"],["border-bottom-right-radius","0px"]])),k3("off"),M0(t=>new Y5(t)),J("keydown",J6()),z7(e)]))]))}var f4="lustre-ui-combobox";function p4(e,t,n,r){let i=e.value===t,o=P(new F(e.value),n),s;i?s=Q0:s=c=>a5(c,a([]));let u=s,l=a(["combobox-option",o?"intent":"",r?"last":""]);return W3(a([j("part",We(l," ")),j("value",e.value),H0(new J5(e.value)),g0(new Et(e.value))]),a([u(a([k2(a([["height","1rem"],["width","1rem"]]))])),a5(a([y9("flex","1 1 0%")]),a([_1("slot",a([Z9("option-"+e.value)]),a([a2(e.label)]))]))]))}function _4(e,t,n){if(e instanceof b)return a([]);{let r=e.tail;if(r instanceof b){let i=e.head;return a([[i.value,p4(i,t,n,!0)]])}else{let i=e.head,o=r;return _([i.label,p4(i,t,n,!1)],_4(o,t,n))}}}function K6(e,t,n){return G0(a([]),_4(e.filtered,t,n))}function d4(e){return v9(a([Y1(a([y9("display","none"),J("slotchange",R6())]),a([])),F0(a([O0(new Ve),N0(),q0("var(--padding-y)"),D0(new Y2),I0(new X5),B0(e.expanded),J("click",G6(!e.expanded)),J("keydown",W6(!e.expanded))]),Y6(e.value,e.placeholder,e.options),D1(a([j("part","combobox-options")]),a([X6(e.query),K6(e.options,e.value,e.intent)])))]))}function m4(){let e=S5();if(e instanceof h){let t=d9(a4,C4,d4,a([m2(!1),j1("value",n=>new h(new Ae(n))),j1("placeholder",n=>new h(new Se(n))),j1("strategy",n=>n==="by-length"?new h(new b2(new P5)):n==="by-index"?new h(new b2(new w2)):new M(void 0))]));return i9(t,f4)}else if(e[0]instanceof X9){let n=d9(a4,C4,d4,a([m2(!1),j1("value",r=>new h(new Ae(r))),j1("placeholder",r=>new h(new Se(r))),j1("strategy",r=>r==="by-length"?new h(new b2(new P5)):r==="by-index"?new h(new b2(new w2)):new M(void 0))]));return i9(n,f4)}else return e}function Q6(e,t,n){let r="\x1B[90m",i="\x1B[39m",o=`${t}:${n}`,s=z9(e);if(globalThis.process?.stderr?.write){let u=`${r}${o}${i} +${s} +`;process.stderr.write(u)}else if(globalThis.Deno){let u=`${r}${o}${i} +${s} +`;globalThis.Deno.stderr.writeSync(new TextEncoder().encode(u))}else{let u=`${o} +${s}`;globalThis.console.log(u)}return e}function en(e){let t='"';for(let n=0;n"~"&&r<"\xA0"?t+="\\u{"+r.charCodeAt(0).toString(16).toUpperCase().padStart(4,"0")+"}":t+=r}return t+='"',t}function tn(e){let t="dict.from_list([",n=!0,r=[];return e.forEach((i,o)=>{r.push([o,i])}),r.sort(),r.forEach(([i,o])=>{n||(t=t+", "),t=t+"#("+z9(i)+", "+z9(o)+")",n=!1}),t+"])"}function nn(e){let t=globalThis.Object.keys(e).map(n=>{let r=z9(e[n]);return isNaN(parseInt(n))?`${n}: ${r}`:r}).join(", ");return t?`${e.constructor.name}(${t})`:e.constructor.name}function rn(e){let t=Object.getPrototypeOf(e)?.constructor?.name||"Object",n=[];for(let o of Object.keys(e))n.push(`${z9(o)}: ${z9(e[o])}`);let r=n.length?" "+n.join(", ")+" ":"";return`//js(${t==="Object"?"":t+" "}{${r}})`}function z9(e){let t=typeof e;if(e===!0)return"True";if(e===!1)return"False";if(e===null)return"//js(null)";if(e===void 0)return"Nil";if(t==="string")return en(e);if(t==="bigint"||t==="number")return e.toString();if(globalThis.Array.isArray(e))return`#(${e.map(z9).join(", ")})`;if(e instanceof L1)return`[${e.toArray().map(z9).join(", ")}]`;if(e instanceof t2)return`//utfcodepoint(${String.fromCodePoint(e.value)})`;if(e instanceof X1)return on(e);if(e instanceof p)return nn(e);if(sn(e))return tn(e);if(e instanceof Set)return`//js(Set(${[...e].map(z9).join(", ")}))`;if(e instanceof RegExp)return`//js(${e})`;if(e instanceof Date)return`//js(Date("${e.toISOString()}"))`;if(e instanceof Function){let n=[];for(let r of Array(e.length).keys())n.push(String.fromCharCode(r+97));return`//fn(${n.join(", ")}) { ... }`}return rn(e)}function on(e){let t=e.bitOffset+8*Math.trunc(e.bitSize/8),n=H2(e,e.bitOffset,t),r=e.bitSize%8;if(r>0){let i=ze(e,t,e.bitSize,!1,!1),o=Array.from(n.rawBuffer),s=`${i}:size(${r})`;return o.length===0?`<<${s}>>`:`<<${Array.from(n.rawBuffer).join(", ")}, ${s}>>`}else return`<<${Array.from(n.rawBuffer).join(", ")}>>`}function sn(e){try{return e instanceof z1}catch{return!1}}v5();S5();s4();m4(); diff --git a/priv/static/lustre_ui_components.mjs b/priv/static/lustre_ui_components.mjs new file mode 100644 index 0000000..9053a83 --- /dev/null +++ b/priv/static/lustre_ui_components.mjs @@ -0,0 +1,8768 @@ +// build/dev/javascript/prelude.mjs +var CustomType = class { + withFields(fields) { + let properties = Object.keys(this).map( + (label) => label in fields ? fields[label] : this[label] + ); + return new this.constructor(...properties); + } +}; +var List = class { + static fromArray(array3, tail) { + let t = tail || new Empty(); + for (let i = array3.length - 1; i >= 0; --i) { + t = new NonEmpty(array3[i], t); + } + return t; + } + [Symbol.iterator]() { + return new ListIterator(this); + } + toArray() { + return [...this]; + } + // @internal + atLeastLength(desired) { + let current = this; + while (desired-- > 0 && current) + current = current.tail; + return current !== void 0; + } + // @internal + hasLength(desired) { + let current = this; + while (desired-- > 0 && current) + current = current.tail; + return desired === -1 && current instanceof Empty; + } + // @internal + countLength() { + let current = this; + let length2 = 0; + while (current) { + current = current.tail; + length2++; + } + return length2 - 1; + } +}; +function prepend(element7, tail) { + return new NonEmpty(element7, tail); +} +function toList(elements, tail) { + return List.fromArray(elements, tail); +} +var ListIterator = class { + #current; + constructor(current) { + this.#current = current; + } + next() { + if (this.#current instanceof Empty) { + return { done: true }; + } else { + let { head, tail } = this.#current; + this.#current = tail; + return { value: head, done: false }; + } + } +}; +var Empty = class extends List { +}; +var NonEmpty = class extends List { + constructor(head, tail) { + super(); + this.head = head; + this.tail = tail; + } +}; +var BitArray = class { + /** + * The size in bits of this bit array's data. + * + * @type {number} + */ + bitSize; + /** + * The size in bytes of this bit array's data. If this bit array doesn't store + * a whole number of bytes then this value is rounded up. + * + * @type {number} + */ + byteSize; + /** + * The number of unused high bits in the first byte of this bit array's + * buffer prior to the start of its data. The value of any unused high bits is + * undefined. + * + * The bit offset will be in the range 0-7. + * + * @type {number} + */ + bitOffset; + /** + * The raw bytes that hold this bit array's data. + * + * If `bitOffset` is not zero then there are unused high bits in the first + * byte of this buffer. + * + * If `bitOffset + bitSize` is not a multiple of 8 then there are unused low + * bits in the last byte of this buffer. + * + * @type {Uint8Array} + */ + rawBuffer; + /** + * Constructs a new bit array from a `Uint8Array`, an optional size in + * bits, and an optional bit offset. + * + * If no bit size is specified it is taken as `buffer.length * 8`, i.e. all + * bytes in the buffer make up the new bit array's data. + * + * If no bit offset is specified it defaults to zero, i.e. there are no unused + * high bits in the first byte of the buffer. + * + * @param {Uint8Array} buffer + * @param {number} [bitSize] + * @param {number} [bitOffset] + */ + constructor(buffer, bitSize, bitOffset) { + if (!(buffer instanceof Uint8Array)) { + throw globalThis.Error( + "BitArray can only be constructed from a Uint8Array" + ); + } + this.bitSize = bitSize ?? buffer.length * 8; + this.byteSize = Math.trunc((this.bitSize + 7) / 8); + this.bitOffset = bitOffset ?? 0; + if (this.bitSize < 0) { + throw globalThis.Error(`BitArray bit size is invalid: ${this.bitSize}`); + } + if (this.bitOffset < 0 || this.bitOffset > 7) { + throw globalThis.Error( + `BitArray bit offset is invalid: ${this.bitOffset}` + ); + } + if (buffer.length !== Math.trunc((this.bitOffset + this.bitSize + 7) / 8)) { + throw globalThis.Error("BitArray buffer length is invalid"); + } + this.rawBuffer = buffer; + } + /** + * Returns a specific byte in this bit array. If the byte index is out of + * range then `undefined` is returned. + * + * When returning the final byte of a bit array with a bit size that's not a + * multiple of 8, the content of the unused low bits are undefined. + * + * @param {number} index + * @returns {number | undefined} + */ + byteAt(index4) { + if (index4 < 0 || index4 >= this.byteSize) { + return void 0; + } + return bitArrayByteAt(this.rawBuffer, this.bitOffset, index4); + } + /** @internal */ + equals(other) { + if (this.bitSize !== other.bitSize) { + return false; + } + const wholeByteCount = Math.trunc(this.bitSize / 8); + if (this.bitOffset === 0 && other.bitOffset === 0) { + for (let i = 0; i < wholeByteCount; i++) { + if (this.rawBuffer[i] !== other.rawBuffer[i]) { + return false; + } + } + const trailingBitsCount = this.bitSize % 8; + if (trailingBitsCount) { + const unusedLowBitCount = 8 - trailingBitsCount; + if (this.rawBuffer[wholeByteCount] >> unusedLowBitCount !== other.rawBuffer[wholeByteCount] >> unusedLowBitCount) { + return false; + } + } + } else { + for (let i = 0; i < wholeByteCount; i++) { + const a = bitArrayByteAt(this.rawBuffer, this.bitOffset, i); + const b = bitArrayByteAt(other.rawBuffer, other.bitOffset, i); + if (a !== b) { + return false; + } + } + const trailingBitsCount = this.bitSize % 8; + if (trailingBitsCount) { + const a = bitArrayByteAt( + this.rawBuffer, + this.bitOffset, + wholeByteCount + ); + const b = bitArrayByteAt( + other.rawBuffer, + other.bitOffset, + wholeByteCount + ); + const unusedLowBitCount = 8 - trailingBitsCount; + if (a >> unusedLowBitCount !== b >> unusedLowBitCount) { + return false; + } + } + } + return true; + } + /** + * Returns this bit array's internal buffer. + * + * @deprecated Use `BitArray.byteAt()` or `BitArray.rawBuffer` instead. + * + * @returns {Uint8Array} + */ + get buffer() { + bitArrayPrintDeprecationWarning( + "buffer", + "Use BitArray.byteAt() or BitArray.rawBuffer instead" + ); + if (this.bitOffset !== 0 || this.bitSize % 8 !== 0) { + throw new globalThis.Error( + "BitArray.buffer does not support unaligned bit arrays" + ); + } + return this.rawBuffer; + } + /** + * Returns the length in bytes of this bit array's internal buffer. + * + * @deprecated Use `BitArray.bitSize` or `BitArray.byteSize` instead. + * + * @returns {number} + */ + get length() { + bitArrayPrintDeprecationWarning( + "length", + "Use BitArray.bitSize or BitArray.byteSize instead" + ); + if (this.bitOffset !== 0 || this.bitSize % 8 !== 0) { + throw new globalThis.Error( + "BitArray.length does not support unaligned bit arrays" + ); + } + return this.rawBuffer.length; + } +}; +function bitArrayByteAt(buffer, bitOffset, index4) { + if (bitOffset === 0) { + return buffer[index4] ?? 0; + } else { + const a = buffer[index4] << bitOffset & 255; + const b = buffer[index4 + 1] >> 8 - bitOffset; + return a | b; + } +} +var UtfCodepoint = class { + constructor(value2) { + this.value = value2; + } +}; +var isBitArrayDeprecationMessagePrinted = {}; +function bitArrayPrintDeprecationWarning(name6, message) { + if (isBitArrayDeprecationMessagePrinted[name6]) { + return; + } + console.warn( + `Deprecated BitArray.${name6} property used in JavaScript FFI code. ${message}.` + ); + isBitArrayDeprecationMessagePrinted[name6] = true; +} +function bitArraySlice(bitArray, start3, end) { + end ??= bitArray.bitSize; + bitArrayValidateRange(bitArray, start3, end); + if (start3 === end) { + return new BitArray(new Uint8Array()); + } + if (start3 === 0 && end === bitArray.bitSize) { + return bitArray; + } + start3 += bitArray.bitOffset; + end += bitArray.bitOffset; + const startByteIndex = Math.trunc(start3 / 8); + const endByteIndex = Math.trunc((end + 7) / 8); + const byteLength = endByteIndex - startByteIndex; + let buffer; + if (startByteIndex === 0 && byteLength === bitArray.rawBuffer.byteLength) { + buffer = bitArray.rawBuffer; + } else { + buffer = new Uint8Array( + bitArray.rawBuffer.buffer, + bitArray.rawBuffer.byteOffset + startByteIndex, + byteLength + ); + } + return new BitArray(buffer, end - start3, start3 % 8); +} +function bitArraySliceToInt(bitArray, start3, end, isBigEndian, isSigned) { + bitArrayValidateRange(bitArray, start3, end); + if (start3 === end) { + return 0; + } + start3 += bitArray.bitOffset; + end += bitArray.bitOffset; + const isStartByteAligned = start3 % 8 === 0; + const isEndByteAligned = end % 8 === 0; + if (isStartByteAligned && isEndByteAligned) { + return intFromAlignedSlice( + bitArray, + start3 / 8, + end / 8, + isBigEndian, + isSigned + ); + } + const size3 = end - start3; + const startByteIndex = Math.trunc(start3 / 8); + const endByteIndex = Math.trunc((end - 1) / 8); + if (startByteIndex == endByteIndex) { + const mask2 = 255 >> start3 % 8; + const unusedLowBitCount = (8 - end % 8) % 8; + let value2 = (bitArray.rawBuffer[startByteIndex] & mask2) >> unusedLowBitCount; + if (isSigned) { + const highBit = 2 ** (size3 - 1); + if (value2 >= highBit) { + value2 -= highBit * 2; + } + } + return value2; + } + if (size3 <= 53) { + return intFromUnalignedSliceUsingNumber( + bitArray.rawBuffer, + start3, + end, + isBigEndian, + isSigned + ); + } else { + return intFromUnalignedSliceUsingBigInt( + bitArray.rawBuffer, + start3, + end, + isBigEndian, + isSigned + ); + } +} +function intFromAlignedSlice(bitArray, start3, end, isBigEndian, isSigned) { + const byteSize = end - start3; + if (byteSize <= 6) { + return intFromAlignedSliceUsingNumber( + bitArray.rawBuffer, + start3, + end, + isBigEndian, + isSigned + ); + } else { + return intFromAlignedSliceUsingBigInt( + bitArray.rawBuffer, + start3, + end, + isBigEndian, + isSigned + ); + } +} +function intFromAlignedSliceUsingNumber(buffer, start3, end, isBigEndian, isSigned) { + const byteSize = end - start3; + let value2 = 0; + if (isBigEndian) { + for (let i = start3; i < end; i++) { + value2 *= 256; + value2 += buffer[i]; + } + } else { + for (let i = end - 1; i >= start3; i--) { + value2 *= 256; + value2 += buffer[i]; + } + } + if (isSigned) { + const highBit = 2 ** (byteSize * 8 - 1); + if (value2 >= highBit) { + value2 -= highBit * 2; + } + } + return value2; +} +function intFromAlignedSliceUsingBigInt(buffer, start3, end, isBigEndian, isSigned) { + const byteSize = end - start3; + let value2 = 0n; + if (isBigEndian) { + for (let i = start3; i < end; i++) { + value2 *= 256n; + value2 += BigInt(buffer[i]); + } + } else { + for (let i = end - 1; i >= start3; i--) { + value2 *= 256n; + value2 += BigInt(buffer[i]); + } + } + if (isSigned) { + const highBit = 1n << BigInt(byteSize * 8 - 1); + if (value2 >= highBit) { + value2 -= highBit * 2n; + } + } + return Number(value2); +} +function intFromUnalignedSliceUsingNumber(buffer, start3, end, isBigEndian, isSigned) { + const isStartByteAligned = start3 % 8 === 0; + let size3 = end - start3; + let byteIndex = Math.trunc(start3 / 8); + let value2 = 0; + if (isBigEndian) { + if (!isStartByteAligned) { + const leadingBitsCount = 8 - start3 % 8; + value2 = buffer[byteIndex++] & (1 << leadingBitsCount) - 1; + size3 -= leadingBitsCount; + } + while (size3 >= 8) { + value2 *= 256; + value2 += buffer[byteIndex++]; + size3 -= 8; + } + if (size3 > 0) { + value2 *= 2 ** size3; + value2 += buffer[byteIndex] >> 8 - size3; + } + } else { + if (isStartByteAligned) { + let size4 = end - start3; + let scale = 1; + while (size4 >= 8) { + value2 += buffer[byteIndex++] * scale; + scale *= 256; + size4 -= 8; + } + value2 += (buffer[byteIndex] >> 8 - size4) * scale; + } else { + const highBitsCount = start3 % 8; + const lowBitsCount = 8 - highBitsCount; + let size4 = end - start3; + let scale = 1; + while (size4 >= 8) { + const byte = buffer[byteIndex] << highBitsCount | buffer[byteIndex + 1] >> lowBitsCount; + value2 += (byte & 255) * scale; + scale *= 256; + size4 -= 8; + byteIndex++; + } + if (size4 > 0) { + const lowBitsUsed = size4 - Math.max(0, size4 - lowBitsCount); + let trailingByte = (buffer[byteIndex] & (1 << lowBitsCount) - 1) >> lowBitsCount - lowBitsUsed; + size4 -= lowBitsUsed; + if (size4 > 0) { + trailingByte *= 2 ** size4; + trailingByte += buffer[byteIndex + 1] >> 8 - size4; + } + value2 += trailingByte * scale; + } + } + } + if (isSigned) { + const highBit = 2 ** (end - start3 - 1); + if (value2 >= highBit) { + value2 -= highBit * 2; + } + } + return value2; +} +function intFromUnalignedSliceUsingBigInt(buffer, start3, end, isBigEndian, isSigned) { + const isStartByteAligned = start3 % 8 === 0; + let size3 = end - start3; + let byteIndex = Math.trunc(start3 / 8); + let value2 = 0n; + if (isBigEndian) { + if (!isStartByteAligned) { + const leadingBitsCount = 8 - start3 % 8; + value2 = BigInt(buffer[byteIndex++] & (1 << leadingBitsCount) - 1); + size3 -= leadingBitsCount; + } + while (size3 >= 8) { + value2 *= 256n; + value2 += BigInt(buffer[byteIndex++]); + size3 -= 8; + } + if (size3 > 0) { + value2 <<= BigInt(size3); + value2 += BigInt(buffer[byteIndex] >> 8 - size3); + } + } else { + if (isStartByteAligned) { + let size4 = end - start3; + let shift = 0n; + while (size4 >= 8) { + value2 += BigInt(buffer[byteIndex++]) << shift; + shift += 8n; + size4 -= 8; + } + value2 += BigInt(buffer[byteIndex] >> 8 - size4) << shift; + } else { + const highBitsCount = start3 % 8; + const lowBitsCount = 8 - highBitsCount; + let size4 = end - start3; + let shift = 0n; + while (size4 >= 8) { + const byte = buffer[byteIndex] << highBitsCount | buffer[byteIndex + 1] >> lowBitsCount; + value2 += BigInt(byte & 255) << shift; + shift += 8n; + size4 -= 8; + byteIndex++; + } + if (size4 > 0) { + const lowBitsUsed = size4 - Math.max(0, size4 - lowBitsCount); + let trailingByte = (buffer[byteIndex] & (1 << lowBitsCount) - 1) >> lowBitsCount - lowBitsUsed; + size4 -= lowBitsUsed; + if (size4 > 0) { + trailingByte <<= size4; + trailingByte += buffer[byteIndex + 1] >> 8 - size4; + } + value2 += BigInt(trailingByte) << shift; + } + } + } + if (isSigned) { + const highBit = 2n ** BigInt(end - start3 - 1); + if (value2 >= highBit) { + value2 -= highBit * 2n; + } + } + return Number(value2); +} +function bitArrayValidateRange(bitArray, start3, end) { + if (start3 < 0 || start3 > bitArray.bitSize || end < start3 || end > bitArray.bitSize) { + const msg = `Invalid bit array slice: start = ${start3}, end = ${end}, bit size = ${bitArray.bitSize}`; + throw new globalThis.Error(msg); + } +} +var Result = class _Result extends CustomType { + // @internal + static isResult(data) { + return data instanceof _Result; + } +}; +var Ok = class extends Result { + constructor(value2) { + super(); + this[0] = value2; + } + // @internal + isOk() { + return true; + } +}; +var Error = class extends Result { + constructor(detail) { + super(); + this[0] = detail; + } + // @internal + isOk() { + return false; + } +}; +function isEqual(x, y) { + let values3 = [x, y]; + while (values3.length) { + let a = values3.pop(); + let b = values3.pop(); + if (a === b) + continue; + if (!isObject(a) || !isObject(b)) + return false; + let unequal = !structurallyCompatibleObjects(a, b) || unequalDates(a, b) || unequalBuffers(a, b) || unequalArrays(a, b) || unequalMaps(a, b) || unequalSets(a, b) || unequalRegExps(a, b); + if (unequal) + return false; + const proto = Object.getPrototypeOf(a); + if (proto !== null && typeof proto.equals === "function") { + try { + if (a.equals(b)) + continue; + else + return false; + } catch { + } + } + let [keys2, get3] = getters(a); + for (let k of keys2(a)) { + values3.push(get3(a, k), get3(b, k)); + } + } + return true; +} +function getters(object4) { + if (object4 instanceof Map) { + return [(x) => x.keys(), (x, y) => x.get(y)]; + } else { + let extra = object4 instanceof globalThis.Error ? ["message"] : []; + return [(x) => [...extra, ...Object.keys(x)], (x, y) => x[y]]; + } +} +function unequalDates(a, b) { + return a instanceof Date && (a > b || a < b); +} +function unequalBuffers(a, b) { + return !(a instanceof BitArray) && a.buffer instanceof ArrayBuffer && a.BYTES_PER_ELEMENT && !(a.byteLength === b.byteLength && a.every((n, i) => n === b[i])); +} +function unequalArrays(a, b) { + return Array.isArray(a) && a.length !== b.length; +} +function unequalMaps(a, b) { + return a instanceof Map && a.size !== b.size; +} +function unequalSets(a, b) { + return a instanceof Set && (a.size != b.size || [...a].some((e) => !b.has(e))); +} +function unequalRegExps(a, b) { + return a instanceof RegExp && (a.source !== b.source || a.flags !== b.flags); +} +function isObject(a) { + return typeof a === "object" && a !== null; +} +function structurallyCompatibleObjects(a, b) { + if (typeof a !== "object" && typeof b !== "object" && (!a || !b)) + return false; + let nonstructural = [Promise, WeakSet, WeakMap, Function]; + if (nonstructural.some((c) => a instanceof c)) + return false; + return a.constructor === b.constructor; +} +function makeError(variant, file, module, line, fn, message, extra) { + let error = new globalThis.Error(message); + error.gleam_error = variant; + error.file = file; + error.module = module; + error.line = line; + error.function = fn; + error.fn = fn; + for (let k in extra) + error[k] = extra[k]; + return error; +} + +// build/dev/javascript/gleam_stdlib/gleam/order.mjs +var Lt = class extends CustomType { +}; +var Eq = class extends CustomType { +}; +var Gt = class extends CustomType { +}; + +// build/dev/javascript/gleam_stdlib/gleam/option.mjs +var Some = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var None = class extends CustomType { +}; +function from_result(result) { + if (result instanceof Ok) { + let a = result[0]; + return new Some(a); + } else { + return new None(); + } +} + +// build/dev/javascript/gleam_stdlib/dict.mjs +var referenceMap = /* @__PURE__ */ new WeakMap(); +var tempDataView = /* @__PURE__ */ new DataView( + /* @__PURE__ */ new ArrayBuffer(8) +); +var referenceUID = 0; +function hashByReference(o) { + const known = referenceMap.get(o); + if (known !== void 0) { + return known; + } + const hash = referenceUID++; + if (referenceUID === 2147483647) { + referenceUID = 0; + } + referenceMap.set(o, hash); + return hash; +} +function hashMerge(a, b) { + return a ^ b + 2654435769 + (a << 6) + (a >> 2) | 0; +} +function hashString(s) { + let hash = 0; + const len = s.length; + for (let i = 0; i < len; i++) { + hash = Math.imul(31, hash) + s.charCodeAt(i) | 0; + } + return hash; +} +function hashNumber(n) { + tempDataView.setFloat64(0, n); + const i = tempDataView.getInt32(0); + const j = tempDataView.getInt32(4); + return Math.imul(73244475, i >> 16 ^ i) ^ j; +} +function hashBigInt(n) { + return hashString(n.toString()); +} +function hashObject(o) { + const proto = Object.getPrototypeOf(o); + if (proto !== null && typeof proto.hashCode === "function") { + try { + const code = o.hashCode(o); + if (typeof code === "number") { + return code; + } + } catch { + } + } + if (o instanceof Promise || o instanceof WeakSet || o instanceof WeakMap) { + return hashByReference(o); + } + if (o instanceof Date) { + return hashNumber(o.getTime()); + } + let h = 0; + if (o instanceof ArrayBuffer) { + o = new Uint8Array(o); + } + if (Array.isArray(o) || o instanceof Uint8Array) { + for (let i = 0; i < o.length; i++) { + h = Math.imul(31, h) + getHash(o[i]) | 0; + } + } else if (o instanceof Set) { + o.forEach((v) => { + h = h + getHash(v) | 0; + }); + } else if (o instanceof Map) { + o.forEach((v, k) => { + h = h + hashMerge(getHash(v), getHash(k)) | 0; + }); + } else { + const keys2 = Object.keys(o); + for (let i = 0; i < keys2.length; i++) { + const k = keys2[i]; + const v = o[k]; + h = h + hashMerge(getHash(v), hashString(k)) | 0; + } + } + return h; +} +function getHash(u) { + if (u === null) + return 1108378658; + if (u === void 0) + return 1108378659; + if (u === true) + return 1108378657; + if (u === false) + return 1108378656; + switch (typeof u) { + case "number": + return hashNumber(u); + case "string": + return hashString(u); + case "bigint": + return hashBigInt(u); + case "object": + return hashObject(u); + case "symbol": + return hashByReference(u); + case "function": + return hashByReference(u); + default: + return 0; + } +} +var SHIFT = 5; +var BUCKET_SIZE = Math.pow(2, SHIFT); +var MASK = BUCKET_SIZE - 1; +var MAX_INDEX_NODE = BUCKET_SIZE / 2; +var MIN_ARRAY_NODE = BUCKET_SIZE / 4; +var ENTRY = 0; +var ARRAY_NODE = 1; +var INDEX_NODE = 2; +var COLLISION_NODE = 3; +var EMPTY = { + type: INDEX_NODE, + bitmap: 0, + array: [] +}; +function mask(hash, shift) { + return hash >>> shift & MASK; +} +function bitpos(hash, shift) { + return 1 << mask(hash, shift); +} +function bitcount(x) { + x -= x >> 1 & 1431655765; + x = (x & 858993459) + (x >> 2 & 858993459); + x = x + (x >> 4) & 252645135; + x += x >> 8; + x += x >> 16; + return x & 127; +} +function index(bitmap, bit) { + return bitcount(bitmap & bit - 1); +} +function cloneAndSet(arr, at, val) { + const len = arr.length; + const out = new Array(len); + for (let i = 0; i < len; ++i) { + out[i] = arr[i]; + } + out[at] = val; + return out; +} +function spliceIn(arr, at, val) { + const len = arr.length; + const out = new Array(len + 1); + let i = 0; + let g = 0; + while (i < at) { + out[g++] = arr[i++]; + } + out[g++] = val; + while (i < len) { + out[g++] = arr[i++]; + } + return out; +} +function spliceOut(arr, at) { + const len = arr.length; + const out = new Array(len - 1); + let i = 0; + let g = 0; + while (i < at) { + out[g++] = arr[i++]; + } + ++i; + while (i < len) { + out[g++] = arr[i++]; + } + return out; +} +function createNode(shift, key1, val1, key2hash, key2, val2) { + const key1hash = getHash(key1); + if (key1hash === key2hash) { + return { + type: COLLISION_NODE, + hash: key1hash, + array: [ + { type: ENTRY, k: key1, v: val1 }, + { type: ENTRY, k: key2, v: val2 } + ] + }; + } + const addedLeaf = { val: false }; + return assoc( + assocIndex(EMPTY, shift, key1hash, key1, val1, addedLeaf), + shift, + key2hash, + key2, + val2, + addedLeaf + ); +} +function assoc(root3, shift, hash, key, val, addedLeaf) { + switch (root3.type) { + case ARRAY_NODE: + return assocArray(root3, shift, hash, key, val, addedLeaf); + case INDEX_NODE: + return assocIndex(root3, shift, hash, key, val, addedLeaf); + case COLLISION_NODE: + return assocCollision(root3, shift, hash, key, val, addedLeaf); + } +} +function assocArray(root3, shift, hash, key, val, addedLeaf) { + const idx = mask(hash, shift); + const node = root3.array[idx]; + if (node === void 0) { + addedLeaf.val = true; + return { + type: ARRAY_NODE, + size: root3.size + 1, + array: cloneAndSet(root3.array, idx, { type: ENTRY, k: key, v: val }) + }; + } + if (node.type === ENTRY) { + if (isEqual(key, node.k)) { + if (val === node.v) { + return root3; + } + return { + type: ARRAY_NODE, + size: root3.size, + array: cloneAndSet(root3.array, idx, { + type: ENTRY, + k: key, + v: val + }) + }; + } + addedLeaf.val = true; + return { + type: ARRAY_NODE, + size: root3.size, + array: cloneAndSet( + root3.array, + idx, + createNode(shift + SHIFT, node.k, node.v, hash, key, val) + ) + }; + } + const n = assoc(node, shift + SHIFT, hash, key, val, addedLeaf); + if (n === node) { + return root3; + } + return { + type: ARRAY_NODE, + size: root3.size, + array: cloneAndSet(root3.array, idx, n) + }; +} +function assocIndex(root3, shift, hash, key, val, addedLeaf) { + const bit = bitpos(hash, shift); + const idx = index(root3.bitmap, bit); + if ((root3.bitmap & bit) !== 0) { + const node = root3.array[idx]; + if (node.type !== ENTRY) { + const n = assoc(node, shift + SHIFT, hash, key, val, addedLeaf); + if (n === node) { + return root3; + } + return { + type: INDEX_NODE, + bitmap: root3.bitmap, + array: cloneAndSet(root3.array, idx, n) + }; + } + const nodeKey = node.k; + if (isEqual(key, nodeKey)) { + if (val === node.v) { + return root3; + } + return { + type: INDEX_NODE, + bitmap: root3.bitmap, + array: cloneAndSet(root3.array, idx, { + type: ENTRY, + k: key, + v: val + }) + }; + } + addedLeaf.val = true; + return { + type: INDEX_NODE, + bitmap: root3.bitmap, + array: cloneAndSet( + root3.array, + idx, + createNode(shift + SHIFT, nodeKey, node.v, hash, key, val) + ) + }; + } else { + const n = root3.array.length; + if (n >= MAX_INDEX_NODE) { + const nodes = new Array(32); + const jdx = mask(hash, shift); + nodes[jdx] = assocIndex(EMPTY, shift + SHIFT, hash, key, val, addedLeaf); + let j = 0; + let bitmap = root3.bitmap; + for (let i = 0; i < 32; i++) { + if ((bitmap & 1) !== 0) { + const node = root3.array[j++]; + nodes[i] = node; + } + bitmap = bitmap >>> 1; + } + return { + type: ARRAY_NODE, + size: n + 1, + array: nodes + }; + } else { + const newArray = spliceIn(root3.array, idx, { + type: ENTRY, + k: key, + v: val + }); + addedLeaf.val = true; + return { + type: INDEX_NODE, + bitmap: root3.bitmap | bit, + array: newArray + }; + } + } +} +function assocCollision(root3, shift, hash, key, val, addedLeaf) { + if (hash === root3.hash) { + const idx = collisionIndexOf(root3, key); + if (idx !== -1) { + const entry = root3.array[idx]; + if (entry.v === val) { + return root3; + } + return { + type: COLLISION_NODE, + hash, + array: cloneAndSet(root3.array, idx, { type: ENTRY, k: key, v: val }) + }; + } + const size3 = root3.array.length; + addedLeaf.val = true; + return { + type: COLLISION_NODE, + hash, + array: cloneAndSet(root3.array, size3, { type: ENTRY, k: key, v: val }) + }; + } + return assoc( + { + type: INDEX_NODE, + bitmap: bitpos(root3.hash, shift), + array: [root3] + }, + shift, + hash, + key, + val, + addedLeaf + ); +} +function collisionIndexOf(root3, key) { + const size3 = root3.array.length; + for (let i = 0; i < size3; i++) { + if (isEqual(key, root3.array[i].k)) { + return i; + } + } + return -1; +} +function find(root3, shift, hash, key) { + switch (root3.type) { + case ARRAY_NODE: + return findArray(root3, shift, hash, key); + case INDEX_NODE: + return findIndex(root3, shift, hash, key); + case COLLISION_NODE: + return findCollision(root3, key); + } +} +function findArray(root3, shift, hash, key) { + const idx = mask(hash, shift); + const node = root3.array[idx]; + if (node === void 0) { + return void 0; + } + if (node.type !== ENTRY) { + return find(node, shift + SHIFT, hash, key); + } + if (isEqual(key, node.k)) { + return node; + } + return void 0; +} +function findIndex(root3, shift, hash, key) { + const bit = bitpos(hash, shift); + if ((root3.bitmap & bit) === 0) { + return void 0; + } + const idx = index(root3.bitmap, bit); + const node = root3.array[idx]; + if (node.type !== ENTRY) { + return find(node, shift + SHIFT, hash, key); + } + if (isEqual(key, node.k)) { + return node; + } + return void 0; +} +function findCollision(root3, key) { + const idx = collisionIndexOf(root3, key); + if (idx < 0) { + return void 0; + } + return root3.array[idx]; +} +function without(root3, shift, hash, key) { + switch (root3.type) { + case ARRAY_NODE: + return withoutArray(root3, shift, hash, key); + case INDEX_NODE: + return withoutIndex(root3, shift, hash, key); + case COLLISION_NODE: + return withoutCollision(root3, key); + } +} +function withoutArray(root3, shift, hash, key) { + const idx = mask(hash, shift); + const node = root3.array[idx]; + if (node === void 0) { + return root3; + } + let n = void 0; + if (node.type === ENTRY) { + if (!isEqual(node.k, key)) { + return root3; + } + } else { + n = without(node, shift + SHIFT, hash, key); + if (n === node) { + return root3; + } + } + if (n === void 0) { + if (root3.size <= MIN_ARRAY_NODE) { + const arr = root3.array; + const out = new Array(root3.size - 1); + let i = 0; + let j = 0; + let bitmap = 0; + while (i < idx) { + const nv = arr[i]; + if (nv !== void 0) { + out[j] = nv; + bitmap |= 1 << i; + ++j; + } + ++i; + } + ++i; + while (i < arr.length) { + const nv = arr[i]; + if (nv !== void 0) { + out[j] = nv; + bitmap |= 1 << i; + ++j; + } + ++i; + } + return { + type: INDEX_NODE, + bitmap, + array: out + }; + } + return { + type: ARRAY_NODE, + size: root3.size - 1, + array: cloneAndSet(root3.array, idx, n) + }; + } + return { + type: ARRAY_NODE, + size: root3.size, + array: cloneAndSet(root3.array, idx, n) + }; +} +function withoutIndex(root3, shift, hash, key) { + const bit = bitpos(hash, shift); + if ((root3.bitmap & bit) === 0) { + return root3; + } + const idx = index(root3.bitmap, bit); + const node = root3.array[idx]; + if (node.type !== ENTRY) { + const n = without(node, shift + SHIFT, hash, key); + if (n === node) { + return root3; + } + if (n !== void 0) { + return { + type: INDEX_NODE, + bitmap: root3.bitmap, + array: cloneAndSet(root3.array, idx, n) + }; + } + if (root3.bitmap === bit) { + return void 0; + } + return { + type: INDEX_NODE, + bitmap: root3.bitmap ^ bit, + array: spliceOut(root3.array, idx) + }; + } + if (isEqual(key, node.k)) { + if (root3.bitmap === bit) { + return void 0; + } + return { + type: INDEX_NODE, + bitmap: root3.bitmap ^ bit, + array: spliceOut(root3.array, idx) + }; + } + return root3; +} +function withoutCollision(root3, key) { + const idx = collisionIndexOf(root3, key); + if (idx < 0) { + return root3; + } + if (root3.array.length === 1) { + return void 0; + } + return { + type: COLLISION_NODE, + hash: root3.hash, + array: spliceOut(root3.array, idx) + }; +} +function forEach(root3, fn) { + if (root3 === void 0) { + return; + } + const items = root3.array; + const size3 = items.length; + for (let i = 0; i < size3; i++) { + const item = items[i]; + if (item === void 0) { + continue; + } + if (item.type === ENTRY) { + fn(item.v, item.k); + continue; + } + forEach(item, fn); + } +} +var Dict = class _Dict { + /** + * @template V + * @param {Record} o + * @returns {Dict} + */ + static fromObject(o) { + const keys2 = Object.keys(o); + let m = _Dict.new(); + for (let i = 0; i < keys2.length; i++) { + const k = keys2[i]; + m = m.set(k, o[k]); + } + return m; + } + /** + * @template K,V + * @param {Map} o + * @returns {Dict} + */ + static fromMap(o) { + let m = _Dict.new(); + o.forEach((v, k) => { + m = m.set(k, v); + }); + return m; + } + static new() { + return new _Dict(void 0, 0); + } + /** + * @param {undefined | Node} root + * @param {number} size + */ + constructor(root3, size3) { + this.root = root3; + this.size = size3; + } + /** + * @template NotFound + * @param {K} key + * @param {NotFound} notFound + * @returns {NotFound | V} + */ + get(key, notFound) { + if (this.root === void 0) { + return notFound; + } + const found = find(this.root, 0, getHash(key), key); + if (found === void 0) { + return notFound; + } + return found.v; + } + /** + * @param {K} key + * @param {V} val + * @returns {Dict} + */ + set(key, val) { + const addedLeaf = { val: false }; + const root3 = this.root === void 0 ? EMPTY : this.root; + const newRoot = assoc(root3, 0, getHash(key), key, val, addedLeaf); + if (newRoot === this.root) { + return this; + } + return new _Dict(newRoot, addedLeaf.val ? this.size + 1 : this.size); + } + /** + * @param {K} key + * @returns {Dict} + */ + delete(key) { + if (this.root === void 0) { + return this; + } + const newRoot = without(this.root, 0, getHash(key), key); + if (newRoot === this.root) { + return this; + } + if (newRoot === void 0) { + return _Dict.new(); + } + return new _Dict(newRoot, this.size - 1); + } + /** + * @param {K} key + * @returns {boolean} + */ + has(key) { + if (this.root === void 0) { + return false; + } + return find(this.root, 0, getHash(key), key) !== void 0; + } + /** + * @returns {[K,V][]} + */ + entries() { + if (this.root === void 0) { + return []; + } + const result = []; + this.forEach((v, k) => result.push([k, v])); + return result; + } + /** + * + * @param {(val:V,key:K)=>void} fn + */ + forEach(fn) { + forEach(this.root, fn); + } + hashCode() { + let h = 0; + this.forEach((v, k) => { + h = h + hashMerge(getHash(v), getHash(k)) | 0; + }); + return h; + } + /** + * @param {unknown} o + * @returns {boolean} + */ + equals(o) { + if (!(o instanceof _Dict) || this.size !== o.size) { + return false; + } + try { + this.forEach((v, k) => { + if (!isEqual(o.get(k, !v), v)) { + throw unequalDictSymbol; + } + }); + return true; + } catch (e) { + if (e === unequalDictSymbol) { + return false; + } + throw e; + } + } +}; +var unequalDictSymbol = /* @__PURE__ */ Symbol(); + +// build/dev/javascript/gleam_stdlib/gleam/dict.mjs +function do_has_key(key, dict2) { + return !isEqual(map_get(dict2, key), new Error(void 0)); +} +function has_key(dict2, key) { + return do_has_key(key, dict2); +} +function insert(dict2, key, value2) { + return map_insert(key, value2, dict2); +} +function delete$(dict2, key) { + return map_remove(key, dict2); +} +function fold_loop(loop$list, loop$initial, loop$fun) { + while (true) { + let list4 = loop$list; + let initial = loop$initial; + let fun = loop$fun; + if (list4 instanceof Empty) { + return initial; + } else { + let rest = list4.tail; + let k = list4.head[0]; + let v = list4.head[1]; + loop$list = rest; + loop$initial = fun(initial, k, v); + loop$fun = fun; + } + } +} +function fold(dict2, initial, fun) { + return fold_loop(map_to_list(dict2), initial, fun); +} +function do_filter(f, dict2) { + let insert$1 = (dict3, k, v) => { + let $ = f(k, v); + if ($) { + return insert(dict3, k, v); + } else { + return dict3; + } + }; + return fold(dict2, new_map(), insert$1); +} +function filter(dict2, predicate) { + return do_filter(predicate, dict2); +} + +// build/dev/javascript/gleam_stdlib/gleam/dynamic.mjs +function nil() { + return identity(void 0); +} + +// build/dev/javascript/gleam_stdlib/gleam/list.mjs +var Ascending = class extends CustomType { +}; +var Descending = class extends CustomType { +}; +function reverse_and_prepend(loop$prefix, loop$suffix) { + while (true) { + let prefix = loop$prefix; + let suffix = loop$suffix; + if (prefix instanceof Empty) { + return suffix; + } else { + let first$1 = prefix.head; + let rest$1 = prefix.tail; + loop$prefix = rest$1; + loop$suffix = prepend(first$1, suffix); + } + } +} +function reverse(list4) { + return reverse_and_prepend(list4, toList([])); +} +function first(list4) { + if (list4 instanceof Empty) { + return new Error(void 0); + } else { + let first$1 = list4.head; + return new Ok(first$1); + } +} +function filter_loop(loop$list, loop$fun, loop$acc) { + while (true) { + let list4 = loop$list; + let fun = loop$fun; + let acc = loop$acc; + if (list4 instanceof Empty) { + return reverse(acc); + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + let _block; + let $ = fun(first$1); + if ($) { + _block = prepend(first$1, acc); + } else { + _block = acc; + } + let new_acc = _block; + loop$list = rest$1; + loop$fun = fun; + loop$acc = new_acc; + } + } +} +function filter2(list4, predicate) { + return filter_loop(list4, predicate, toList([])); +} +function filter_map_loop(loop$list, loop$fun, loop$acc) { + while (true) { + let list4 = loop$list; + let fun = loop$fun; + let acc = loop$acc; + if (list4 instanceof Empty) { + return reverse(acc); + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + let _block; + let $ = fun(first$1); + if ($ instanceof Ok) { + let first$2 = $[0]; + _block = prepend(first$2, acc); + } else { + _block = acc; + } + let new_acc = _block; + loop$list = rest$1; + loop$fun = fun; + loop$acc = new_acc; + } + } +} +function filter_map(list4, fun) { + return filter_map_loop(list4, fun, toList([])); +} +function map_loop(loop$list, loop$fun, loop$acc) { + while (true) { + let list4 = loop$list; + let fun = loop$fun; + let acc = loop$acc; + if (list4 instanceof Empty) { + return reverse(acc); + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + loop$list = rest$1; + loop$fun = fun; + loop$acc = prepend(fun(first$1), acc); + } + } +} +function map(list4, fun) { + return map_loop(list4, fun, toList([])); +} +function try_map_loop(loop$list, loop$fun, loop$acc) { + while (true) { + let list4 = loop$list; + let fun = loop$fun; + let acc = loop$acc; + if (list4 instanceof Empty) { + return new Ok(reverse(acc)); + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + let $ = fun(first$1); + if ($ instanceof Ok) { + let first$2 = $[0]; + loop$list = rest$1; + loop$fun = fun; + loop$acc = prepend(first$2, acc); + } else { + let error = $[0]; + return new Error(error); + } + } + } +} +function try_map(list4, fun) { + return try_map_loop(list4, fun, toList([])); +} +function append_loop(loop$first, loop$second) { + while (true) { + let first3 = loop$first; + let second2 = loop$second; + if (first3 instanceof Empty) { + return second2; + } else { + let first$1 = first3.head; + let rest$1 = first3.tail; + loop$first = rest$1; + loop$second = prepend(first$1, second2); + } + } +} +function append(first3, second2) { + return append_loop(reverse(first3), second2); +} +function prepend2(list4, item) { + return prepend(item, list4); +} +function fold2(loop$list, loop$initial, loop$fun) { + while (true) { + let list4 = loop$list; + let initial = loop$initial; + let fun = loop$fun; + if (list4 instanceof Empty) { + return initial; + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + loop$list = rest$1; + loop$initial = fun(initial, first$1); + loop$fun = fun; + } + } +} +function fold_right(list4, initial, fun) { + if (list4 instanceof Empty) { + return initial; + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + return fun(fold_right(rest$1, initial, fun), first$1); + } +} +function index_fold_loop(loop$over, loop$acc, loop$with, loop$index) { + while (true) { + let over = loop$over; + let acc = loop$acc; + let with$ = loop$with; + let index4 = loop$index; + if (over instanceof Empty) { + return acc; + } else { + let first$1 = over.head; + let rest$1 = over.tail; + loop$over = rest$1; + loop$acc = with$(acc, first$1, index4); + loop$with = with$; + loop$index = index4 + 1; + } + } +} +function index_fold(list4, initial, fun) { + return index_fold_loop(list4, initial, fun, 0); +} +function find2(loop$list, loop$is_desired) { + while (true) { + let list4 = loop$list; + let is_desired = loop$is_desired; + if (list4 instanceof Empty) { + return new Error(void 0); + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + let $ = is_desired(first$1); + if ($) { + return new Ok(first$1); + } else { + loop$list = rest$1; + loop$is_desired = is_desired; + } + } + } +} +function sequences(loop$list, loop$compare, loop$growing, loop$direction, loop$prev, loop$acc) { + while (true) { + let list4 = loop$list; + let compare4 = loop$compare; + let growing = loop$growing; + let direction = loop$direction; + let prev = loop$prev; + let acc = loop$acc; + let growing$1 = prepend(prev, growing); + if (list4 instanceof Empty) { + if (direction instanceof Ascending) { + return prepend(reverse(growing$1), acc); + } else { + return prepend(growing$1, acc); + } + } else { + let new$1 = list4.head; + let rest$1 = list4.tail; + let $ = compare4(prev, new$1); + if (direction instanceof Ascending) { + if ($ instanceof Lt) { + loop$list = rest$1; + loop$compare = compare4; + loop$growing = growing$1; + loop$direction = direction; + loop$prev = new$1; + loop$acc = acc; + } else if ($ instanceof Eq) { + loop$list = rest$1; + loop$compare = compare4; + loop$growing = growing$1; + loop$direction = direction; + loop$prev = new$1; + loop$acc = acc; + } else { + let _block; + if (direction instanceof Ascending) { + _block = prepend(reverse(growing$1), acc); + } else { + _block = prepend(growing$1, acc); + } + let acc$1 = _block; + if (rest$1 instanceof Empty) { + return prepend(toList([new$1]), acc$1); + } else { + let next2 = rest$1.head; + let rest$2 = rest$1.tail; + let _block$1; + let $1 = compare4(new$1, next2); + if ($1 instanceof Lt) { + _block$1 = new Ascending(); + } else if ($1 instanceof Eq) { + _block$1 = new Ascending(); + } else { + _block$1 = new Descending(); + } + let direction$1 = _block$1; + loop$list = rest$2; + loop$compare = compare4; + loop$growing = toList([new$1]); + loop$direction = direction$1; + loop$prev = next2; + loop$acc = acc$1; + } + } + } else if ($ instanceof Lt) { + let _block; + if (direction instanceof Ascending) { + _block = prepend(reverse(growing$1), acc); + } else { + _block = prepend(growing$1, acc); + } + let acc$1 = _block; + if (rest$1 instanceof Empty) { + return prepend(toList([new$1]), acc$1); + } else { + let next2 = rest$1.head; + let rest$2 = rest$1.tail; + let _block$1; + let $1 = compare4(new$1, next2); + if ($1 instanceof Lt) { + _block$1 = new Ascending(); + } else if ($1 instanceof Eq) { + _block$1 = new Ascending(); + } else { + _block$1 = new Descending(); + } + let direction$1 = _block$1; + loop$list = rest$2; + loop$compare = compare4; + loop$growing = toList([new$1]); + loop$direction = direction$1; + loop$prev = next2; + loop$acc = acc$1; + } + } else if ($ instanceof Eq) { + let _block; + if (direction instanceof Ascending) { + _block = prepend(reverse(growing$1), acc); + } else { + _block = prepend(growing$1, acc); + } + let acc$1 = _block; + if (rest$1 instanceof Empty) { + return prepend(toList([new$1]), acc$1); + } else { + let next2 = rest$1.head; + let rest$2 = rest$1.tail; + let _block$1; + let $1 = compare4(new$1, next2); + if ($1 instanceof Lt) { + _block$1 = new Ascending(); + } else if ($1 instanceof Eq) { + _block$1 = new Ascending(); + } else { + _block$1 = new Descending(); + } + let direction$1 = _block$1; + loop$list = rest$2; + loop$compare = compare4; + loop$growing = toList([new$1]); + loop$direction = direction$1; + loop$prev = next2; + loop$acc = acc$1; + } + } else { + loop$list = rest$1; + loop$compare = compare4; + loop$growing = growing$1; + loop$direction = direction; + loop$prev = new$1; + loop$acc = acc; + } + } + } +} +function merge_ascendings(loop$list1, loop$list2, loop$compare, loop$acc) { + while (true) { + let list1 = loop$list1; + let list22 = loop$list2; + let compare4 = loop$compare; + let acc = loop$acc; + if (list1 instanceof Empty) { + let list4 = list22; + return reverse_and_prepend(list4, acc); + } else if (list22 instanceof Empty) { + let list4 = list1; + return reverse_and_prepend(list4, acc); + } else { + let first1 = list1.head; + let rest1 = list1.tail; + let first22 = list22.head; + let rest2 = list22.tail; + let $ = compare4(first1, first22); + if ($ instanceof Lt) { + loop$list1 = rest1; + loop$list2 = list22; + loop$compare = compare4; + loop$acc = prepend(first1, acc); + } else if ($ instanceof Eq) { + loop$list1 = list1; + loop$list2 = rest2; + loop$compare = compare4; + loop$acc = prepend(first22, acc); + } else { + loop$list1 = list1; + loop$list2 = rest2; + loop$compare = compare4; + loop$acc = prepend(first22, acc); + } + } + } +} +function merge_ascending_pairs(loop$sequences, loop$compare, loop$acc) { + while (true) { + let sequences2 = loop$sequences; + let compare4 = loop$compare; + let acc = loop$acc; + if (sequences2 instanceof Empty) { + return reverse(acc); + } else { + let $ = sequences2.tail; + if ($ instanceof Empty) { + let sequence = sequences2.head; + return reverse(prepend(reverse(sequence), acc)); + } else { + let ascending1 = sequences2.head; + let ascending2 = $.head; + let rest$1 = $.tail; + let descending = merge_ascendings( + ascending1, + ascending2, + compare4, + toList([]) + ); + loop$sequences = rest$1; + loop$compare = compare4; + loop$acc = prepend(descending, acc); + } + } + } +} +function merge_descendings(loop$list1, loop$list2, loop$compare, loop$acc) { + while (true) { + let list1 = loop$list1; + let list22 = loop$list2; + let compare4 = loop$compare; + let acc = loop$acc; + if (list1 instanceof Empty) { + let list4 = list22; + return reverse_and_prepend(list4, acc); + } else if (list22 instanceof Empty) { + let list4 = list1; + return reverse_and_prepend(list4, acc); + } else { + let first1 = list1.head; + let rest1 = list1.tail; + let first22 = list22.head; + let rest2 = list22.tail; + let $ = compare4(first1, first22); + if ($ instanceof Lt) { + loop$list1 = list1; + loop$list2 = rest2; + loop$compare = compare4; + loop$acc = prepend(first22, acc); + } else if ($ instanceof Eq) { + loop$list1 = rest1; + loop$list2 = list22; + loop$compare = compare4; + loop$acc = prepend(first1, acc); + } else { + loop$list1 = rest1; + loop$list2 = list22; + loop$compare = compare4; + loop$acc = prepend(first1, acc); + } + } + } +} +function merge_descending_pairs(loop$sequences, loop$compare, loop$acc) { + while (true) { + let sequences2 = loop$sequences; + let compare4 = loop$compare; + let acc = loop$acc; + if (sequences2 instanceof Empty) { + return reverse(acc); + } else { + let $ = sequences2.tail; + if ($ instanceof Empty) { + let sequence = sequences2.head; + return reverse(prepend(reverse(sequence), acc)); + } else { + let descending1 = sequences2.head; + let descending2 = $.head; + let rest$1 = $.tail; + let ascending = merge_descendings( + descending1, + descending2, + compare4, + toList([]) + ); + loop$sequences = rest$1; + loop$compare = compare4; + loop$acc = prepend(ascending, acc); + } + } + } +} +function merge_all(loop$sequences, loop$direction, loop$compare) { + while (true) { + let sequences2 = loop$sequences; + let direction = loop$direction; + let compare4 = loop$compare; + if (sequences2 instanceof Empty) { + return toList([]); + } else if (direction instanceof Ascending) { + let $ = sequences2.tail; + if ($ instanceof Empty) { + let sequence = sequences2.head; + return sequence; + } else { + let sequences$1 = merge_ascending_pairs(sequences2, compare4, toList([])); + loop$sequences = sequences$1; + loop$direction = new Descending(); + loop$compare = compare4; + } + } else { + let $ = sequences2.tail; + if ($ instanceof Empty) { + let sequence = sequences2.head; + return reverse(sequence); + } else { + let sequences$1 = merge_descending_pairs(sequences2, compare4, toList([])); + loop$sequences = sequences$1; + loop$direction = new Ascending(); + loop$compare = compare4; + } + } + } +} +function sort(list4, compare4) { + if (list4 instanceof Empty) { + return toList([]); + } else { + let $ = list4.tail; + if ($ instanceof Empty) { + let x = list4.head; + return toList([x]); + } else { + let x = list4.head; + let y = $.head; + let rest$1 = $.tail; + let _block; + let $1 = compare4(x, y); + if ($1 instanceof Lt) { + _block = new Ascending(); + } else if ($1 instanceof Eq) { + _block = new Ascending(); + } else { + _block = new Descending(); + } + let direction = _block; + let sequences$1 = sequences( + rest$1, + compare4, + toList([x]), + direction, + y, + toList([]) + ); + return merge_all(sequences$1, new Ascending(), compare4); + } + } +} + +// build/dev/javascript/gleam_stdlib/gleam/dynamic/decode.mjs +var DecodeError = class extends CustomType { + constructor(expected, found, path2) { + super(); + this.expected = expected; + this.found = found; + this.path = path2; + } +}; +var Decoder = class extends CustomType { + constructor(function$) { + super(); + this.function = function$; + } +}; +function run(data, decoder) { + let $ = decoder.function(data); + let maybe_invalid_data = $[0]; + let errors = $[1]; + if (errors instanceof Empty) { + return new Ok(maybe_invalid_data); + } else { + return new Error(errors); + } +} +function success(data) { + return new Decoder((_) => { + return [data, toList([])]; + }); +} +function decode_dynamic(data) { + return [data, toList([])]; +} +function map2(decoder, transformer) { + return new Decoder( + (d) => { + let $ = decoder.function(d); + let data = $[0]; + let errors = $[1]; + return [transformer(data), errors]; + } + ); +} +function then$(decoder, next2) { + return new Decoder( + (dynamic_data) => { + let $ = decoder.function(dynamic_data); + let data = $[0]; + let errors = $[1]; + let decoder$1 = next2(data); + let $1 = decoder$1.function(dynamic_data); + let layer = $1; + let data$1 = $1[0]; + if (errors instanceof Empty) { + return layer; + } else { + return [data$1, errors]; + } + } + ); +} +function run_decoders(loop$data, loop$failure, loop$decoders) { + while (true) { + let data = loop$data; + let failure2 = loop$failure; + let decoders = loop$decoders; + if (decoders instanceof Empty) { + return failure2; + } else { + let decoder = decoders.head; + let decoders$1 = decoders.tail; + let $ = decoder.function(data); + let layer = $; + let errors = $[1]; + if (errors instanceof Empty) { + return layer; + } else { + loop$data = data; + loop$failure = failure2; + loop$decoders = decoders$1; + } + } + } +} +function one_of(first3, alternatives) { + return new Decoder( + (dynamic_data) => { + let $ = first3.function(dynamic_data); + let layer = $; + let errors = $[1]; + if (errors instanceof Empty) { + return layer; + } else { + return run_decoders(dynamic_data, layer, alternatives); + } + } + ); +} +var dynamic = /* @__PURE__ */ new Decoder(decode_dynamic); +function decode_error(expected, found) { + return toList([ + new DecodeError(expected, classify_dynamic(found), toList([])) + ]); +} +function run_dynamic_function(data, name6, f) { + let $ = f(data); + if ($ instanceof Ok) { + let data$1 = $[0]; + return [data$1, toList([])]; + } else { + let zero = $[0]; + return [ + zero, + toList([new DecodeError(name6, classify_dynamic(data), toList([]))]) + ]; + } +} +function decode_bool(data) { + let $ = isEqual(identity(true), data); + if ($) { + return [true, toList([])]; + } else { + let $1 = isEqual(identity(false), data); + if ($1) { + return [false, toList([])]; + } else { + return [false, decode_error("Bool", data)]; + } + } +} +function decode_int(data) { + return run_dynamic_function(data, "Int", int); +} +function failure(zero, expected) { + return new Decoder((d) => { + return [zero, decode_error(expected, d)]; + }); +} +function new_primitive_decoder(name6, decoding_function) { + return new Decoder( + (d) => { + let $ = decoding_function(d); + if ($ instanceof Ok) { + let t = $[0]; + return [t, toList([])]; + } else { + let zero = $[0]; + return [ + zero, + toList([new DecodeError(name6, classify_dynamic(d), toList([]))]) + ]; + } + } + ); +} +var bool = /* @__PURE__ */ new Decoder(decode_bool); +var int2 = /* @__PURE__ */ new Decoder(decode_int); +function decode_string(data) { + return run_dynamic_function(data, "String", string); +} +var string2 = /* @__PURE__ */ new Decoder(decode_string); +function push_path(layer, path2) { + let decoder = one_of( + string2, + toList([ + (() => { + let _pipe = int2; + return map2(_pipe, to_string); + })() + ]) + ); + let path$1 = map( + path2, + (key) => { + let key$1 = identity(key); + let $ = run(key$1, decoder); + if ($ instanceof Ok) { + let key$2 = $[0]; + return key$2; + } else { + return "<" + classify_dynamic(key$1) + ">"; + } + } + ); + let errors = map( + layer[1], + (error) => { + let _record = error; + return new DecodeError( + _record.expected, + _record.found, + append(path$1, error.path) + ); + } + ); + return [layer[0], errors]; +} +function index3(loop$path, loop$position, loop$inner, loop$data, loop$handle_miss) { + while (true) { + let path2 = loop$path; + let position = loop$position; + let inner = loop$inner; + let data = loop$data; + let handle_miss = loop$handle_miss; + if (path2 instanceof Empty) { + let _pipe = inner(data); + return push_path(_pipe, reverse(position)); + } else { + let key = path2.head; + let path$1 = path2.tail; + let $ = index2(data, key); + if ($ instanceof Ok) { + let $1 = $[0]; + if ($1 instanceof Some) { + let data$1 = $1[0]; + loop$path = path$1; + loop$position = prepend(key, position); + loop$inner = inner; + loop$data = data$1; + loop$handle_miss = handle_miss; + } else { + return handle_miss(data, prepend(key, position)); + } + } else { + let kind = $[0]; + let $1 = inner(data); + let default$ = $1[0]; + let _pipe = [ + default$, + toList([new DecodeError(kind, classify_dynamic(data), toList([]))]) + ]; + return push_path(_pipe, reverse(position)); + } + } + } +} +function subfield(field_path, field_decoder, next2) { + return new Decoder( + (data) => { + let $ = index3( + field_path, + toList([]), + field_decoder.function, + data, + (data2, position) => { + let $12 = field_decoder.function(data2); + let default$ = $12[0]; + let _pipe = [ + default$, + toList([new DecodeError("Field", "Nothing", toList([]))]) + ]; + return push_path(_pipe, reverse(position)); + } + ); + let out = $[0]; + let errors1 = $[1]; + let $1 = next2(out).function(data); + let out$1 = $1[0]; + let errors2 = $1[1]; + return [out$1, append(errors1, errors2)]; + } + ); +} +function field(field_name, field_decoder, next2) { + return subfield(toList([field_name]), field_decoder, next2); +} + +// build/dev/javascript/gleam_stdlib/gleam_stdlib.mjs +var Nil = void 0; +var NOT_FOUND = {}; +function identity(x) { + return x; +} +function to_string(term) { + return term.toString(); +} +function float_to_string(float2) { + const string5 = float2.toString().replace("+", ""); + if (string5.indexOf(".") >= 0) { + return string5; + } else { + const index4 = string5.indexOf("e"); + if (index4 >= 0) { + return string5.slice(0, index4) + ".0" + string5.slice(index4); + } else { + return string5 + ".0"; + } + } +} +function string_length(string5) { + if (string5 === "") { + return 0; + } + const iterator = graphemes_iterator(string5); + if (iterator) { + let i = 0; + for (const _ of iterator) { + i++; + } + return i; + } else { + return string5.match(/./gsu).length; + } +} +var segmenter = void 0; +function graphemes_iterator(string5) { + if (globalThis.Intl && Intl.Segmenter) { + segmenter ||= new Intl.Segmenter(); + return segmenter.segment(string5)[Symbol.iterator](); + } +} +function lowercase(string5) { + return string5.toLowerCase(); +} +function contains_string(haystack, needle) { + return haystack.indexOf(needle) >= 0; +} +function starts_with(haystack, needle) { + return haystack.startsWith(needle); +} +var unicode_whitespaces = [ + " ", + // Space + " ", + // Horizontal tab + "\n", + // Line feed + "\v", + // Vertical tab + "\f", + // Form feed + "\r", + // Carriage return + "\x85", + // Next line + "\u2028", + // Line separator + "\u2029" + // Paragraph separator +].join(""); +var trim_start_regex = /* @__PURE__ */ new RegExp( + `^[${unicode_whitespaces}]*` +); +var trim_end_regex = /* @__PURE__ */ new RegExp(`[${unicode_whitespaces}]*$`); +function new_map() { + return Dict.new(); +} +function map_size(map4) { + return map4.size; +} +function map_to_list(map4) { + return List.fromArray(map4.entries()); +} +function map_remove(key, map4) { + return map4.delete(key); +} +function map_get(map4, key) { + const value2 = map4.get(key, NOT_FOUND); + if (value2 === NOT_FOUND) { + return new Error(Nil); + } + return new Ok(value2); +} +function map_insert(key, value2, map4) { + return map4.set(key, value2); +} +function classify_dynamic(data) { + if (typeof data === "string") { + return "String"; + } else if (typeof data === "boolean") { + return "Bool"; + } else if (data instanceof Result) { + return "Result"; + } else if (data instanceof List) { + return "List"; + } else if (data instanceof BitArray) { + return "BitArray"; + } else if (data instanceof Dict) { + return "Dict"; + } else if (Number.isInteger(data)) { + return "Int"; + } else if (Array.isArray(data)) { + return `Array`; + } else if (typeof data === "number") { + return "Float"; + } else if (data === null) { + return "Nil"; + } else if (data === void 0) { + return "Nil"; + } else { + const type = typeof data; + return type.charAt(0).toUpperCase() + type.slice(1); + } +} +function index2(data, key) { + if (data instanceof Dict || data instanceof WeakMap || data instanceof Map) { + const token2 = {}; + const entry = data.get(key, token2); + if (entry === token2) + return new Ok(new None()); + return new Ok(new Some(entry)); + } + const key_is_int = Number.isInteger(key); + if (key_is_int && key >= 0 && key < 8 && data instanceof List) { + let i = 0; + for (const value2 of data) { + if (i === key) + return new Ok(new Some(value2)); + i++; + } + return new Error("Indexable"); + } + if (key_is_int && Array.isArray(data) || data && typeof data === "object" || data && Object.getPrototypeOf(data) === Object.prototype) { + if (key in data) + return new Ok(new Some(data[key])); + return new Ok(new None()); + } + return new Error(key_is_int ? "Indexable" : "Dict"); +} +function int(data) { + if (Number.isInteger(data)) + return new Ok(data); + return new Error(0); +} +function string(data) { + if (typeof data === "string") + return new Ok(data); + return new Error(""); +} + +// build/dev/javascript/gleam_stdlib/gleam/float.mjs +function sum_loop(loop$numbers, loop$initial) { + while (true) { + let numbers = loop$numbers; + let initial = loop$initial; + if (numbers instanceof Empty) { + return initial; + } else { + let first3 = numbers.head; + let rest = numbers.tail; + loop$numbers = rest; + loop$initial = first3 + initial; + } + } +} +function sum(numbers) { + return sum_loop(numbers, 0); +} + +// build/dev/javascript/gleam_stdlib/gleam/int.mjs +function compare2(a, b) { + let $ = a === b; + if ($) { + return new Eq(); + } else { + let $1 = a < b; + if ($1) { + return new Lt(); + } else { + return new Gt(); + } + } +} +function add(a, b) { + return a + b; +} +function subtract(a, b) { + return a - b; +} + +// build/dev/javascript/gleam_stdlib/gleam/string.mjs +function concat_loop(loop$strings, loop$accumulator) { + while (true) { + let strings = loop$strings; + let accumulator = loop$accumulator; + if (strings instanceof Empty) { + return accumulator; + } else { + let string5 = strings.head; + let strings$1 = strings.tail; + loop$strings = strings$1; + loop$accumulator = accumulator + string5; + } + } +} +function concat2(strings) { + return concat_loop(strings, ""); +} +function join_loop(loop$strings, loop$separator, loop$accumulator) { + while (true) { + let strings = loop$strings; + let separator = loop$separator; + let accumulator = loop$accumulator; + if (strings instanceof Empty) { + return accumulator; + } else { + let string5 = strings.head; + let strings$1 = strings.tail; + loop$strings = strings$1; + loop$separator = separator; + loop$accumulator = accumulator + separator + string5; + } + } +} +function join(strings, separator) { + if (strings instanceof Empty) { + return ""; + } else { + let first$1 = strings.head; + let rest = strings.tail; + return join_loop(rest, separator, first$1); + } +} + +// build/dev/javascript/gleam_stdlib/gleam/result.mjs +function is_ok(result) { + if (result instanceof Ok) { + return true; + } else { + return false; + } +} +function map3(result, fun) { + if (result instanceof Ok) { + let x = result[0]; + return new Ok(fun(x)); + } else { + let e = result[0]; + return new Error(e); + } +} +function try$(result, fun) { + if (result instanceof Ok) { + let x = result[0]; + return fun(x); + } else { + let e = result[0]; + return new Error(e); + } +} +function then$2(result, fun) { + return try$(result, fun); +} +function unwrap(result, default$) { + if (result instanceof Ok) { + let v = result[0]; + return v; + } else { + return default$; + } +} +function or(first3, second2) { + if (first3 instanceof Ok) { + return first3; + } else { + return second2; + } +} +function replace_error(result, error) { + if (result instanceof Ok) { + let x = result[0]; + return new Ok(x); + } else { + return new Error(error); + } +} + +// build/dev/javascript/gleam_json/gleam_json_ffi.mjs +function object(entries) { + return Object.fromEntries(entries); +} +function identity2(x) { + return x; +} +function do_null() { + return null; +} + +// build/dev/javascript/gleam_json/gleam/json.mjs +function string3(input2) { + return identity2(input2); +} +function bool2(input2) { + return identity2(input2); +} +function null$() { + return do_null(); +} +function object2(entries) { + return object(entries); +} + +// build/dev/javascript/gleam_stdlib/gleam/bool.mjs +function to_string2(bool4) { + if (bool4) { + return "True"; + } else { + return "False"; + } +} +function guard(requirement, consequence, alternative) { + if (requirement) { + return consequence; + } else { + return alternative(); + } +} + +// build/dev/javascript/gleam_stdlib/gleam/function.mjs +function identity3(x) { + return x; +} + +// build/dev/javascript/gleam_stdlib/gleam/set.mjs +var Set2 = class extends CustomType { + constructor(dict2) { + super(); + this.dict = dict2; + } +}; +function new$() { + return new Set2(new_map()); +} +function size(set2) { + return map_size(set2.dict); +} +function contains(set2, member) { + let _pipe = set2.dict; + let _pipe$1 = map_get(_pipe, member); + return is_ok(_pipe$1); +} +function delete$2(set2, member) { + return new Set2(delete$(set2.dict, member)); +} +function filter3(set2, predicate) { + return new Set2(filter(set2.dict, (m, _) => { + return predicate(m); + })); +} +var token = void 0; +function insert2(set2, member) { + return new Set2(insert(set2.dict, member, token)); +} +function from_list2(members) { + let dict2 = fold2( + members, + new_map(), + (m, k) => { + return insert(m, k, token); + } + ); + return new Set2(dict2); +} + +// build/dev/javascript/lustre/lustre/internals/constants.ffi.mjs +var EMPTY_DICT = /* @__PURE__ */ Dict.new(); +var EMPTY_SET = /* @__PURE__ */ new$(); +var empty_dict = () => EMPTY_DICT; +var empty_set = () => EMPTY_SET; +var document2 = () => globalThis?.document; +var NAMESPACE_HTML = "http://www.w3.org/1999/xhtml"; +var ELEMENT_NODE = 1; +var TEXT_NODE = 3; +var DOCUMENT_FRAGMENT_NODE = 11; +var SUPPORTS_MOVE_BEFORE = !!globalThis.HTMLElement?.prototype?.moveBefore; + +// build/dev/javascript/lustre/lustre/internals/constants.mjs +var empty_list = /* @__PURE__ */ toList([]); +var option_none = /* @__PURE__ */ new None(); + +// build/dev/javascript/lustre/lustre/vdom/vattr.ffi.mjs +var GT = /* @__PURE__ */ new Gt(); +var LT = /* @__PURE__ */ new Lt(); +var EQ = /* @__PURE__ */ new Eq(); +function compare3(a, b) { + if (a.name === b.name) { + return EQ; + } else if (a.name < b.name) { + return LT; + } else { + return GT; + } +} + +// build/dev/javascript/lustre/lustre/vdom/vattr.mjs +var Attribute = class extends CustomType { + constructor(kind, name6, value2) { + super(); + this.kind = kind; + this.name = name6; + this.value = value2; + } +}; +var Property = class extends CustomType { + constructor(kind, name6, value2) { + super(); + this.kind = kind; + this.name = name6; + this.value = value2; + } +}; +var Event2 = class extends CustomType { + constructor(kind, name6, handler, include, prevent_default3, stop_propagation, immediate2, debounce, throttle) { + super(); + this.kind = kind; + this.name = name6; + this.handler = handler; + this.include = include; + this.prevent_default = prevent_default3; + this.stop_propagation = stop_propagation; + this.immediate = immediate2; + this.debounce = debounce; + this.throttle = throttle; + } +}; +var Handler = class extends CustomType { + constructor(prevent_default3, stop_propagation, message) { + super(); + this.prevent_default = prevent_default3; + this.stop_propagation = stop_propagation; + this.message = message; + } +}; +var Never = class extends CustomType { + constructor(kind) { + super(); + this.kind = kind; + } +}; +function merge(loop$attributes, loop$merged) { + while (true) { + let attributes = loop$attributes; + let merged = loop$merged; + if (attributes instanceof Empty) { + return merged; + } else { + let $ = attributes.head; + if ($ instanceof Attribute) { + let $1 = $.name; + if ($1 === "") { + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = merged; + } else if ($1 === "class") { + let $2 = $.value; + if ($2 === "") { + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = merged; + } else { + let $3 = attributes.tail; + if ($3 instanceof Empty) { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } else { + let $4 = $3.head; + if ($4 instanceof Attribute) { + let $5 = $4.name; + if ($5 === "class") { + let kind = $.kind; + let class1 = $2; + let rest = $3.tail; + let class2 = $4.value; + let value2 = class1 + " " + class2; + let attribute$1 = new Attribute(kind, "class", value2); + loop$attributes = prepend(attribute$1, rest); + loop$merged = merged; + } else { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } else { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } + } + } else if ($1 === "style") { + let $2 = $.value; + if ($2 === "") { + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = merged; + } else { + let $3 = attributes.tail; + if ($3 instanceof Empty) { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } else { + let $4 = $3.head; + if ($4 instanceof Attribute) { + let $5 = $4.name; + if ($5 === "style") { + let kind = $.kind; + let style1 = $2; + let rest = $3.tail; + let style2 = $4.value; + let value2 = style1 + ";" + style2; + let attribute$1 = new Attribute(kind, "style", value2); + loop$attributes = prepend(attribute$1, rest); + loop$merged = merged; + } else { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } else { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } + } + } else { + let attribute$1 = $; + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } else { + let attribute$1 = $; + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } + } +} +function prepare(attributes) { + if (attributes instanceof Empty) { + return attributes; + } else { + let $ = attributes.tail; + if ($ instanceof Empty) { + return attributes; + } else { + let _pipe = attributes; + let _pipe$1 = sort(_pipe, (a, b) => { + return compare3(b, a); + }); + return merge(_pipe$1, empty_list); + } + } +} +var attribute_kind = 0; +function attribute(name6, value2) { + return new Attribute(attribute_kind, name6, value2); +} +var property_kind = 1; +var event_kind = 2; +function event(name6, handler, include, prevent_default3, stop_propagation, immediate2, debounce, throttle) { + return new Event2( + event_kind, + name6, + handler, + include, + prevent_default3, + stop_propagation, + immediate2, + debounce, + throttle + ); +} +var never_kind = 0; +var never = /* @__PURE__ */ new Never(never_kind); +var always_kind = 2; + +// build/dev/javascript/lustre/lustre/attribute.mjs +function attribute2(name6, value2) { + return attribute(name6, value2); +} +function class$(name6) { + return attribute2("class", name6); +} +function style(property3, value2) { + if (property3 === "") { + return class$(""); + } else if (value2 === "") { + return class$(""); + } else { + return attribute2("style", property3 + ":" + value2 + ";"); + } +} +function do_styles(loop$properties, loop$styles) { + while (true) { + let properties = loop$properties; + let styles2 = loop$styles; + if (properties instanceof Empty) { + return styles2; + } else { + let $ = properties.head[0]; + if ($ === "") { + let rest = properties.tail; + loop$properties = rest; + loop$styles = styles2; + } else { + let $1 = properties.head[1]; + if ($1 === "") { + let rest = properties.tail; + loop$properties = rest; + loop$styles = styles2; + } else { + let rest = properties.tail; + let name$1 = $; + let value$1 = $1; + loop$properties = rest; + loop$styles = styles2 + name$1 + ":" + value$1 + ";"; + } + } + } + } +} +function styles(properties) { + return attribute2("style", do_styles(properties, "")); +} +function autocomplete(value2) { + return attribute2("autocomplete", value2); +} +function name(element_name) { + return attribute2("name", element_name); +} +function value(control_value) { + return attribute2("value", control_value); +} + +// build/dev/javascript/lustre/lustre/effect.mjs +var Effect = class extends CustomType { + constructor(synchronous, before_paint2, after_paint2) { + super(); + this.synchronous = synchronous; + this.before_paint = before_paint2; + this.after_paint = after_paint2; + } +}; +var empty = /* @__PURE__ */ new Effect( + /* @__PURE__ */ toList([]), + /* @__PURE__ */ toList([]), + /* @__PURE__ */ toList([]) +); +function none() { + return empty; +} +function from(effect) { + let task = (actions) => { + let dispatch = actions.dispatch; + return effect(dispatch); + }; + let _record = empty; + return new Effect(toList([task]), _record.before_paint, _record.after_paint); +} +function before_paint(effect) { + let task = (actions) => { + let root3 = actions.root(); + let dispatch = actions.dispatch; + return effect(dispatch, root3); + }; + let _record = empty; + return new Effect(_record.synchronous, toList([task]), _record.after_paint); +} +function after_paint(effect) { + let task = (actions) => { + let root3 = actions.root(); + let dispatch = actions.dispatch; + return effect(dispatch, root3); + }; + let _record = empty; + return new Effect(_record.synchronous, _record.before_paint, toList([task])); +} +function event2(name6, data) { + let task = (actions) => { + return actions.emit(name6, data); + }; + let _record = empty; + return new Effect(toList([task]), _record.before_paint, _record.after_paint); +} +function batch(effects) { + return fold2( + effects, + empty, + (acc, eff) => { + return new Effect( + fold2(eff.synchronous, acc.synchronous, prepend2), + fold2(eff.before_paint, acc.before_paint, prepend2), + fold2(eff.after_paint, acc.after_paint, prepend2) + ); + } + ); +} + +// build/dev/javascript/lustre/lustre/internals/mutable_map.ffi.mjs +function empty2() { + return null; +} +function get(map4, key) { + const value2 = map4?.get(key); + if (value2 != null) { + return new Ok(value2); + } else { + return new Error(void 0); + } +} +function insert3(map4, key, value2) { + map4 ??= /* @__PURE__ */ new Map(); + map4.set(key, value2); + return map4; +} +function remove(map4, key) { + map4?.delete(key); + return map4; +} + +// build/dev/javascript/lustre/lustre/vdom/path.mjs +var Root = class extends CustomType { +}; +var Key = class extends CustomType { + constructor(key, parent) { + super(); + this.key = key; + this.parent = parent; + } +}; +var Index = class extends CustomType { + constructor(index4, parent) { + super(); + this.index = index4; + this.parent = parent; + } +}; +function do_matches(loop$path, loop$candidates) { + while (true) { + let path2 = loop$path; + let candidates = loop$candidates; + if (candidates instanceof Empty) { + return false; + } else { + let candidate = candidates.head; + let rest = candidates.tail; + let $ = starts_with(path2, candidate); + if ($) { + return true; + } else { + loop$path = path2; + loop$candidates = rest; + } + } + } +} +function add3(parent, index4, key) { + if (key === "") { + return new Index(index4, parent); + } else { + return new Key(key, parent); + } +} +var root2 = /* @__PURE__ */ new Root(); +var separator_element = " "; +function do_to_string(loop$path, loop$acc) { + while (true) { + let path2 = loop$path; + let acc = loop$acc; + if (path2 instanceof Root) { + if (acc instanceof Empty) { + return ""; + } else { + let segments = acc.tail; + return concat2(segments); + } + } else if (path2 instanceof Key) { + let key = path2.key; + let parent = path2.parent; + loop$path = parent; + loop$acc = prepend(separator_element, prepend(key, acc)); + } else { + let index4 = path2.index; + let parent = path2.parent; + loop$path = parent; + loop$acc = prepend( + separator_element, + prepend(to_string(index4), acc) + ); + } + } +} +function to_string3(path2) { + return do_to_string(path2, toList([])); +} +function matches(path2, candidates) { + if (candidates instanceof Empty) { + return false; + } else { + return do_matches(to_string3(path2), candidates); + } +} +var separator_event = "\n"; +function event3(path2, event4) { + return do_to_string(path2, toList([separator_event, event4])); +} + +// build/dev/javascript/lustre/lustre/vdom/vnode.mjs +var Fragment = class extends CustomType { + constructor(kind, key, mapper, children, keyed_children, children_count) { + super(); + this.kind = kind; + this.key = key; + this.mapper = mapper; + this.children = children; + this.keyed_children = keyed_children; + this.children_count = children_count; + } +}; +var Element2 = class extends CustomType { + constructor(kind, key, mapper, namespace2, tag, attributes, children, keyed_children, self_closing, void$) { + super(); + this.kind = kind; + this.key = key; + this.mapper = mapper; + this.namespace = namespace2; + this.tag = tag; + this.attributes = attributes; + this.children = children; + this.keyed_children = keyed_children; + this.self_closing = self_closing; + this.void = void$; + } +}; +var Text = class extends CustomType { + constructor(kind, key, mapper, content) { + super(); + this.kind = kind; + this.key = key; + this.mapper = mapper; + this.content = content; + } +}; +var UnsafeInnerHtml = class extends CustomType { + constructor(kind, key, mapper, namespace2, tag, attributes, inner_html) { + super(); + this.kind = kind; + this.key = key; + this.mapper = mapper; + this.namespace = namespace2; + this.tag = tag; + this.attributes = attributes; + this.inner_html = inner_html; + } +}; +function is_void_element(tag, namespace2) { + if (namespace2 === "") { + if (tag === "area") { + return true; + } else if (tag === "base") { + return true; + } else if (tag === "br") { + return true; + } else if (tag === "col") { + return true; + } else if (tag === "embed") { + return true; + } else if (tag === "hr") { + return true; + } else if (tag === "img") { + return true; + } else if (tag === "input") { + return true; + } else if (tag === "link") { + return true; + } else if (tag === "meta") { + return true; + } else if (tag === "param") { + return true; + } else if (tag === "source") { + return true; + } else if (tag === "track") { + return true; + } else if (tag === "wbr") { + return true; + } else { + return false; + } + } else { + return false; + } +} +function advance(node) { + if (node instanceof Fragment) { + let children_count = node.children_count; + return 1 + children_count; + } else { + return 1; + } +} +var fragment_kind = 0; +function fragment(key, mapper, children, keyed_children, children_count) { + return new Fragment( + fragment_kind, + key, + mapper, + children, + keyed_children, + children_count + ); +} +var element_kind = 1; +function element(key, mapper, namespace2, tag, attributes, children, keyed_children, self_closing, void$) { + return new Element2( + element_kind, + key, + mapper, + namespace2, + tag, + prepare(attributes), + children, + keyed_children, + self_closing, + void$ || is_void_element(tag, namespace2) + ); +} +var text_kind = 2; +function text(key, mapper, content) { + return new Text(text_kind, key, mapper, content); +} +var unsafe_inner_html_kind = 3; +function set_fragment_key(loop$key, loop$children, loop$index, loop$new_children, loop$keyed_children) { + while (true) { + let key = loop$key; + let children = loop$children; + let index4 = loop$index; + let new_children = loop$new_children; + let keyed_children = loop$keyed_children; + if (children instanceof Empty) { + return [reverse(new_children), keyed_children]; + } else { + let $ = children.head; + if ($ instanceof Fragment) { + let node = $; + if (node.key === "") { + let children$1 = children.tail; + let child_key = key + "::" + to_string(index4); + let $1 = set_fragment_key( + child_key, + node.children, + 0, + empty_list, + empty2() + ); + let node_children = $1[0]; + let node_keyed_children = $1[1]; + let _block; + let _record = node; + _block = new Fragment( + _record.kind, + _record.key, + _record.mapper, + node_children, + node_keyed_children, + _record.children_count + ); + let new_node = _block; + let new_children$1 = prepend(new_node, new_children); + let index$1 = index4 + 1; + loop$key = key; + loop$children = children$1; + loop$index = index$1; + loop$new_children = new_children$1; + loop$keyed_children = keyed_children; + } else { + let node$1 = $; + if (node$1.key !== "") { + let children$1 = children.tail; + let child_key = key + "::" + node$1.key; + let keyed_node = to_keyed(child_key, node$1); + let new_children$1 = prepend(keyed_node, new_children); + let keyed_children$1 = insert3( + keyed_children, + child_key, + keyed_node + ); + let index$1 = index4 + 1; + loop$key = key; + loop$children = children$1; + loop$index = index$1; + loop$new_children = new_children$1; + loop$keyed_children = keyed_children$1; + } else { + let node$2 = $; + let children$1 = children.tail; + let new_children$1 = prepend(node$2, new_children); + let index$1 = index4 + 1; + loop$key = key; + loop$children = children$1; + loop$index = index$1; + loop$new_children = new_children$1; + loop$keyed_children = keyed_children; + } + } + } else { + let node = $; + if (node.key !== "") { + let children$1 = children.tail; + let child_key = key + "::" + node.key; + let keyed_node = to_keyed(child_key, node); + let new_children$1 = prepend(keyed_node, new_children); + let keyed_children$1 = insert3( + keyed_children, + child_key, + keyed_node + ); + let index$1 = index4 + 1; + loop$key = key; + loop$children = children$1; + loop$index = index$1; + loop$new_children = new_children$1; + loop$keyed_children = keyed_children$1; + } else { + let node$1 = $; + let children$1 = children.tail; + let new_children$1 = prepend(node$1, new_children); + let index$1 = index4 + 1; + loop$key = key; + loop$children = children$1; + loop$index = index$1; + loop$new_children = new_children$1; + loop$keyed_children = keyed_children; + } + } + } + } +} +function to_keyed(key, node) { + if (node instanceof Fragment) { + let children = node.children; + let $ = set_fragment_key( + key, + children, + 0, + empty_list, + empty2() + ); + let children$1 = $[0]; + let keyed_children = $[1]; + let _record = node; + return new Fragment( + _record.kind, + key, + _record.mapper, + children$1, + keyed_children, + _record.children_count + ); + } else if (node instanceof Element2) { + let _record = node; + return new Element2( + _record.kind, + key, + _record.mapper, + _record.namespace, + _record.tag, + _record.attributes, + _record.children, + _record.keyed_children, + _record.self_closing, + _record.void + ); + } else if (node instanceof Text) { + let _record = node; + return new Text(_record.kind, key, _record.mapper, _record.content); + } else { + let _record = node; + return new UnsafeInnerHtml( + _record.kind, + key, + _record.mapper, + _record.namespace, + _record.tag, + _record.attributes, + _record.inner_html + ); + } +} + +// build/dev/javascript/lustre/lustre/internals/equals.ffi.mjs +var isReferenceEqual = (a, b) => a === b; +var isEqual2 = (a, b) => { + if (a === b) { + return true; + } + if (a == null || b == null) { + return false; + } + const type = typeof a; + if (type !== typeof b) { + return false; + } + if (type !== "object") { + return false; + } + const ctor = a.constructor; + if (ctor !== b.constructor) { + return false; + } + if (Array.isArray(a)) { + return areArraysEqual(a, b); + } + return areObjectsEqual(a, b); +}; +var areArraysEqual = (a, b) => { + let index4 = a.length; + if (index4 !== b.length) { + return false; + } + while (index4--) { + if (!isEqual2(a[index4], b[index4])) { + return false; + } + } + return true; +}; +var areObjectsEqual = (a, b) => { + const properties = Object.keys(a); + let index4 = properties.length; + if (Object.keys(b).length !== index4) { + return false; + } + while (index4--) { + const property3 = properties[index4]; + if (!Object.hasOwn(b, property3)) { + return false; + } + if (!isEqual2(a[property3], b[property3])) { + return false; + } + } + return true; +}; + +// build/dev/javascript/lustre/lustre/vdom/events.mjs +var Events = class extends CustomType { + constructor(handlers, dispatched_paths, next_dispatched_paths) { + super(); + this.handlers = handlers; + this.dispatched_paths = dispatched_paths; + this.next_dispatched_paths = next_dispatched_paths; + } +}; +function new$3() { + return new Events( + empty2(), + empty_list, + empty_list + ); +} +function tick(events) { + return new Events( + events.handlers, + events.next_dispatched_paths, + empty_list + ); +} +function do_remove_event(handlers, path2, name6) { + return remove(handlers, event3(path2, name6)); +} +function remove_event(events, path2, name6) { + let handlers = do_remove_event(events.handlers, path2, name6); + let _record = events; + return new Events( + handlers, + _record.dispatched_paths, + _record.next_dispatched_paths + ); +} +function remove_attributes(handlers, path2, attributes) { + return fold2( + attributes, + handlers, + (events, attribute5) => { + if (attribute5 instanceof Event2) { + let name6 = attribute5.name; + return do_remove_event(events, path2, name6); + } else { + return events; + } + } + ); +} +function handle(events, path2, name6, event4) { + let next_dispatched_paths = prepend(path2, events.next_dispatched_paths); + let _block; + let _record = events; + _block = new Events( + _record.handlers, + _record.dispatched_paths, + next_dispatched_paths + ); + let events$1 = _block; + let $ = get( + events$1.handlers, + path2 + separator_event + name6 + ); + if ($ instanceof Ok) { + let handler = $[0]; + return [events$1, run(event4, handler)]; + } else { + return [events$1, new Error(toList([]))]; + } +} +function has_dispatched_events(events, path2) { + return matches(path2, events.dispatched_paths); +} +function do_add_event(handlers, mapper, path2, name6, handler) { + return insert3( + handlers, + event3(path2, name6), + map2( + handler, + (handler2) => { + let _record = handler2; + return new Handler( + _record.prevent_default, + _record.stop_propagation, + identity3(mapper)(handler2.message) + ); + } + ) + ); +} +function add_event(events, mapper, path2, name6, handler) { + let handlers = do_add_event(events.handlers, mapper, path2, name6, handler); + let _record = events; + return new Events( + handlers, + _record.dispatched_paths, + _record.next_dispatched_paths + ); +} +function add_attributes(handlers, mapper, path2, attributes) { + return fold2( + attributes, + handlers, + (events, attribute5) => { + if (attribute5 instanceof Event2) { + let name6 = attribute5.name; + let handler = attribute5.handler; + return do_add_event(events, mapper, path2, name6, handler); + } else { + return events; + } + } + ); +} +function compose_mapper(mapper, child_mapper) { + let $ = isReferenceEqual(mapper, identity3); + let $1 = isReferenceEqual(child_mapper, identity3); + if ($1) { + return mapper; + } else if ($) { + return child_mapper; + } else { + return (msg) => { + return mapper(child_mapper(msg)); + }; + } +} +function do_remove_children(loop$handlers, loop$path, loop$child_index, loop$children) { + while (true) { + let handlers = loop$handlers; + let path2 = loop$path; + let child_index = loop$child_index; + let children = loop$children; + if (children instanceof Empty) { + return handlers; + } else { + let child2 = children.head; + let rest = children.tail; + let _pipe = handlers; + let _pipe$1 = do_remove_child(_pipe, path2, child_index, child2); + loop$handlers = _pipe$1; + loop$path = path2; + loop$child_index = child_index + advance(child2); + loop$children = rest; + } + } +} +function do_remove_child(handlers, parent, child_index, child2) { + if (child2 instanceof Fragment) { + let children = child2.children; + return do_remove_children(handlers, parent, child_index + 1, children); + } else if (child2 instanceof Element2) { + let attributes = child2.attributes; + let children = child2.children; + let path2 = add3(parent, child_index, child2.key); + let _pipe = handlers; + let _pipe$1 = remove_attributes(_pipe, path2, attributes); + return do_remove_children(_pipe$1, path2, 0, children); + } else if (child2 instanceof Text) { + return handlers; + } else { + let attributes = child2.attributes; + let path2 = add3(parent, child_index, child2.key); + return remove_attributes(handlers, path2, attributes); + } +} +function remove_child(events, parent, child_index, child2) { + let handlers = do_remove_child(events.handlers, parent, child_index, child2); + let _record = events; + return new Events( + handlers, + _record.dispatched_paths, + _record.next_dispatched_paths + ); +} +function do_add_children(loop$handlers, loop$mapper, loop$path, loop$child_index, loop$children) { + while (true) { + let handlers = loop$handlers; + let mapper = loop$mapper; + let path2 = loop$path; + let child_index = loop$child_index; + let children = loop$children; + if (children instanceof Empty) { + return handlers; + } else { + let child2 = children.head; + let rest = children.tail; + let _pipe = handlers; + let _pipe$1 = do_add_child(_pipe, mapper, path2, child_index, child2); + loop$handlers = _pipe$1; + loop$mapper = mapper; + loop$path = path2; + loop$child_index = child_index + advance(child2); + loop$children = rest; + } + } +} +function do_add_child(handlers, mapper, parent, child_index, child2) { + if (child2 instanceof Fragment) { + let children = child2.children; + let composed_mapper = compose_mapper(mapper, child2.mapper); + let child_index$1 = child_index + 1; + return do_add_children( + handlers, + composed_mapper, + parent, + child_index$1, + children + ); + } else if (child2 instanceof Element2) { + let attributes = child2.attributes; + let children = child2.children; + let path2 = add3(parent, child_index, child2.key); + let composed_mapper = compose_mapper(mapper, child2.mapper); + let _pipe = handlers; + let _pipe$1 = add_attributes(_pipe, composed_mapper, path2, attributes); + return do_add_children(_pipe$1, composed_mapper, path2, 0, children); + } else if (child2 instanceof Text) { + return handlers; + } else { + let attributes = child2.attributes; + let path2 = add3(parent, child_index, child2.key); + let composed_mapper = compose_mapper(mapper, child2.mapper); + return add_attributes(handlers, composed_mapper, path2, attributes); + } +} +function add_child(events, mapper, parent, index4, child2) { + let handlers = do_add_child(events.handlers, mapper, parent, index4, child2); + let _record = events; + return new Events( + handlers, + _record.dispatched_paths, + _record.next_dispatched_paths + ); +} +function add_children(events, mapper, path2, child_index, children) { + let handlers = do_add_children( + events.handlers, + mapper, + path2, + child_index, + children + ); + let _record = events; + return new Events( + handlers, + _record.dispatched_paths, + _record.next_dispatched_paths + ); +} + +// build/dev/javascript/lustre/lustre/element.mjs +function element2(tag, attributes, children) { + return element( + "", + identity3, + "", + tag, + attributes, + children, + empty2(), + false, + false + ); +} +function namespaced(namespace2, tag, attributes, children) { + return element( + "", + identity3, + namespace2, + tag, + attributes, + children, + empty2(), + false, + false + ); +} +function text2(content) { + return text("", identity3, content); +} +function none2() { + return text("", identity3, ""); +} +function count_fragment_children(loop$children, loop$count) { + while (true) { + let children = loop$children; + let count = loop$count; + if (children instanceof Empty) { + return count; + } else { + let child2 = children.head; + let rest = children.tail; + loop$children = rest; + loop$count = count + advance(child2); + } + } +} +function fragment2(children) { + return fragment( + "", + identity3, + children, + empty2(), + count_fragment_children(children, 0) + ); +} + +// build/dev/javascript/lustre/lustre/element/html.mjs +function text3(content) { + return text2(content); +} +function div(attrs, children) { + return element2("div", attrs, children); +} +function li(attrs, children) { + return element2("li", attrs, children); +} +function p(attrs, children) { + return element2("p", attrs, children); +} +function span(attrs, children) { + return element2("span", attrs, children); +} +function svg(attrs, children) { + return namespaced("http://www.w3.org/2000/svg", "svg", attrs, children); +} +function button(attrs, children) { + return element2("button", attrs, children); +} +function input(attrs) { + return element2("input", attrs, empty_list); +} +function slot(attrs, fallback) { + return element2("slot", attrs, fallback); +} + +// build/dev/javascript/lustre/lustre/vdom/patch.mjs +var Patch = class extends CustomType { + constructor(index4, removed, changes, children) { + super(); + this.index = index4; + this.removed = removed; + this.changes = changes; + this.children = children; + } +}; +var ReplaceText = class extends CustomType { + constructor(kind, content) { + super(); + this.kind = kind; + this.content = content; + } +}; +var ReplaceInnerHtml = class extends CustomType { + constructor(kind, inner_html) { + super(); + this.kind = kind; + this.inner_html = inner_html; + } +}; +var Update = class extends CustomType { + constructor(kind, added, removed) { + super(); + this.kind = kind; + this.added = added; + this.removed = removed; + } +}; +var Move = class extends CustomType { + constructor(kind, key, before, count) { + super(); + this.kind = kind; + this.key = key; + this.before = before; + this.count = count; + } +}; +var RemoveKey = class extends CustomType { + constructor(kind, key, count) { + super(); + this.kind = kind; + this.key = key; + this.count = count; + } +}; +var Replace = class extends CustomType { + constructor(kind, from2, count, with$) { + super(); + this.kind = kind; + this.from = from2; + this.count = count; + this.with = with$; + } +}; +var Insert = class extends CustomType { + constructor(kind, children, before) { + super(); + this.kind = kind; + this.children = children; + this.before = before; + } +}; +var Remove = class extends CustomType { + constructor(kind, from2, count) { + super(); + this.kind = kind; + this.from = from2; + this.count = count; + } +}; +function new$5(index4, removed, changes, children) { + return new Patch(index4, removed, changes, children); +} +var replace_text_kind = 0; +function replace_text(content) { + return new ReplaceText(replace_text_kind, content); +} +var replace_inner_html_kind = 1; +function replace_inner_html(inner_html) { + return new ReplaceInnerHtml(replace_inner_html_kind, inner_html); +} +var update_kind = 2; +function update(added, removed) { + return new Update(update_kind, added, removed); +} +var move_kind = 3; +function move(key, before, count) { + return new Move(move_kind, key, before, count); +} +var remove_key_kind = 4; +function remove_key(key, count) { + return new RemoveKey(remove_key_kind, key, count); +} +var replace_kind = 5; +function replace2(from2, count, with$) { + return new Replace(replace_kind, from2, count, with$); +} +var insert_kind = 6; +function insert4(children, before) { + return new Insert(insert_kind, children, before); +} +var remove_kind = 7; +function remove2(from2, count) { + return new Remove(remove_kind, from2, count); +} + +// build/dev/javascript/lustre/lustre/vdom/diff.mjs +var Diff = class extends CustomType { + constructor(patch, events) { + super(); + this.patch = patch; + this.events = events; + } +}; +var AttributeChange = class extends CustomType { + constructor(added, removed, events) { + super(); + this.added = added; + this.removed = removed; + this.events = events; + } +}; +function is_controlled(events, namespace2, tag, path2) { + if (tag === "input") { + if (namespace2 === "") { + return has_dispatched_events(events, path2); + } else { + return false; + } + } else if (tag === "select") { + if (namespace2 === "") { + return has_dispatched_events(events, path2); + } else { + return false; + } + } else if (tag === "textarea") { + if (namespace2 === "") { + return has_dispatched_events(events, path2); + } else { + return false; + } + } else { + return false; + } +} +function diff_attributes(loop$controlled, loop$path, loop$mapper, loop$events, loop$old, loop$new, loop$added, loop$removed) { + while (true) { + let controlled = loop$controlled; + let path2 = loop$path; + let mapper = loop$mapper; + let events = loop$events; + let old = loop$old; + let new$9 = loop$new; + let added = loop$added; + let removed = loop$removed; + if (new$9 instanceof Empty) { + if (old instanceof Empty) { + return new AttributeChange(added, removed, events); + } else { + let $ = old.head; + if ($ instanceof Event2) { + let prev = $; + let old$1 = old.tail; + let name6 = $.name; + let removed$1 = prepend(prev, removed); + let events$1 = remove_event(events, path2, name6); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = old$1; + loop$new = new$9; + loop$added = added; + loop$removed = removed$1; + } else { + let prev = $; + let old$1 = old.tail; + let removed$1 = prepend(prev, removed); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = old$1; + loop$new = new$9; + loop$added = added; + loop$removed = removed$1; + } + } + } else if (old instanceof Empty) { + let $ = new$9.head; + if ($ instanceof Event2) { + let next2 = $; + let new$1 = new$9.tail; + let name6 = $.name; + let handler = $.handler; + let added$1 = prepend(next2, added); + let events$1 = add_event(events, mapper, path2, name6, handler); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = old; + loop$new = new$1; + loop$added = added$1; + loop$removed = removed; + } else { + let next2 = $; + let new$1 = new$9.tail; + let added$1 = prepend(next2, added); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = old; + loop$new = new$1; + loop$added = added$1; + loop$removed = removed; + } + } else { + let next2 = new$9.head; + let remaining_new = new$9.tail; + let prev = old.head; + let remaining_old = old.tail; + let $ = compare3(prev, next2); + if ($ instanceof Lt) { + if (prev instanceof Event2) { + let name6 = prev.name; + let removed$1 = prepend(prev, removed); + let events$1 = remove_event(events, path2, name6); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = remaining_old; + loop$new = new$9; + loop$added = added; + loop$removed = removed$1; + } else { + let removed$1 = prepend(prev, removed); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = remaining_old; + loop$new = new$9; + loop$added = added; + loop$removed = removed$1; + } + } else if ($ instanceof Eq) { + if (next2 instanceof Attribute) { + if (prev instanceof Attribute) { + let _block; + let $1 = next2.name; + if ($1 === "value") { + _block = controlled || prev.value !== next2.value; + } else if ($1 === "checked") { + _block = controlled || prev.value !== next2.value; + } else if ($1 === "selected") { + _block = controlled || prev.value !== next2.value; + } else { + _block = prev.value !== next2.value; + } + let has_changes = _block; + let _block$1; + if (has_changes) { + _block$1 = prepend(next2, added); + } else { + _block$1 = added; + } + let added$1 = _block$1; + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } else if (prev instanceof Event2) { + let name6 = prev.name; + let added$1 = prepend(next2, added); + let removed$1 = prepend(prev, removed); + let events$1 = remove_event(events, path2, name6); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed$1; + } else { + let added$1 = prepend(next2, added); + let removed$1 = prepend(prev, removed); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed$1; + } + } else if (next2 instanceof Property) { + if (prev instanceof Property) { + let _block; + let $1 = next2.name; + if ($1 === "scrollLeft") { + _block = true; + } else if ($1 === "scrollRight") { + _block = true; + } else if ($1 === "value") { + _block = controlled || !isEqual2( + prev.value, + next2.value + ); + } else if ($1 === "checked") { + _block = controlled || !isEqual2( + prev.value, + next2.value + ); + } else if ($1 === "selected") { + _block = controlled || !isEqual2( + prev.value, + next2.value + ); + } else { + _block = !isEqual2(prev.value, next2.value); + } + let has_changes = _block; + let _block$1; + if (has_changes) { + _block$1 = prepend(next2, added); + } else { + _block$1 = added; + } + let added$1 = _block$1; + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } else if (prev instanceof Event2) { + let name6 = prev.name; + let added$1 = prepend(next2, added); + let removed$1 = prepend(prev, removed); + let events$1 = remove_event(events, path2, name6); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed$1; + } else { + let added$1 = prepend(next2, added); + let removed$1 = prepend(prev, removed); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed$1; + } + } else if (prev instanceof Event2) { + let name6 = next2.name; + let handler = next2.handler; + let has_changes = !isEqual( + prev.prevent_default, + next2.prevent_default + ) || !isEqual(prev.stop_propagation, next2.stop_propagation) || prev.immediate !== next2.immediate || prev.debounce !== next2.debounce || prev.throttle !== next2.throttle; + let _block; + if (has_changes) { + _block = prepend(next2, added); + } else { + _block = added; + } + let added$1 = _block; + let events$1 = add_event(events, mapper, path2, name6, handler); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } else { + let name6 = next2.name; + let handler = next2.handler; + let added$1 = prepend(next2, added); + let removed$1 = prepend(prev, removed); + let events$1 = add_event(events, mapper, path2, name6, handler); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed$1; + } + } else if (next2 instanceof Event2) { + let name6 = next2.name; + let handler = next2.handler; + let added$1 = prepend(next2, added); + let events$1 = add_event(events, mapper, path2, name6, handler); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } else { + let added$1 = prepend(next2, added); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } + } + } +} +function do_diff(loop$old, loop$old_keyed, loop$new, loop$new_keyed, loop$moved, loop$moved_offset, loop$removed, loop$node_index, loop$patch_index, loop$path, loop$changes, loop$children, loop$mapper, loop$events) { + while (true) { + let old = loop$old; + let old_keyed = loop$old_keyed; + let new$9 = loop$new; + let new_keyed = loop$new_keyed; + let moved = loop$moved; + let moved_offset = loop$moved_offset; + let removed = loop$removed; + let node_index = loop$node_index; + let patch_index = loop$patch_index; + let path2 = loop$path; + let changes = loop$changes; + let children = loop$children; + let mapper = loop$mapper; + let events = loop$events; + if (new$9 instanceof Empty) { + if (old instanceof Empty) { + return new Diff( + new Patch(patch_index, removed, changes, children), + events + ); + } else { + let prev = old.head; + let old$1 = old.tail; + let _block; + let $ = prev.key === "" || !contains(moved, prev.key); + if ($) { + _block = removed + advance(prev); + } else { + _block = removed; + } + let removed$1 = _block; + let events$1 = remove_child(events, path2, node_index, prev); + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$9; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed$1; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes; + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else if (old instanceof Empty) { + let events$1 = add_children( + events, + mapper, + path2, + node_index, + new$9 + ); + let insert5 = insert4(new$9, node_index - moved_offset); + let changes$1 = prepend(insert5, changes); + return new Diff( + new Patch(patch_index, removed, changes$1, children), + events$1 + ); + } else { + let next2 = new$9.head; + let prev = old.head; + if (prev.key !== next2.key) { + let new_remaining = new$9.tail; + let old_remaining = old.tail; + let next_did_exist = get(old_keyed, next2.key); + let prev_does_exist = get(new_keyed, prev.key); + let prev_has_moved = contains(moved, prev.key); + if (next_did_exist instanceof Ok) { + if (prev_does_exist instanceof Ok) { + if (prev_has_moved) { + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new$9; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - advance(prev); + loop$removed = removed; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes; + loop$children = children; + loop$mapper = mapper; + loop$events = events; + } else { + let match = next_did_exist[0]; + let count = advance(next2); + let before = node_index - moved_offset; + let move2 = move(next2.key, before, count); + let changes$1 = prepend(move2, changes); + let moved$1 = insert2(moved, next2.key); + let moved_offset$1 = moved_offset + count; + loop$old = prepend(match, old); + loop$old_keyed = old_keyed; + loop$new = new$9; + loop$new_keyed = new_keyed; + loop$moved = moved$1; + loop$moved_offset = moved_offset$1; + loop$removed = removed; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes$1; + loop$children = children; + loop$mapper = mapper; + loop$events = events; + } + } else { + let count = advance(prev); + let moved_offset$1 = moved_offset - count; + let events$1 = remove_child(events, path2, node_index, prev); + let remove3 = remove_key(prev.key, count); + let changes$1 = prepend(remove3, changes); + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new$9; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset$1; + loop$removed = removed; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes$1; + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else if (prev_does_exist instanceof Ok) { + let before = node_index - moved_offset; + let count = advance(next2); + let events$1 = add_child( + events, + mapper, + path2, + node_index, + next2 + ); + let insert5 = insert4(toList([next2]), before); + let changes$1 = prepend(insert5, changes); + loop$old = old; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset + count; + loop$removed = removed; + loop$node_index = node_index + count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes$1; + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } else { + let prev_count = advance(prev); + let next_count = advance(next2); + let change = replace2( + node_index - moved_offset, + prev_count, + next2 + ); + let _block; + let _pipe = events; + let _pipe$1 = remove_child(_pipe, path2, node_index, prev); + _block = add_child(_pipe$1, mapper, path2, node_index, next2); + let events$1 = _block; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - prev_count + next_count; + loop$removed = removed; + loop$node_index = node_index + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = prepend(change, changes); + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else { + let $ = old.head; + if ($ instanceof Fragment) { + let $1 = new$9.head; + if ($1 instanceof Fragment) { + let next$1 = $1; + let new$1 = new$9.tail; + let prev$1 = $; + let old$1 = old.tail; + let node_index$1 = node_index + 1; + let prev_count = prev$1.children_count; + let next_count = next$1.children_count; + let composed_mapper = compose_mapper(mapper, next$1.mapper); + let child2 = do_diff( + prev$1.children, + prev$1.keyed_children, + next$1.children, + next$1.keyed_children, + empty_set(), + moved_offset, + 0, + node_index$1, + -1, + path2, + empty_list, + children, + composed_mapper, + events + ); + let _block; + let $2 = child2.patch.removed > 0; + if ($2) { + let remove_from = node_index$1 + next_count - moved_offset; + let patch = remove2(remove_from, child2.patch.removed); + _block = append( + child2.patch.changes, + prepend(patch, changes) + ); + } else { + _block = append(child2.patch.changes, changes); + } + let changes$1 = _block; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset + next_count - prev_count; + loop$removed = removed; + loop$node_index = node_index$1 + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes$1; + loop$children = child2.patch.children; + loop$mapper = mapper; + loop$events = child2.events; + } else { + let next$1 = $1; + let new_remaining = new$9.tail; + let prev$1 = $; + let old_remaining = old.tail; + let prev_count = advance(prev$1); + let next_count = advance(next$1); + let change = replace2( + node_index - moved_offset, + prev_count, + next$1 + ); + let _block; + let _pipe = events; + let _pipe$1 = remove_child(_pipe, path2, node_index, prev$1); + _block = add_child( + _pipe$1, + mapper, + path2, + node_index, + next$1 + ); + let events$1 = _block; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - prev_count + next_count; + loop$removed = removed; + loop$node_index = node_index + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = prepend(change, changes); + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else if ($ instanceof Element2) { + let $1 = new$9.head; + if ($1 instanceof Element2) { + let next$1 = $1; + let prev$1 = $; + if (prev$1.namespace === next$1.namespace && prev$1.tag === next$1.tag) { + let new$1 = new$9.tail; + let old$1 = old.tail; + let composed_mapper = compose_mapper( + mapper, + next$1.mapper + ); + let child_path = add3(path2, node_index, next$1.key); + let controlled = is_controlled( + events, + next$1.namespace, + next$1.tag, + child_path + ); + let $2 = diff_attributes( + controlled, + child_path, + composed_mapper, + events, + prev$1.attributes, + next$1.attributes, + empty_list, + empty_list + ); + let added_attrs = $2.added; + let removed_attrs = $2.removed; + let events$1 = $2.events; + let _block; + if (removed_attrs instanceof Empty) { + if (added_attrs instanceof Empty) { + _block = empty_list; + } else { + _block = toList([update(added_attrs, removed_attrs)]); + } + } else { + _block = toList([update(added_attrs, removed_attrs)]); + } + let initial_child_changes = _block; + let child2 = do_diff( + prev$1.children, + prev$1.keyed_children, + next$1.children, + next$1.keyed_children, + empty_set(), + 0, + 0, + 0, + node_index, + child_path, + initial_child_changes, + empty_list, + composed_mapper, + events$1 + ); + let _block$1; + let $3 = child2.patch; + let $4 = $3.children; + if ($4 instanceof Empty) { + let $5 = $3.changes; + if ($5 instanceof Empty) { + let $6 = $3.removed; + if ($6 === 0) { + _block$1 = children; + } else { + _block$1 = prepend(child2.patch, children); + } + } else { + _block$1 = prepend(child2.patch, children); + } + } else { + _block$1 = prepend(child2.patch, children); + } + let children$1 = _block$1; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes; + loop$children = children$1; + loop$mapper = mapper; + loop$events = child2.events; + } else { + let next$2 = $1; + let new_remaining = new$9.tail; + let prev$2 = $; + let old_remaining = old.tail; + let prev_count = advance(prev$2); + let next_count = advance(next$2); + let change = replace2( + node_index - moved_offset, + prev_count, + next$2 + ); + let _block; + let _pipe = events; + let _pipe$1 = remove_child( + _pipe, + path2, + node_index, + prev$2 + ); + _block = add_child( + _pipe$1, + mapper, + path2, + node_index, + next$2 + ); + let events$1 = _block; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - prev_count + next_count; + loop$removed = removed; + loop$node_index = node_index + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = prepend(change, changes); + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else { + let next$1 = $1; + let new_remaining = new$9.tail; + let prev$1 = $; + let old_remaining = old.tail; + let prev_count = advance(prev$1); + let next_count = advance(next$1); + let change = replace2( + node_index - moved_offset, + prev_count, + next$1 + ); + let _block; + let _pipe = events; + let _pipe$1 = remove_child(_pipe, path2, node_index, prev$1); + _block = add_child( + _pipe$1, + mapper, + path2, + node_index, + next$1 + ); + let events$1 = _block; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - prev_count + next_count; + loop$removed = removed; + loop$node_index = node_index + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = prepend(change, changes); + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else if ($ instanceof Text) { + let $1 = new$9.head; + if ($1 instanceof Text) { + let next$1 = $1; + let prev$1 = $; + if (prev$1.content === next$1.content) { + let new$1 = new$9.tail; + let old$1 = old.tail; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes; + loop$children = children; + loop$mapper = mapper; + loop$events = events; + } else { + let next$2 = $1; + let new$1 = new$9.tail; + let old$1 = old.tail; + let child2 = new$5( + node_index, + 0, + toList([replace_text(next$2.content)]), + empty_list + ); + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes; + loop$children = prepend(child2, children); + loop$mapper = mapper; + loop$events = events; + } + } else { + let next$1 = $1; + let new_remaining = new$9.tail; + let prev$1 = $; + let old_remaining = old.tail; + let prev_count = advance(prev$1); + let next_count = advance(next$1); + let change = replace2( + node_index - moved_offset, + prev_count, + next$1 + ); + let _block; + let _pipe = events; + let _pipe$1 = remove_child(_pipe, path2, node_index, prev$1); + _block = add_child( + _pipe$1, + mapper, + path2, + node_index, + next$1 + ); + let events$1 = _block; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - prev_count + next_count; + loop$removed = removed; + loop$node_index = node_index + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = prepend(change, changes); + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else { + let $1 = new$9.head; + if ($1 instanceof UnsafeInnerHtml) { + let next$1 = $1; + let new$1 = new$9.tail; + let prev$1 = $; + let old$1 = old.tail; + let composed_mapper = compose_mapper(mapper, next$1.mapper); + let child_path = add3(path2, node_index, next$1.key); + let $2 = diff_attributes( + false, + child_path, + composed_mapper, + events, + prev$1.attributes, + next$1.attributes, + empty_list, + empty_list + ); + let added_attrs = $2.added; + let removed_attrs = $2.removed; + let events$1 = $2.events; + let _block; + if (removed_attrs instanceof Empty) { + if (added_attrs instanceof Empty) { + _block = empty_list; + } else { + _block = toList([update(added_attrs, removed_attrs)]); + } + } else { + _block = toList([update(added_attrs, removed_attrs)]); + } + let child_changes = _block; + let _block$1; + let $3 = prev$1.inner_html === next$1.inner_html; + if ($3) { + _block$1 = child_changes; + } else { + _block$1 = prepend( + replace_inner_html(next$1.inner_html), + child_changes + ); + } + let child_changes$1 = _block$1; + let _block$2; + if (child_changes$1 instanceof Empty) { + _block$2 = children; + } else { + _block$2 = prepend( + new$5(node_index, 0, child_changes$1, toList([])), + children + ); + } + let children$1 = _block$2; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes; + loop$children = children$1; + loop$mapper = mapper; + loop$events = events$1; + } else { + let next$1 = $1; + let new_remaining = new$9.tail; + let prev$1 = $; + let old_remaining = old.tail; + let prev_count = advance(prev$1); + let next_count = advance(next$1); + let change = replace2( + node_index - moved_offset, + prev_count, + next$1 + ); + let _block; + let _pipe = events; + let _pipe$1 = remove_child(_pipe, path2, node_index, prev$1); + _block = add_child( + _pipe$1, + mapper, + path2, + node_index, + next$1 + ); + let events$1 = _block; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - prev_count + next_count; + loop$removed = removed; + loop$node_index = node_index + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = prepend(change, changes); + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } + } + } + } +} +function diff(events, old, new$9) { + return do_diff( + toList([old]), + empty2(), + toList([new$9]), + empty2(), + empty_set(), + 0, + 0, + 0, + 0, + root2, + empty_list, + empty_list, + identity3, + tick(events) + ); +} + +// build/dev/javascript/lustre/lustre/vdom/reconciler.ffi.mjs +var Reconciler = class { + offset = 0; + #root = null; + #dispatch = () => { + }; + #useServerEvents = false; + #exposeKeys = false; + constructor(root3, dispatch, { useServerEvents = false, exposeKeys = false } = {}) { + this.#root = root3; + this.#dispatch = dispatch; + this.#useServerEvents = useServerEvents; + this.#exposeKeys = exposeKeys; + } + mount(vdom) { + appendChild(this.#root, this.#createChild(this.#root, 0, vdom)); + } + #stack = []; + push(patch) { + const offset = this.offset; + if (offset) { + iterate(patch.changes, (change) => { + switch (change.kind) { + case insert_kind: + case move_kind: + change.before = (change.before | 0) + offset; + break; + case remove_kind: + case replace_kind: + change.from = (change.from | 0) + offset; + break; + } + }); + iterate(patch.children, (child2) => { + child2.index = (child2.index | 0) + offset; + }); + } + this.#stack.push({ node: this.#root, patch }); + this.#reconcile(); + } + // PATCHING ------------------------------------------------------------------ + #reconcile() { + const self = this; + while (self.#stack.length) { + const { node, patch } = self.#stack.pop(); + iterate(patch.changes, (change) => { + switch (change.kind) { + case insert_kind: + self.#insert(node, change.children, change.before); + break; + case move_kind: + self.#move(node, change.key, change.before, change.count); + break; + case remove_key_kind: + self.#removeKey(node, change.key, change.count); + break; + case remove_kind: + self.#remove(node, change.from, change.count); + break; + case replace_kind: + self.#replace(node, change.from, change.count, change.with); + break; + case replace_text_kind: + self.#replaceText(node, change.content); + break; + case replace_inner_html_kind: + self.#replaceInnerHtml(node, change.inner_html); + break; + case update_kind: + self.#update(node, change.added, change.removed); + break; + } + }); + if (patch.removed) { + self.#remove( + node, + node.childNodes.length - patch.removed, + patch.removed + ); + } + let lastIndex = -1; + let lastChild = null; + iterate(patch.children, (child2) => { + const index4 = child2.index | 0; + const next2 = lastChild && lastIndex - index4 === 1 ? lastChild.previousSibling : childAt(node, index4); + self.#stack.push({ node: next2, patch: child2 }); + lastChild = next2; + lastIndex = index4; + }); + } + } + // CHANGES ------------------------------------------------------------------- + #insert(node, children, before) { + const fragment4 = createDocumentFragment(); + let childIndex = before | 0; + iterate(children, (child2) => { + const el = this.#createChild(node, childIndex, child2); + appendChild(fragment4, el); + childIndex += advance(child2); + }); + insertBefore(node, fragment4, childAt(node, before)); + } + #move(node, key, before, count) { + let el = getKeyedChild(node, key); + const beforeEl = childAt(node, before); + for (let i = 0; i < count && el !== null; ++i) { + const next2 = el.nextSibling; + if (SUPPORTS_MOVE_BEFORE) { + node.moveBefore(el, beforeEl); + } else { + insertBefore(node, el, beforeEl); + } + el = next2; + } + } + #removeKey(node, key, count) { + this.#removeFromChild(node, getKeyedChild(node, key), count); + } + #remove(node, from2, count) { + this.#removeFromChild(node, childAt(node, from2), count); + } + #removeFromChild(parent, child2, count) { + while (count-- > 0 && child2 !== null) { + const next2 = child2.nextSibling; + const key = child2[meta].key; + if (key) { + parent[meta].keyedChildren.delete(key); + } + for (const [_, { timeout }] of child2[meta].debouncers ?? []) { + clearTimeout(timeout); + } + parent.removeChild(child2); + child2 = next2; + } + } + #replace(parent, from2, count, child2) { + this.#remove(parent, from2, count); + const el = this.#createChild(parent, from2, child2); + insertBefore(parent, el, childAt(parent, from2)); + } + #replaceText(node, content) { + node.data = content ?? ""; + } + #replaceInnerHtml(node, inner_html) { + node.innerHTML = inner_html ?? ""; + } + #update(node, added, removed) { + iterate(removed, (attribute5) => { + const name6 = attribute5.name; + if (node[meta].handlers.has(name6)) { + node.removeEventListener(name6, handleEvent); + node[meta].handlers.delete(name6); + if (node[meta].throttles.has(name6)) { + node[meta].throttles.delete(name6); + } + if (node[meta].debouncers.has(name6)) { + clearTimeout(node[meta].debouncers.get(name6).timeout); + node[meta].debouncers.delete(name6); + } + } else { + node.removeAttribute(name6); + SYNCED_ATTRIBUTES[name6]?.removed?.(node, name6); + } + }); + iterate(added, (attribute5) => { + this.#createAttribute(node, attribute5); + }); + } + // CONSTRUCTORS -------------------------------------------------------------- + #createChild(parent, index4, vnode) { + switch (vnode.kind) { + case element_kind: { + const node = createChildElement(parent, index4, vnode); + this.#createAttributes(node, vnode); + this.#insert(node, vnode.children); + return node; + } + case text_kind: { + return createChildText(parent, index4, vnode); + } + case fragment_kind: { + const node = createDocumentFragment(); + const head = createChildText(parent, index4, vnode); + appendChild(node, head); + let childIndex = index4 + 1; + iterate(vnode.children, (child2) => { + appendChild(node, this.#createChild(parent, childIndex, child2)); + childIndex += advance(child2); + }); + return node; + } + case unsafe_inner_html_kind: { + const node = createChildElement(parent, index4, vnode); + this.#createAttributes(node, vnode); + this.#replaceInnerHtml(node, vnode.inner_html); + return node; + } + } + } + #createAttributes(node, { key, attributes }) { + if (this.#exposeKeys && key) { + node.setAttribute("data-lustre-key", key); + } + iterate(attributes, (attribute5) => this.#createAttribute(node, attribute5)); + } + #createAttribute(node, attribute5) { + const { debouncers, handlers, throttles } = node[meta]; + const { + kind, + name: name6, + value: value2, + prevent_default: prevent, + stop_propagation: stop, + immediate: immediate2, + include, + debounce: debounceDelay, + throttle: throttleDelay + } = attribute5; + switch (kind) { + case attribute_kind: { + const valueOrDefault = value2 ?? ""; + if (name6 === "virtual:defaultValue") { + node.defaultValue = valueOrDefault; + return; + } + if (valueOrDefault !== node.getAttribute(name6)) { + node.setAttribute(name6, valueOrDefault); + } + SYNCED_ATTRIBUTES[name6]?.added?.(node, value2); + break; + } + case property_kind: + node[name6] = value2; + break; + case event_kind: { + if (handlers.has(name6)) { + node.removeEventListener(name6, handleEvent); + } + node.addEventListener(name6, handleEvent, { + passive: prevent.kind === never_kind + }); + if (throttleDelay > 0) { + const throttle = throttles.get(name6) ?? {}; + throttle.delay = throttleDelay; + throttles.set(name6, throttle); + } else { + throttles.delete(name6); + } + if (debounceDelay > 0) { + const debounce = debouncers.get(name6) ?? {}; + debounce.delay = debounceDelay; + debouncers.set(name6, debounce); + } else { + clearTimeout(debouncers.get(name6)?.timeout); + debouncers.delete(name6); + } + handlers.set(name6, (event4) => { + if (prevent.kind === always_kind) + event4.preventDefault(); + if (stop.kind === always_kind) + event4.stopPropagation(); + const type = event4.type; + const path2 = event4.currentTarget[meta].path; + const data = this.#useServerEvents ? createServerEvent(event4, include ?? []) : event4; + const throttle = throttles.get(type); + if (throttle) { + const now = Date.now(); + const last = throttle.last || 0; + if (now > last + throttle.delay) { + throttle.last = now; + throttle.lastEvent = event4; + this.#dispatch(data, path2, type, immediate2); + } + } + const debounce = debouncers.get(type); + if (debounce) { + clearTimeout(debounce.timeout); + debounce.timeout = setTimeout(() => { + if (event4 === throttles.get(type)?.lastEvent) + return; + this.#dispatch(data, path2, type, immediate2); + }, debounce.delay); + } + if (!throttle && !debounce) { + this.#dispatch(data, path2, type, immediate2); + } + }); + break; + } + } + } +}; +var iterate = (list4, callback) => { + if (Array.isArray(list4)) { + for (let i = 0; i < list4.length; i++) { + callback(list4[i]); + } + } else if (list4) { + for (list4; list4.tail; list4 = list4.tail) { + callback(list4.head); + } + } +}; +var appendChild = (node, child2) => node.appendChild(child2); +var insertBefore = (parent, node, referenceNode) => parent.insertBefore(node, referenceNode ?? null); +var createChildElement = (parent, index4, { key, tag, namespace: namespace2 }) => { + const node = document2().createElementNS(namespace2 || NAMESPACE_HTML, tag); + initialiseMetadata(parent, node, index4, key); + return node; +}; +var createChildText = (parent, index4, { key, content }) => { + const node = document2().createTextNode(content ?? ""); + initialiseMetadata(parent, node, index4, key); + return node; +}; +var createDocumentFragment = () => document2().createDocumentFragment(); +var childAt = (node, at) => node.childNodes[at | 0]; +var meta = Symbol("lustre"); +var initialiseMetadata = (parent, node, index4 = 0, key = "") => { + const segment = `${key || index4}`; + switch (node.nodeType) { + case ELEMENT_NODE: + case DOCUMENT_FRAGMENT_NODE: + node[meta] = { + key, + path: segment, + keyedChildren: /* @__PURE__ */ new Map(), + handlers: /* @__PURE__ */ new Map(), + throttles: /* @__PURE__ */ new Map(), + debouncers: /* @__PURE__ */ new Map() + }; + break; + case TEXT_NODE: + node[meta] = { key }; + break; + } + if (parent && parent[meta] && key) { + parent[meta].keyedChildren.set(key, new WeakRef(node)); + } + if (parent && parent[meta] && parent[meta].path) { + node[meta].path = `${parent[meta].path}${separator_element}${segment}`; + } +}; +var getKeyedChild = (node, key) => node[meta].keyedChildren.get(key).deref(); +var handleEvent = (event4) => { + const target = event4.currentTarget; + const handler = target[meta].handlers.get(event4.type); + if (event4.type === "submit") { + event4.detail ??= {}; + event4.detail.formData = [...new FormData(event4.target).entries()]; + } + handler(event4); +}; +var createServerEvent = (event4, include = []) => { + const data = {}; + if (event4.type === "input" || event4.type === "change") { + include.push("target.value"); + } + if (event4.type === "submit") { + include.push("detail.formData"); + } + for (const property3 of include) { + const path2 = property3.split("."); + for (let i = 0, input2 = event4, output = data; i < path2.length; i++) { + if (i === path2.length - 1) { + output[path2[i]] = input2[path2[i]]; + break; + } + output = output[path2[i]] ??= {}; + input2 = input2[path2[i]]; + } + } + return data; +}; +var syncedBooleanAttribute = (name6) => { + return { + added(node) { + node[name6] = true; + }, + removed(node) { + node[name6] = false; + } + }; +}; +var syncedAttribute = (name6) => { + return { + added(node, value2) { + node[name6] = value2; + } + }; +}; +var SYNCED_ATTRIBUTES = { + checked: syncedBooleanAttribute("checked"), + selected: syncedBooleanAttribute("selected"), + value: syncedAttribute("value"), + autofocus: { + added(node) { + queueMicrotask(() => node.focus?.()); + } + }, + autoplay: { + added(node) { + try { + node.play?.(); + } catch (e) { + console.error(e); + } + } + } +}; + +// build/dev/javascript/lustre/lustre/vdom/virtualise.ffi.mjs +var virtualise = (root3) => { + const vdom = virtualiseNode(null, root3, ""); + if (vdom === null || vdom.children instanceof Empty) { + const empty3 = emptyTextNode(root3); + root3.appendChild(empty3); + return none2(); + } else if (vdom.children instanceof NonEmpty && vdom.children.tail instanceof Empty) { + return vdom.children.head; + } else { + const head = emptyTextNode(root3); + root3.insertBefore(head, root3.firstChild); + return fragment2(vdom.children); + } +}; +var emptyTextNode = (parent) => { + const node = document2().createTextNode(""); + initialiseMetadata(parent, node); + return node; +}; +var virtualiseNode = (parent, node, index4) => { + switch (node.nodeType) { + case ELEMENT_NODE: { + const key = node.getAttribute("data-lustre-key"); + initialiseMetadata(parent, node, index4, key); + if (key) { + node.removeAttribute("data-lustre-key"); + } + const tag = node.localName; + const namespace2 = node.namespaceURI; + const isHtmlElement = !namespace2 || namespace2 === NAMESPACE_HTML; + if (isHtmlElement && INPUT_ELEMENTS.includes(tag)) { + virtualiseInputEvents(tag, node); + } + const attributes = virtualiseAttributes(node); + const children = virtualiseChildNodes(node); + const vnode = isHtmlElement ? element2(tag, attributes, children) : namespaced(namespace2, tag, attributes, children); + return key ? to_keyed(key, vnode) : vnode; + } + case TEXT_NODE: + initialiseMetadata(parent, node, index4); + return node.data ? text2(node.data) : null; + case DOCUMENT_FRAGMENT_NODE: + initialiseMetadata(parent, node, index4); + return node.childNodes.length > 0 ? fragment2(virtualiseChildNodes(node)) : null; + default: + return null; + } +}; +var INPUT_ELEMENTS = ["input", "select", "textarea"]; +var virtualiseInputEvents = (tag, node) => { + const value2 = node.value; + const checked = node.checked; + if (tag === "input" && node.type === "checkbox" && !checked) + return; + if (tag === "input" && node.type === "radio" && !checked) + return; + if (node.type !== "checkbox" && node.type !== "radio" && !value2) + return; + queueMicrotask(() => { + node.value = value2; + node.checked = checked; + node.dispatchEvent(new Event("input", { bubbles: true })); + node.dispatchEvent(new Event("change", { bubbles: true })); + if (document2().activeElement !== node) { + node.dispatchEvent(new Event("blur", { bubbles: true })); + } + }); +}; +var virtualiseChildNodes = (node) => { + let children = null; + let index4 = 0; + let child2 = node.firstChild; + let ptr = null; + while (child2) { + const vnode = virtualiseNode(node, child2, index4); + const next2 = child2.nextSibling; + if (vnode) { + const list_node = new NonEmpty(vnode, null); + if (ptr) { + ptr = ptr.tail = list_node; + } else { + ptr = children = list_node; + } + index4 += 1; + } else { + node.removeChild(child2); + } + child2 = next2; + } + if (!ptr) + return empty_list; + ptr.tail = empty_list; + return children; +}; +var virtualiseAttributes = (node) => { + let index4 = node.attributes.length; + let attributes = empty_list; + while (index4-- > 0) { + attributes = new NonEmpty( + virtualiseAttribute(node.attributes[index4]), + attributes + ); + } + return attributes; +}; +var virtualiseAttribute = (attr) => { + const name6 = attr.localName; + const value2 = attr.value; + return attribute2(name6, value2); +}; + +// build/dev/javascript/lustre/lustre/runtime/client/runtime.ffi.mjs +var is_browser = () => !!document2(); +var Runtime = class { + constructor(root3, [model, effects], view5, update6) { + this.root = root3; + this.#model = model; + this.#view = view5; + this.#update = update6; + this.#reconciler = new Reconciler(this.root, (event4, path2, name6) => { + const [events, result] = handle(this.#events, path2, name6, event4); + this.#events = events; + if (result.isOk()) { + const handler = result[0]; + if (handler.stop_propagation) + event4.stopPropagation(); + if (handler.prevent_default) + event4.preventDefault(); + this.dispatch(handler.message, false); + } + }); + this.#vdom = virtualise(this.root); + this.#events = new$3(); + this.#shouldFlush = true; + this.#tick(effects); + } + // PUBLIC API ---------------------------------------------------------------- + root = null; + set offset(offset) { + this.#reconciler.offset = offset; + } + dispatch(msg, immediate2 = false) { + this.#shouldFlush ||= immediate2; + if (this.#shouldQueue) { + this.#queue.push(msg); + } else { + const [model, effects] = this.#update(this.#model, msg); + this.#model = model; + this.#tick(effects); + } + } + emit(event4, data) { + const target = this.root.host ?? this.root; + target.dispatchEvent( + new CustomEvent(event4, { + detail: data, + bubbles: true, + composed: true + }) + ); + } + // PRIVATE API --------------------------------------------------------------- + #model; + #view; + #update; + #vdom; + #events; + #reconciler; + #shouldQueue = false; + #queue = []; + #beforePaint = empty_list; + #afterPaint = empty_list; + #renderTimer = null; + #shouldFlush = false; + #actions = { + dispatch: (msg, immediate2) => this.dispatch(msg, immediate2), + emit: (event4, data) => this.emit(event4, data), + select: () => { + }, + root: () => this.root + }; + // A `#tick` is where we process effects and trigger any synchronous updates. + // Once a tick has been processed a render will be scheduled if none is already. + // p0 + #tick(effects) { + this.#shouldQueue = true; + while (true) { + for (let list4 = effects.synchronous; list4.tail; list4 = list4.tail) { + list4.head(this.#actions); + } + this.#beforePaint = listAppend(this.#beforePaint, effects.before_paint); + this.#afterPaint = listAppend(this.#afterPaint, effects.after_paint); + if (!this.#queue.length) + break; + [this.#model, effects] = this.#update(this.#model, this.#queue.shift()); + } + this.#shouldQueue = false; + if (this.#shouldFlush) { + cancelAnimationFrame(this.#renderTimer); + this.#render(); + } else if (!this.#renderTimer) { + this.#renderTimer = requestAnimationFrame(() => { + this.#render(); + }); + } + } + #render() { + this.#shouldFlush = false; + this.#renderTimer = null; + const next2 = this.#view(this.#model); + const { patch, events } = diff(this.#events, this.#vdom, next2); + this.#events = events; + this.#vdom = next2; + this.#reconciler.push(patch); + if (this.#beforePaint instanceof NonEmpty) { + const effects = makeEffect(this.#beforePaint); + this.#beforePaint = empty_list; + queueMicrotask(() => { + this.#shouldFlush = true; + this.#tick(effects); + }); + } + if (this.#afterPaint instanceof NonEmpty) { + const effects = makeEffect(this.#afterPaint); + this.#afterPaint = empty_list; + requestAnimationFrame(() => { + this.#shouldFlush = true; + this.#tick(effects); + }); + } + } +}; +function makeEffect(synchronous) { + return { + synchronous, + after_paint: empty_list, + before_paint: empty_list + }; +} +function listAppend(a, b) { + if (a instanceof Empty) { + return b; + } else if (b instanceof Empty) { + return a; + } else { + return append(a, b); + } +} +var copiedStyleSheets = /* @__PURE__ */ new WeakMap(); +async function adoptStylesheets(shadowRoot) { + const pendingParentStylesheets = []; + for (const node of document2().querySelectorAll( + "link[rel=stylesheet], style" + )) { + if (node.sheet) + continue; + pendingParentStylesheets.push( + new Promise((resolve, reject) => { + node.addEventListener("load", resolve); + node.addEventListener("error", reject); + }) + ); + } + await Promise.allSettled(pendingParentStylesheets); + if (!shadowRoot.host.isConnected) { + return []; + } + shadowRoot.adoptedStyleSheets = shadowRoot.host.getRootNode().adoptedStyleSheets; + const pending = []; + for (const sheet of document2().styleSheets) { + try { + shadowRoot.adoptedStyleSheets.push(sheet); + } catch { + try { + let copiedSheet = copiedStyleSheets.get(sheet); + if (!copiedSheet) { + copiedSheet = new CSSStyleSheet(); + for (const rule of sheet.cssRules) { + copiedSheet.insertRule(rule.cssText, copiedSheet.cssRules.length); + } + copiedStyleSheets.set(sheet, copiedSheet); + } + shadowRoot.adoptedStyleSheets.push(copiedSheet); + } catch { + const node = sheet.ownerNode.cloneNode(); + shadowRoot.prepend(node); + pending.push(node); + } + } + } + return pending; +} + +// build/dev/javascript/lustre/lustre/runtime/server/runtime.mjs +var EffectDispatchedMessage = class extends CustomType { + constructor(message) { + super(); + this.message = message; + } +}; +var EffectEmitEvent = class extends CustomType { + constructor(name6, data) { + super(); + this.name = name6; + this.data = data; + } +}; +var SystemRequestedShutdown = class extends CustomType { +}; + +// build/dev/javascript/lustre/lustre/runtime/client/component.ffi.mjs +var make_component = ({ init: init5, update: update6, view: view5, config }, name6) => { + if (!is_browser()) + return new Error(new NotABrowser()); + if (!name6.includes("-")) + return new Error(new BadComponentName(name6)); + if (customElements.get(name6)) { + return new Error(new ComponentAlreadyRegistered(name6)); + } + const [model, effects] = init5(void 0); + const observedAttributes = config.attributes.entries().map(([name7]) => name7); + const component2 = class Component extends HTMLElement { + static get observedAttributes() { + return observedAttributes; + } + static formAssociated = config.is_form_associated; + #runtime; + #adoptedStyleNodes = []; + #shadowRoot; + constructor() { + super(); + this.internals = this.attachInternals(); + if (!this.internals.shadowRoot) { + this.#shadowRoot = this.attachShadow({ + mode: config.open_shadow_root ? "open" : "closed" + }); + } else { + this.#shadowRoot = this.internals.shadowRoot; + } + if (config.adopt_styles) { + this.#adoptStyleSheets(); + } + this.#runtime = new Runtime( + this.#shadowRoot, + [model, effects], + view5, + update6 + ); + } + adoptedCallback() { + if (config.adopt_styles) { + this.#adoptStyleSheets(); + } + } + attributeChangedCallback(name7, _, value2) { + const decoded = config.attributes.get(name7)(value2); + if (decoded.constructor === Ok) { + this.dispatch(decoded[0]); + } + } + formResetCallback() { + if (config.on_form_reset instanceof Some) { + this.dispatch(config.on_form_reset[0]); + } + } + formStateRestoreCallback(state, reason) { + switch (reason) { + case "restore": + if (config.on_form_restore instanceof Some) { + this.dispatch(config.on_form_restore[0](state)); + } + break; + case "autocomplete": + if (config.on_form_populate instanceof Some) { + this.dispatch(config.on_form_autofill[0](state)); + } + break; + } + } + send(message) { + switch (message.constructor) { + case EffectDispatchedMessage: { + this.dispatch(message.message, false); + break; + } + case EffectEmitEvent: { + this.emit(message.name, message.data); + break; + } + case SystemRequestedShutdown: + break; + } + } + dispatch(msg, immediate2 = false) { + this.#runtime.dispatch(msg, immediate2); + } + emit(event4, data) { + this.#runtime.emit(event4, data); + } + async #adoptStyleSheets() { + while (this.#adoptedStyleNodes.length) { + this.#adoptedStyleNodes.pop().remove(); + this.shadowRoot.firstChild.remove(); + } + this.#adoptedStyleNodes = await adoptStylesheets(this.#shadowRoot); + this.#runtime.offset = this.#adoptedStyleNodes.length; + } + }; + config.properties.forEach((decoder, name7) => { + Object.defineProperty(component2.prototype, name7, { + get() { + return this[`_${name7}`]; + }, + set(value2) { + this[`_${name7}`] = value2; + const decoded = run(value2, decoder); + if (decoded.constructor === Ok) { + this.dispatch(decoded[0]); + } + } + }); + }); + customElements.define(name6, component2); + return new Ok(void 0); +}; +var set_pseudo_state = (root3, value2) => { + if (!is_browser()) + return; + if (root3 instanceof ShadowRoot) { + root3.host.internals.states.add(value2); + } +}; +var remove_pseudo_state = (root3, value2) => { + if (!is_browser()) + return; + if (root3 instanceof ShadowRoot) { + root3.host.internals.states.delete(value2); + } +}; + +// build/dev/javascript/lustre/lustre/component.mjs +var Config2 = class extends CustomType { + constructor(open_shadow_root, adopt_styles2, attributes, properties, is_form_associated, on_form_autofill, on_form_reset, on_form_restore) { + super(); + this.open_shadow_root = open_shadow_root; + this.adopt_styles = adopt_styles2; + this.attributes = attributes; + this.properties = properties; + this.is_form_associated = is_form_associated; + this.on_form_autofill = on_form_autofill; + this.on_form_reset = on_form_reset; + this.on_form_restore = on_form_restore; + } +}; +var Option = class extends CustomType { + constructor(apply) { + super(); + this.apply = apply; + } +}; +function new$6(options) { + let init5 = new Config2( + false, + true, + empty_dict(), + empty_dict(), + false, + option_none, + option_none, + option_none + ); + return fold2( + options, + init5, + (config, option) => { + return option.apply(config); + } + ); +} +function on_attribute_change(name6, decoder) { + return new Option( + (config) => { + let attributes = insert(config.attributes, name6, decoder); + let _record = config; + return new Config2( + _record.open_shadow_root, + _record.adopt_styles, + attributes, + _record.properties, + _record.is_form_associated, + _record.on_form_autofill, + _record.on_form_reset, + _record.on_form_restore + ); + } + ); +} +function adopt_styles(adopt) { + return new Option( + (config) => { + let _record = config; + return new Config2( + _record.open_shadow_root, + adopt, + _record.attributes, + _record.properties, + _record.is_form_associated, + _record.on_form_autofill, + _record.on_form_reset, + _record.on_form_restore + ); + } + ); +} +function set_pseudo_state2(value2) { + return before_paint( + (_, root3) => { + return set_pseudo_state(root3, value2); + } + ); +} +function remove_pseudo_state2(value2) { + return before_paint( + (_, root3) => { + return remove_pseudo_state(root3, value2); + } + ); +} + +// build/dev/javascript/lustre/lustre/runtime/client/spa.ffi.mjs +var Spa = class _Spa { + static start({ init: init5, update: update6, view: view5 }, selector, flags) { + if (!is_browser()) + return new Error(new NotABrowser()); + const root3 = selector instanceof HTMLElement ? selector : document2().querySelector(selector); + if (!root3) + return new Error(new ElementNotFound(selector)); + return new Ok(new _Spa(root3, init5(flags), update6, view5)); + } + #runtime; + constructor(root3, [init5, effects], update6, view5) { + this.#runtime = new Runtime(root3, [init5, effects], view5, update6); + } + send(message) { + switch (message.constructor) { + case EffectDispatchedMessage: { + this.dispatch(message.message, false); + break; + } + case EffectEmitEvent: { + this.emit(message.name, message.data); + break; + } + case SystemRequestedShutdown: + break; + } + } + dispatch(msg, immediate2) { + this.#runtime.dispatch(msg, immediate2); + } + emit(event4, data) { + this.#runtime.emit(event4, data); + } +}; +var start = Spa.start; + +// build/dev/javascript/lustre/lustre.mjs +var App = class extends CustomType { + constructor(init5, update6, view5, config) { + super(); + this.init = init5; + this.update = update6; + this.view = view5; + this.config = config; + } +}; +var BadComponentName = class extends CustomType { + constructor(name6) { + super(); + this.name = name6; + } +}; +var ComponentAlreadyRegistered = class extends CustomType { + constructor(name6) { + super(); + this.name = name6; + } +}; +var ElementNotFound = class extends CustomType { + constructor(selector) { + super(); + this.selector = selector; + } +}; +var NotABrowser = class extends CustomType { +}; +function component(init5, update6, view5, options) { + return new App(init5, update6, view5, new$6(options)); +} + +// build/dev/javascript/gleam_stdlib/gleam/pair.mjs +function first2(pair) { + let a = pair[0]; + return a; +} +function second(pair) { + let a = pair[1]; + return a; +} + +// build/dev/javascript/lustre/lustre/event.mjs +function emit(event4, data) { + return event2(event4, data); +} +function is_immediate_event(name6) { + if (name6 === "input") { + return true; + } else if (name6 === "change") { + return true; + } else if (name6 === "focus") { + return true; + } else if (name6 === "focusin") { + return true; + } else if (name6 === "focusout") { + return true; + } else if (name6 === "blur") { + return true; + } else if (name6 === "select") { + return true; + } else { + return false; + } +} +function on(name6, handler) { + return event( + name6, + map2(handler, (msg) => { + return new Handler(false, false, msg); + }), + empty_list, + never, + never, + is_immediate_event(name6), + 0, + 0 + ); +} +function on_mouse_down(msg) { + return on("mousedown", success(msg)); +} +function on_mouse_over(msg) { + return on("mouseover", success(msg)); +} +function on_input(msg) { + return on( + "input", + subfield( + toList(["target", "value"]), + string2, + (value2) => { + return success(msg(value2)); + } + ) + ); +} +function on_focus(msg) { + return on("focus", success(msg)); +} +function on_blur(msg) { + return on("blur", success(msg)); +} + +// build/dev/javascript/lustre_ui/lustre/ffi/dom.ffi.mjs +var assigned_elements = (slot2) => { + if (!(slot2 instanceof HTMLSlotElement)) + return new Error(void 0); + const elements = slot2.assignedElements(); + return new Ok(List.fromArray(elements)); +}; +var bounding_client_rect = (element7) => { + if (!(element7 instanceof HTMLElement)) + return new Error(void 0); + const rect = element7.getBoundingClientRect(); + return new Ok( + new BoundingClientRect( + rect.top, + rect.right, + rect.bottom, + rect.left, + rect.width, + rect.height + ) + ); +}; +var attribute3 = (element7, name6) => { + if (!(element7 instanceof HTMLElement)) + return new Error(void 0); + if (typeof name6 !== "string") + return new Error(void 0); + const value2 = element7.getAttribute(name6); + if (value2 === null) { + return new Error(void 0); + } else { + return new Ok(value2); + } +}; +var prevent_default = (event4) => { + if (!(event4 instanceof Event)) + return; + event4.preventDefault(); +}; +var find_element = (selector, root3 = document) => { + if (typeof selector !== "string") + return new Error(void 0); + if (!(root3 instanceof Document || root3 instanceof Element)) + return new Error(void 0); + const element7 = root3.querySelector(selector); + if (element7 === null) { + return new Error(void 0); + } else { + return new Ok(element7); + } +}; +var focus = (element7) => { + if (!(element7 instanceof HTMLElement)) + return; + element7.focus(); +}; + +// build/dev/javascript/lustre_ui/lustre/ffi/dom.mjs +var BoundingClientRect = class extends CustomType { + constructor(top, right, bottom, left, width, height) { + super(); + this.top = top; + this.right = right; + this.bottom = bottom; + this.left = left; + this.width = width; + this.height = height; + } +}; +function assigned_elements2(decoder, lenient) { + let lenient_decoder = one_of( + map2(decoder, (var0) => { + return new Ok(var0); + }), + toList([success(new Error(void 0))]) + ); + return new_primitive_decoder( + "HTMLSlotElement.assignedElements()", + (slot2) => { + let $ = assigned_elements(slot2); + if ($ instanceof Ok) { + if (lenient) { + let elements = $[0]; + let _pipe = elements; + let _pipe$1 = try_map( + _pipe, + (_capture) => { + return run(_capture, lenient_decoder); + } + ); + let _pipe$2 = map3( + _pipe$1, + (_capture) => { + return filter_map(_capture, identity3); + } + ); + return replace_error(_pipe$2, toList([])); + } else { + let elements = $[0]; + let _pipe = elements; + let _pipe$1 = try_map( + _pipe, + (_capture) => { + return run(_capture, decoder); + } + ); + return replace_error(_pipe$1, toList([])); + } + } else { + return new Error(toList([])); + } + } + ); +} +function bounding_client_rect2() { + return new_primitive_decoder( + "Element.getBoundingClientRect()", + (element7) => { + let $ = bounding_client_rect(element7); + if ($ instanceof Ok) { + let rect = $[0]; + return new Ok(rect); + } else { + return new Error(new BoundingClientRect(0, 0, 0, 0, 0, 0)); + } + } + ); +} +function attribute4(name6) { + return new_primitive_decoder( + "Element.getAttribute()", + (element7) => { + let $ = attribute3(element7, name6); + if ($ instanceof Ok) { + let value2 = $[0]; + return new Ok(value2); + } else { + return new Error(""); + } + } + ); +} +function prevent_default2(event4) { + return from((_) => { + return prevent_default(event4); + }); +} +function child(selector, zero, decoder) { + return new_primitive_decoder( + "Element.querySelector()", + (element7) => { + let $ = find_element(selector, element7); + if ($ instanceof Ok) { + let child$1 = $[0]; + let _pipe = run(child$1, decoder); + return replace_error(_pipe, zero); + } else { + return new Error(zero); + } + } + ); +} +function focus2(selector) { + return before_paint( + (_, root3) => { + let $ = find_element(selector, root3); + if ($ instanceof Ok) { + let element7 = $[0]; + return focus(element7); + } else { + return void 0; + } + } + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/primitives/collapse.mjs +var Model = class extends CustomType { + constructor(height, expanded2) { + super(); + this.height = height; + this.expanded = expanded2; + } +}; +var ParentChangedContent = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var ParentSetExpanded = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var UserPressedTrigger = class extends CustomType { + constructor($0, event4) { + super(); + this[0] = $0; + this.event = event4; + } +}; +function expanded(is_expanded) { + return attribute2( + "aria-expanded", + (() => { + let _pipe = to_string2(is_expanded); + return lowercase(_pipe); + })() + ); +} +function on_change(handler) { + return on( + "change", + subfield( + toList(["detail", "expanded"]), + bool, + (expanded2) => { + return success(handler(expanded2)); + } + ) + ); +} +function init(_) { + let model = new Model(0, false); + let effect = none(); + return [model, effect]; +} +function update2(model, msg) { + if (msg instanceof ParentChangedContent) { + let height = msg[0]; + return [ + (() => { + let _record = model; + return new Model(height, _record.expanded); + })(), + none() + ]; + } else if (msg instanceof ParentSetExpanded) { + let expanded$1 = msg[0]; + return [ + (() => { + let _record = model; + return new Model(_record.height, expanded$1); + })(), + none() + ]; + } else { + let height = msg[0]; + let event4 = msg.event; + let _block; + let _record = model; + _block = new Model(height, _record.expanded); + let model$1 = _block; + let emit_change = emit( + "change", + object2(toList([["expanded", bool2(!model$1.expanded)]])) + ); + let _block$1; + let $ = model$1.expanded; + if ($) { + _block$1 = emit("collapse", null$()); + } else { + _block$1 = emit("expand", null$()); + } + let emit_expand_collapse = _block$1; + let effect = batch( + toList([emit_change, emit_expand_collapse, prevent_default2(event4)]) + ); + return [model$1, effect]; + } +} +function handle_click() { + return subfield( + toList(["currentTarget", "nextElementSibling", "firstElementChild"]), + assigned_elements2( + (() => { + let _pipe = bounding_client_rect2(); + return map2(_pipe, (rect) => { + return rect.height; + }); + })(), + false + ), + (heights) => { + let height = sum(heights); + return success(new UserPressedTrigger(height, nil())); + } + ); +} +function handle_keydown() { + return then$( + dynamic, + (event4) => { + return field( + "key", + string2, + (key) => { + if (key === "Enter") { + return subfield( + toList([ + "currentTarget", + "nextElementSibling", + "firstElementChild" + ]), + assigned_elements2( + (() => { + let _pipe = bounding_client_rect2(); + return map2(_pipe, (rect) => { + return rect.height; + }); + })(), + false + ), + (heights) => { + let height = sum(heights); + return success(new UserPressedTrigger(height, event4)); + } + ); + } else if (key === " ") { + return subfield( + toList([ + "currentTarget", + "nextElementSibling", + "firstElementChild" + ]), + assigned_elements2( + (() => { + let _pipe = bounding_client_rect2(); + return map2(_pipe, (rect) => { + return rect.height; + }); + })(), + false + ), + (heights) => { + let height = sum(heights); + return success(new UserPressedTrigger(height, event4)); + } + ); + } else { + return failure( + new UserPressedTrigger(0, nil()), + "" + ); + } + } + ); + } + ); +} +function view_trigger() { + return slot( + toList([ + attribute2("part", "collapse-trigger"), + name("trigger"), + on("click", handle_click()), + on("keydown", handle_keydown()) + ]), + toList([]) + ); +} +function handle_slot_change() { + return subfield( + toList(["currentTarget", "nextElementSibling", "firstElementChild"]), + assigned_elements2( + (() => { + let _pipe = bounding_client_rect2(); + return map2(_pipe, (rect) => { + return rect.height; + }); + })(), + false + ), + (heights) => { + let height = sum(heights); + return success(new ParentChangedContent(height)); + } + ); +} +function view_content(height) { + return div( + toList([ + attribute2("part", "collapse-content"), + styles( + toList([["transition-duration", "inherit"], ["height", height]]) + ) + ]), + toList([ + slot( + toList([on("slotchange", handle_slot_change())]), + toList([]) + ) + ]) + ); +} +var name2 = "lustre-ui-collapse"; +function element3(attributes, trigger, content) { + return element2( + name2, + attributes, + toList([ + div(toList([attribute2("slot", "trigger")]), toList([trigger])), + content + ]) + ); +} +function view(model) { + let _block; + let $ = model.expanded; + if ($) { + _block = float_to_string(model.height) + "px"; + } else { + _block = "0px"; + } + let height = _block; + return fragment2(toList([view_trigger(), view_content(height)])); +} +function register() { + let app = component( + init, + update2, + view, + toList([ + adopt_styles(true), + on_attribute_change( + "aria-expanded", + (value2) => { + if (value2 === "true") { + return new Ok(new ParentSetExpanded(true)); + } else if (value2 === "false") { + return new Ok(new ParentSetExpanded(false)); + } else if (value2 === "") { + return new Ok(new ParentSetExpanded(false)); + } else { + return new Error(void 0); + } + } + ) + ]) + ); + return make_component(app, name2); +} + +// build/dev/javascript/lustre_ui/lustre/ui/primitives/popover.mjs +var TopLeft = class extends CustomType { +}; +var TopMiddle = class extends CustomType { +}; +var TopRight = class extends CustomType { +}; +var RightTop = class extends CustomType { +}; +var RightMiddle = class extends CustomType { +}; +var RightBottom = class extends CustomType { +}; +var BottomLeft = class extends CustomType { +}; +var BottomMiddle = class extends CustomType { +}; +var BottomRight = class extends CustomType { +}; +var LeftTop = class extends CustomType { +}; +var LeftMiddle = class extends CustomType { +}; +var WillExpand = class extends CustomType { +}; +var Expanded = class extends CustomType { +}; +var WillCollapse = class extends CustomType { +}; +var Collapsing = class extends CustomType { +}; +var Collapsed = class extends CustomType { +}; +var ParentSetOpen = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var SchedulerDidTick = class extends CustomType { +}; +var TransitionDidEnd = class extends CustomType { +}; +var UserPressedTrigger2 = class extends CustomType { + constructor(event4) { + super(); + this.event = event4; + } +}; +function open(is_open) { + return attribute2( + "aria-expanded", + (() => { + let _pipe = to_string2(is_open); + return lowercase(_pipe); + })() + ); +} +function anchor(direction) { + return attribute2( + "anchor", + (() => { + if (direction instanceof TopLeft) { + return "top-left"; + } else if (direction instanceof TopMiddle) { + return "top-middle"; + } else if (direction instanceof TopRight) { + return "top-right"; + } else if (direction instanceof RightTop) { + return "right-top"; + } else if (direction instanceof RightMiddle) { + return "right-middle"; + } else if (direction instanceof RightBottom) { + return "right-bottom"; + } else if (direction instanceof BottomLeft) { + return "bottom-left"; + } else if (direction instanceof BottomMiddle) { + return "bottom-middle"; + } else if (direction instanceof BottomRight) { + return "bottom-right"; + } else if (direction instanceof LeftTop) { + return "left-top"; + } else if (direction instanceof LeftMiddle) { + return "left-middle"; + } else { + return "left-bottom"; + } + })() + ); +} +function equal_width() { + return attribute2("equal-width", ""); +} +function gap(value2) { + return style("--gap", value2); +} +function on_open(handler) { + return on("open", success(handler)); +} +function on_close(handler) { + return on("close", success(handler)); +} +function init2(_) { + let model = new Collapsed(); + let effect = batch(toList([set_pseudo_state2("collapsed")])); + return [model, effect]; +} +function tick2() { + return after_paint( + (dispatch, _) => { + return dispatch(new SchedulerDidTick()); + } + ); +} +function update3(model, msg) { + let $ = echo(msg, "src/lustre/ui/primitives/popover.gleam", 162); + if ($ instanceof ParentSetOpen) { + let $1 = $[0]; + if ($1) { + if (model instanceof WillCollapse) { + return [ + new WillExpand(), + batch( + toList([tick2(), set_pseudo_state2("will-expand")]) + ) + ]; + } else if (model instanceof Collapsed) { + return [ + new WillExpand(), + batch( + toList([tick2(), set_pseudo_state2("will-expand")]) + ) + ]; + } else { + return [model, none()]; + } + } else if (model instanceof WillExpand) { + return [ + new WillCollapse(), + batch( + toList([tick2(), set_pseudo_state2("will-collapse")]) + ) + ]; + } else if (model instanceof Expanded) { + return [ + new WillCollapse(), + batch( + toList([tick2(), set_pseudo_state2("will-collapse")]) + ) + ]; + } else { + return [model, none()]; + } + } else if ($ instanceof SchedulerDidTick) { + if (model instanceof WillExpand) { + return [new Expanded(), set_pseudo_state2("expanded")]; + } else if (model instanceof WillCollapse) { + return [new Collapsing(), set_pseudo_state2("collapsing")]; + } else { + return [model, none()]; + } + } else if ($ instanceof TransitionDidEnd) { + if (model instanceof Collapsing) { + return [new Collapsed(), set_pseudo_state2("collapsed")]; + } else { + return [model, none()]; + } + } else if (model instanceof WillExpand) { + let event4 = $.event; + return [ + model, + batch( + toList([ + emit("close", null$()), + emit( + "change", + object2(toList([["open", bool2(false)]])) + ), + prevent_default2(event4) + ]) + ) + ]; + } else if (model instanceof Expanded) { + let event4 = $.event; + return [ + model, + batch( + toList([ + emit("close", null$()), + emit( + "change", + object2(toList([["open", bool2(false)]])) + ), + prevent_default2(event4) + ]) + ) + ]; + } else if (model instanceof WillCollapse) { + let event4 = $.event; + return [ + model, + batch( + toList([ + emit("open", null$()), + emit( + "change", + object2(toList([["open", bool2(true)]])) + ), + prevent_default2(event4) + ]) + ) + ]; + } else if (model instanceof Collapsing) { + let event4 = $.event; + return [ + model, + batch( + toList([ + emit("open", null$()), + emit( + "change", + object2(toList([["open", bool2(true)]])) + ), + prevent_default2(event4) + ]) + ) + ]; + } else { + let event4 = $.event; + return [ + model, + batch( + toList([ + emit("open", null$()), + emit( + "change", + object2(toList([["open", bool2(true)]])) + ), + prevent_default2(event4) + ]) + ) + ]; + } +} +function handle_keydown2() { + return then$( + dynamic, + (event4) => { + return field( + "key", + string2, + (key) => { + if (key === "Enter") { + return success(new UserPressedTrigger2(event4)); + } else if (key === " ") { + return success(new UserPressedTrigger2(event4)); + } else { + return failure(new UserPressedTrigger2(event4), ""); + } + } + ); + } + ); +} +function view_trigger2() { + return slot( + toList([ + name("trigger"), + on( + "click", + map2( + dynamic, + (var0) => { + return new UserPressedTrigger2(var0); + } + ) + ), + on("keydown", handle_keydown2()) + ]), + toList([]) + ); +} +function view_popover(model) { + return guard( + isEqual(model, new Collapsed()), + text3(""), + () => { + return div( + toList([ + attribute2("part", "popover-content"), + on("transitionend", success(new TransitionDidEnd())) + ]), + toList([slot(toList([name("popover")]), toList([]))]) + ); + } + ); +} +function view2(model) { + return div( + toList([style("position", "relative")]), + toList([view_trigger2(), view_popover(model)]) + ); +} +var name3 = "lustre-ui-popover"; +function register2() { + let app = component( + init2, + update3, + view2, + toList([ + adopt_styles(true), + on_attribute_change( + "aria-expanded", + (value2) => { + if (value2 === "true") { + return new Ok(new ParentSetOpen(true)); + } else if (value2 === "") { + return new Ok(new ParentSetOpen(true)); + } else if (value2 === "false") { + return new Ok(new ParentSetOpen(false)); + } else { + return new Error(void 0); + } + } + ) + ]) + ); + return make_component(app, name3); +} +function element4(attributes, trigger, content) { + return element2( + name3, + attributes, + toList([ + div(toList([attribute2("slot", "trigger")]), toList([trigger])), + div(toList([attribute2("slot", "popover")]), toList([content])) + ]) + ); +} +function echo(value2, file, line) { + const grey = "\x1B[90m"; + const reset_color = "\x1B[39m"; + const file_line = `${file}:${line}`; + const string_value = echo$inspect(value2); + if (globalThis.process?.stderr?.write) { + const string5 = `${grey}${file_line}${reset_color} +${string_value} +`; + process.stderr.write(string5); + } else if (globalThis.Deno) { + const string5 = `${grey}${file_line}${reset_color} +${string_value} +`; + globalThis.Deno.stderr.writeSync(new TextEncoder().encode(string5)); + } else { + const string5 = `${file_line} +${string_value}`; + globalThis.console.log(string5); + } + return value2; +} +function echo$inspectString(str) { + let new_str = '"'; + for (let i = 0; i < str.length; i++) { + let char = str[i]; + if (char == "\n") + new_str += "\\n"; + else if (char == "\r") + new_str += "\\r"; + else if (char == " ") + new_str += "\\t"; + else if (char == "\f") + new_str += "\\f"; + else if (char == "\\") + new_str += "\\\\"; + else if (char == '"') + new_str += '\\"'; + else if (char < " " || char > "~" && char < "\xA0") { + new_str += "\\u{" + char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0") + "}"; + } else { + new_str += char; + } + } + new_str += '"'; + return new_str; +} +function echo$inspectDict(map4) { + let body = "dict.from_list(["; + let first3 = true; + let key_value_pairs = []; + map4.forEach((value2, key) => { + key_value_pairs.push([key, value2]); + }); + key_value_pairs.sort(); + key_value_pairs.forEach(([key, value2]) => { + if (!first3) + body = body + ", "; + body = body + "#(" + echo$inspect(key) + ", " + echo$inspect(value2) + ")"; + first3 = false; + }); + return body + "])"; +} +function echo$inspectCustomType(record) { + const props = globalThis.Object.keys(record).map((label) => { + const value2 = echo$inspect(record[label]); + return isNaN(parseInt(label)) ? `${label}: ${value2}` : value2; + }).join(", "); + return props ? `${record.constructor.name}(${props})` : record.constructor.name; +} +function echo$inspectObject(v) { + const name6 = Object.getPrototypeOf(v)?.constructor?.name || "Object"; + const props = []; + for (const k of Object.keys(v)) { + props.push(`${echo$inspect(k)}: ${echo$inspect(v[k])}`); + } + const body = props.length ? " " + props.join(", ") + " " : ""; + const head = name6 === "Object" ? "" : name6 + " "; + return `//js(${head}{${body}})`; +} +function echo$inspect(v) { + const t = typeof v; + if (v === true) + return "True"; + if (v === false) + return "False"; + if (v === null) + return "//js(null)"; + if (v === void 0) + return "Nil"; + if (t === "string") + return echo$inspectString(v); + if (t === "bigint" || t === "number") + return v.toString(); + if (globalThis.Array.isArray(v)) + return `#(${v.map(echo$inspect).join(", ")})`; + if (v instanceof List) + return `[${v.toArray().map(echo$inspect).join(", ")}]`; + if (v instanceof UtfCodepoint) + return `//utfcodepoint(${String.fromCodePoint(v.value)})`; + if (v instanceof BitArray) + return echo$inspectBitArray(v); + if (v instanceof CustomType) + return echo$inspectCustomType(v); + if (echo$isDict(v)) + return echo$inspectDict(v); + if (v instanceof Set) + return `//js(Set(${[...v].map(echo$inspect).join(", ")}))`; + if (v instanceof RegExp) + return `//js(${v})`; + if (v instanceof Date) + return `//js(Date("${v.toISOString()}"))`; + if (v instanceof Function) { + const args = []; + for (const i of Array(v.length).keys()) + args.push(String.fromCharCode(i + 97)); + return `//fn(${args.join(", ")}) { ... }`; + } + return echo$inspectObject(v); +} +function echo$inspectBitArray(bitArray) { + let endOfAlignedBytes = bitArray.bitOffset + 8 * Math.trunc(bitArray.bitSize / 8); + let alignedBytes = bitArraySlice( + bitArray, + bitArray.bitOffset, + endOfAlignedBytes + ); + let remainingUnalignedBits = bitArray.bitSize % 8; + if (remainingUnalignedBits > 0) { + let remainingBits = bitArraySliceToInt( + bitArray, + endOfAlignedBytes, + bitArray.bitSize, + false, + false + ); + let alignedBytesArray = Array.from(alignedBytes.rawBuffer); + let suffix = `${remainingBits}:size(${remainingUnalignedBits})`; + if (alignedBytesArray.length === 0) { + return `<<${suffix}>>`; + } else { + return `<<${Array.from(alignedBytes.rawBuffer).join(", ")}, ${suffix}>>`; + } + } else { + return `<<${Array.from(alignedBytes.rawBuffer).join(", ")}>>`; + } +} +function echo$isDict(value2) { + try { + return value2 instanceof Dict; + } catch { + return false; + } +} + +// build/dev/javascript/lustre/lustre/element/keyed.mjs +function do_extract_keyed_children(loop$key_children_pairs, loop$keyed_children, loop$children, loop$children_count) { + while (true) { + let key_children_pairs = loop$key_children_pairs; + let keyed_children = loop$keyed_children; + let children = loop$children; + let children_count = loop$children_count; + if (key_children_pairs instanceof Empty) { + return [keyed_children, reverse(children), children_count]; + } else { + let rest = key_children_pairs.tail; + let key = key_children_pairs.head[0]; + let element$1 = key_children_pairs.head[1]; + let keyed_element = to_keyed(key, element$1); + let _block; + if (key === "") { + _block = keyed_children; + } else { + _block = insert3(keyed_children, key, keyed_element); + } + let keyed_children$1 = _block; + let children$1 = prepend(keyed_element, children); + let children_count$1 = children_count + advance(keyed_element); + loop$key_children_pairs = rest; + loop$keyed_children = keyed_children$1; + loop$children = children$1; + loop$children_count = children_count$1; + } + } +} +function extract_keyed_children(children) { + return do_extract_keyed_children( + children, + empty2(), + empty_list, + 0 + ); +} +function element5(tag, attributes, children) { + let $ = extract_keyed_children(children); + let keyed_children = $[0]; + let children$1 = $[1]; + return element( + "", + identity3, + "", + tag, + attributes, + children$1, + keyed_children, + false, + false + ); +} +function fragment3(children) { + let $ = extract_keyed_children(children); + let keyed_children = $[0]; + let children$1 = $[1]; + let children_count = $[2]; + return fragment( + "", + identity3, + children$1, + keyed_children, + children_count + ); +} +function ul(attributes, children) { + return element5("ul", attributes, children); +} + +// build/dev/javascript/lustre_ui/lustre/ui/data/bidict.mjs +function new$8() { + return [new_map(), new_map()]; +} +function has(bidict, key) { + return has_key(bidict[0], key); +} +function get2(bidict, key) { + return map_get(bidict[0], key); +} +function get_inverse(bidict, key) { + return map_get(bidict[1], key); +} +function min_inverse(bidict, compare4) { + let _pipe = map_to_list(bidict[1]); + let _pipe$1 = sort(_pipe, (a, b) => { + return compare4(a[0], b[0]); + }); + let _pipe$2 = first(_pipe$1); + return map3(_pipe$2, second); +} +function max_inverse(bidict, compare4) { + let _pipe = map_to_list(bidict[1]); + let _pipe$1 = sort(_pipe, (a, b) => { + return compare4(b[0], a[0]); + }); + let _pipe$2 = first(_pipe$1); + return map3(_pipe$2, second); +} +function next(bidict, key, increment) { + let _pipe = get2(bidict, key); + let _pipe$1 = map3(_pipe, increment); + return then$2( + _pipe$1, + (_capture) => { + return get_inverse(bidict, _capture); + } + ); +} +function set(bidict, key, value2) { + return [ + insert(bidict[0], key, value2), + insert(bidict[1], value2, key) + ]; +} +function from_list3(entries) { + return fold2( + entries, + new$8(), + (bidict, entry) => { + return set(bidict, entry[0], entry[1]); + } + ); +} +function indexed(values3) { + return index_fold( + values3, + new$8(), + (bidict, value2, index4) => { + return set(bidict, value2, index4); + } + ); +} + +// build/dev/javascript/lustre/lustre/element/svg.mjs +var namespace = "http://www.w3.org/2000/svg"; +function path(attrs) { + return namespaced(namespace, "path", attrs, empty_list); +} + +// build/dev/javascript/lustre_ui/lustre/ui/primitives/icon.mjs +function icon(attrs, path2) { + return svg( + prepend( + attribute2("viewBox", "0 0 15 15"), + prepend( + attribute2("fill", "none"), + prepend(class$("lustre-ui-icon"), attrs) + ) + ), + toList([ + path( + toList([ + attribute2("d", path2), + attribute2("fill", "currentColor"), + attribute2("fill-rule", "evenodd"), + attribute2("clip-rule", "evenodd") + ]) + ) + ]) + ); +} +function chevron_down(attrs) { + return icon( + attrs, + "M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" + ); +} +function check(attrs) { + return icon( + attrs, + "M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z" + ); +} +function magnifying_glass(attrs) { + return icon( + attrs, + "M10 6.5C10 8.433 8.433 10 6.5 10C4.567 10 3 8.433 3 6.5C3 4.567 4.567 3 6.5 3C8.433 3 10 4.567 10 6.5ZM9.30884 10.0159C8.53901 10.6318 7.56251 11 6.5 11C4.01472 11 2 8.98528 2 6.5C2 4.01472 4.01472 2 6.5 2C8.98528 2 11 4.01472 11 6.5C11 7.56251 10.6318 8.53901 10.0159 9.30884L12.8536 12.1464C13.0488 12.3417 13.0488 12.6583 12.8536 12.8536C12.6583 13.0488 12.3417 13.0488 12.1464 12.8536L9.30884 10.0159Z" + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/accordion.mjs +var Model2 = class extends CustomType { + constructor(options, expanded2, mode) { + super(); + this.options = options; + this.expanded = expanded2; + this.mode = mode; + } +}; +var AtMostOne = class extends CustomType { +}; +var ExactlyOne = class extends CustomType { +}; +var Multi = class extends CustomType { +}; +var Options = class extends CustomType { + constructor(all, lookup_label, lookup_index) { + super(); + this.all = all; + this.lookup_label = lookup_label; + this.lookup_index = lookup_index; + } +}; +var ParentChangedChildren = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var ParentSetMode = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var UserPressedDown = class extends CustomType { + constructor($0, event4) { + super(); + this[0] = $0; + this.event = event4; + } +}; +var UserPressedEnd = class extends CustomType { +}; +var UserPressedHome = class extends CustomType { +}; +var UserPressedUp = class extends CustomType { + constructor($0, event4) { + super(); + this[0] = $0; + this.event = event4; + } +}; +var UserToggledItem = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +function init3(_) { + let options = new Options(toList([]), new$8(), new$8()); + let model = new Model2(options, new$(), new AtMostOne()); + let effect = none(); + return [model, effect]; +} +function focus_trigger(key) { + let selector = "[data-lustre-key=" + key + "] [part=accordion-trigger]"; + return focus2(selector); +} +function update4(model, msg) { + if (msg instanceof ParentChangedChildren) { + let all = msg[0]; + let lookup_label = from_list3(all); + let lookup_index = indexed(map(all, first2)); + let options = new Options(all, lookup_label, lookup_index); + let expanded2 = filter3( + model.expanded, + (_capture) => { + return has(lookup_label, _capture); + } + ); + let keys2 = map(all, first2); + let _block; + let $ = model.mode; + let $1 = size(expanded2); + if ($ instanceof AtMostOne) { + if ($1 === 0) { + _block = expanded2; + } else if ($1 === 1) { + _block = expanded2; + } else { + let _pipe = find2( + keys2, + (_capture) => { + return contains(expanded2, _capture); + } + ); + let _pipe$1 = map3( + _pipe, + (key) => { + return from_list2(toList([key])); + } + ); + _block = unwrap(_pipe$1, new$()); + } + } else if ($ instanceof ExactlyOne) { + if ($1 === 0) { + let _pipe = first(keys2); + let _pipe$1 = map3( + _pipe, + (key) => { + return from_list2(toList([key])); + } + ); + _block = unwrap(_pipe$1, new$()); + } else if ($1 === 1) { + _block = expanded2; + } else { + let _pipe = find2( + keys2, + (_capture) => { + return contains(expanded2, _capture); + } + ); + let _pipe$1 = map3( + _pipe, + (key) => { + return from_list2(toList([key])); + } + ); + _block = unwrap(_pipe$1, new$()); + } + } else { + _block = expanded2; + } + let expanded$1 = _block; + let _block$1; + let _record = model; + _block$1 = new Model2(options, expanded$1, _record.mode); + let model$1 = _block$1; + let effect = none(); + return [model$1, effect]; + } else if (msg instanceof ParentSetMode) { + let mode = msg[0]; + let keys2 = map(model.options.all, first2); + let _block; + let $ = size(model.expanded); + if (mode instanceof AtMostOne) { + if ($ === 0) { + _block = model.expanded; + } else if ($ === 1) { + _block = model.expanded; + } else { + let _pipe = find2( + keys2, + (_capture) => { + return contains(model.expanded, _capture); + } + ); + let _pipe$1 = map3( + _pipe, + (key) => { + return from_list2(toList([key])); + } + ); + _block = unwrap(_pipe$1, new$()); + } + } else if (mode instanceof ExactlyOne) { + if ($ === 0) { + let _pipe = first(keys2); + let _pipe$1 = map3( + _pipe, + (key) => { + return from_list2(toList([key])); + } + ); + _block = unwrap(_pipe$1, new$()); + } else if ($ === 1) { + _block = model.expanded; + } else { + let _pipe = find2( + keys2, + (_capture) => { + return contains(model.expanded, _capture); + } + ); + let _pipe$1 = map3( + _pipe, + (key) => { + return from_list2(toList([key])); + } + ); + _block = unwrap(_pipe$1, new$()); + } + } else { + _block = model.expanded; + } + let expanded2 = _block; + let _block$1; + let _record = model; + _block$1 = new Model2(_record.options, expanded2, mode); + let model$1 = _block$1; + let effect = none(); + return [model$1, effect]; + } else if (msg instanceof UserPressedDown) { + let key = msg[0]; + let event4 = msg.event; + let effect = try$( + get2(model.options.lookup_index, key), + (index4) => { + return map3( + get_inverse(model.options.lookup_index, index4 + 1), + (next2) => { + return focus_trigger(next2); + } + ); + } + ); + return [ + model, + batch( + toList([ + (() => { + let _pipe = effect; + return unwrap(_pipe, none()); + })(), + prevent_default2(event4) + ]) + ) + ]; + } else if (msg instanceof UserPressedEnd) { + let effect = map3( + max_inverse(model.options.lookup_index, compare2), + (last) => { + return focus_trigger(last); + } + ); + return [ + model, + (() => { + let _pipe = effect; + return unwrap(_pipe, none()); + })() + ]; + } else if (msg instanceof UserPressedHome) { + let effect = map3( + min_inverse(model.options.lookup_index, compare2), + (first3) => { + return focus_trigger(first3); + } + ); + return [ + model, + (() => { + let _pipe = effect; + return unwrap(_pipe, none()); + })() + ]; + } else if (msg instanceof UserPressedUp) { + let key = msg[0]; + let event4 = msg.event; + let effect = try$( + get2(model.options.lookup_index, key), + (index4) => { + return map3( + get_inverse(model.options.lookup_index, index4 - 1), + (prev) => { + return focus_trigger(prev); + } + ); + } + ); + return [ + model, + batch( + toList([ + (() => { + let _pipe = effect; + return unwrap(_pipe, none()); + })(), + prevent_default2(event4) + ]) + ) + ]; + } else { + let value2 = msg[0]; + let _block; + let $ = contains(model.expanded, value2); + let $1 = model.mode; + if ($1 instanceof AtMostOne) { + if ($) { + _block = new$(); + } else { + _block = from_list2(toList([value2])); + } + } else if ($1 instanceof ExactlyOne) { + if ($) { + _block = model.expanded; + } else { + _block = from_list2(toList([value2])); + } + } else if ($) { + _block = delete$2(model.expanded, value2); + } else { + _block = insert2(model.expanded, value2); + } + let expanded2 = _block; + let _block$1; + let _record = model; + _block$1 = new Model2(_record.options, expanded2, _record.mode); + let model$1 = _block$1; + let _block$2; + let $2 = contains(expanded2, value2); + if ($2) { + _block$2 = emit("expand", string3(value2)); + } else { + _block$2 = emit("collapse", string3(value2)); + } + let effect = _block$2; + return [model$1, effect]; + } +} +function handle_slot_change2() { + return field( + "target", + assigned_elements2( + field( + "tagName", + string2, + (tag) => { + return then$( + attribute4("value"), + (value2) => { + return field( + "textContent", + string2, + (label) => { + return success([tag, value2, label]); + } + ); + } + ); + } + ), + true + ), + (options) => { + let _pipe = options; + let _pipe$1 = fold_right( + _pipe, + [toList([]), new$()], + (acc, option) => { + let tag = option[0]; + let value2 = option[1]; + let label = option[2]; + return guard( + tag !== "LUSTRE-UI-ACCORDION-ITEM", + acc, + () => { + return guard( + contains(acc[1], value2), + acc, + () => { + let seen = insert2(acc[1], value2); + let options$1 = prepend([value2, label], acc[0]); + return [options$1, seen]; + } + ); + } + ); + } + ); + let _pipe$2 = first2(_pipe$1); + let _pipe$3 = new ParentChangedChildren(_pipe$2); + return success(_pipe$3); + } + ); +} +function handle_keydown3(id) { + return then$( + dynamic, + (event4) => { + return field( + "key", + string2, + (key) => { + if (key === "ArrowDown") { + return success(new UserPressedDown(id, event4)); + } else if (key === "ArrowUp") { + return success(new UserPressedUp(id, event4)); + } else if (key === "End") { + return success(new UserPressedEnd()); + } else if (key === "Home") { + return success(new UserPressedHome()); + } else { + return failure(new UserPressedHome(), ""); + } + } + ); + } + ); +} +var name4 = "lustre-ui-accordion"; +function view3(model) { + return fragment2( + toList([ + slot( + toList([ + style("display", "none"), + on("slotchange", handle_slot_change2()) + ]), + toList([]) + ), + fragment3( + map( + model.options.all, + (_use0) => { + let key = _use0[0]; + let label = _use0[1]; + let is_expanded = contains(model.expanded, key); + let item$1 = element3( + toList([ + expanded(is_expanded), + on_change((_) => { + return new UserToggledItem(key); + }) + ]), + button( + toList([ + attribute2("part", "accordion-trigger"), + attribute2("tabindex", "0"), + on("keydown", handle_keydown3(key)) + ]), + toList([ + p( + toList([attribute2("part", "accordion-trigger-label")]), + toList([text3(label)]) + ), + chevron_down( + toList([ + attribute2( + "part", + (() => { + if (is_expanded) { + return "accordion-trigger-icon expanded"; + } else { + return "accordion-trigger-icon"; + } + })() + ) + ]) + ) + ]) + ), + slot( + toList([ + attribute2("part", "accordion-content"), + name(key) + ]), + toList([]) + ) + ); + return [key, item$1]; + } + ) + ) + ]) + ); +} +function register3() { + let $ = register(); + if ($ instanceof Ok) { + let app = component( + init3, + update4, + view3, + toList([ + on_attribute_change( + "mode", + (value2) => { + if (value2 === "at-most-one") { + return new Ok(new ParentSetMode(new AtMostOne())); + } else if (value2 === "exactly-one") { + return new Ok(new ParentSetMode(new ExactlyOne())); + } else if (value2 === "multi") { + return new Ok(new ParentSetMode(new Multi())); + } else { + return new Error(void 0); + } + } + ) + ]) + ); + return make_component(app, name4); + } else { + let $1 = $[0]; + if ($1 instanceof ComponentAlreadyRegistered) { + let app = component( + init3, + update4, + view3, + toList([ + on_attribute_change( + "mode", + (value2) => { + if (value2 === "at-most-one") { + return new Ok(new ParentSetMode(new AtMostOne())); + } else if (value2 === "exactly-one") { + return new Ok(new ParentSetMode(new ExactlyOne())); + } else if (value2 === "multi") { + return new Ok(new ParentSetMode(new Multi())); + } else { + return new Error(void 0); + } + } + ) + ]) + ); + return make_component(app, name4); + } else { + let error = $; + return error; + } + } +} + +// build/dev/javascript/lustre_ui/lustre/ui/input.mjs +function element6(attributes) { + return input( + prepend(class$("lustre-ui-input"), attributes) + ); +} +function container(attributes, children) { + return div( + prepend(class$("lustre-ui-input-container"), attributes), + children + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/combobox.mjs +var FILEPATH = "src/lustre/ui/combobox.gleam"; +var Item = class extends CustomType { + constructor(value2, label, content) { + super(); + this.value = value2; + this.label = label; + this.content = content; + } +}; +var Model3 = class extends CustomType { + constructor(expanded2, value2, placeholder, query, intent, intent_strategy, options) { + super(); + this.expanded = expanded2; + this.value = value2; + this.placeholder = placeholder; + this.query = query; + this.intent = intent; + this.intent_strategy = intent_strategy; + this.options = options; + } +}; +var ByIndex = class extends CustomType { +}; +var ByLength = class extends CustomType { +}; +var Options2 = class extends CustomType { + constructor(all, filtered, lookup_label, lookup_index) { + super(); + this.all = all; + this.filtered = filtered; + this.lookup_label = lookup_label; + this.lookup_index = lookup_index; + } +}; +var DomBlurredTrigger = class extends CustomType { +}; +var DomFocusedTrigger = class extends CustomType { +}; +var ParentChangedChildren2 = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var ParentSetPlaceholder = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var ParentSetStrategy = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var ParentSetValue = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var UserActivatedPopoverTrigger = class extends CustomType { + constructor(input2) { + super(); + this.input = input2; + } +}; +var UserChangedQuery = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var UserClosedMenu = class extends CustomType { +}; +var UserHoveredOption = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var UserOpenedMenu = class extends CustomType { +}; +var UserPressedDown2 = class extends CustomType { + constructor(event4) { + super(); + this.event = event4; + } +}; +var UserPressedEnd2 = class extends CustomType { + constructor(event4) { + super(); + this.event = event4; + } +}; +var UserPressedEnter = class extends CustomType { + constructor(event4) { + super(); + this.event = event4; + } +}; +var UserPressedEscape = class extends CustomType { + constructor(event4, trigger) { + super(); + this.event = event4; + this.trigger = trigger; + } +}; +var UserPressedHome2 = class extends CustomType { + constructor(event4) { + super(); + this.event = event4; + } +}; +var UserPressedUp2 = class extends CustomType { + constructor(event4) { + super(); + this.event = event4; + } +}; +var UserSelectedOption = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +function init4(_) { + let model = new Model3( + false, + "", + "Select an option...", + "", + new None(), + new ByIndex(), + new Options2(toList([]), toList([]), new$8(), new$8()) + ); + let effect = batch(toList([set_pseudo_state2("empty")])); + return [model, effect]; +} +function contains_query(option, query) { + let _pipe = option.label; + let _pipe$1 = lowercase(_pipe); + return contains_string(_pipe$1, query); +} +function intent_from_query(query, strategy, options) { + return guard( + query === "", + new None(), + () => { + let query$1 = lowercase(query); + let compare_options = (a, b) => { + let a_label = lowercase(a.label); + let b_label = lowercase(b.label); + let a_starts = starts_with(a_label, query$1); + let b_starts = starts_with(b_label, query$1); + let $ = get2(options.lookup_index, a.value); + if (!($ instanceof Ok)) { + throw makeError( + "let_assert", + FILEPATH, + "lustre/ui/combobox", + 513, + "intent_from_query", + "Pattern match failed, no pattern matched the value.", + { + value: $, + start: 14126, + end: 14192, + pattern_start: 14137, + pattern_end: 14148 + } + ); + } + let a_index = $[0]; + let $1 = get2(options.lookup_index, b.value); + if (!($1 instanceof Ok)) { + throw makeError( + "let_assert", + FILEPATH, + "lustre/ui/combobox", + 514, + "intent_from_query", + "Pattern match failed, no pattern matched the value.", + { + value: $1, + start: 14197, + end: 14263, + pattern_start: 14208, + pattern_end: 14219 + } + ); + } + let b_index = $1[0]; + if (b_starts) { + if (!a_starts) { + return new Gt(); + } else if (strategy instanceof ByIndex) { + return compare2(a_index, b_index); + } else { + return compare2( + string_length(a_label), + string_length(b_label) + ); + } + } else if (a_starts) { + return new Lt(); + } else if (strategy instanceof ByIndex) { + return compare2(a_index, b_index); + } else { + return compare2(string_length(a_label), string_length(b_label)); + } + }; + let _pipe = options.all; + let _pipe$1 = filter2( + _pipe, + (_capture) => { + return contains_query(_capture, query$1); + } + ); + let _pipe$2 = sort(_pipe$1, compare_options); + let _pipe$3 = first(_pipe$2); + let _pipe$4 = map3(_pipe$3, (option) => { + return option.value; + }); + return from_result(_pipe$4); + } + ); +} +function update5(model, msg) { + let $ = echo2(msg, "src/lustre/ui/combobox.gleam", 316); + if ($ instanceof DomBlurredTrigger) { + return [model, remove_pseudo_state2("trigger-focus")]; + } else if ($ instanceof DomFocusedTrigger) { + return [model, set_pseudo_state2("trigger-focus")]; + } else if ($ instanceof ParentChangedChildren2) { + let all = $[0]; + let lookup_label = from_list3( + map(all, (item) => { + return [item.value, item.label]; + }) + ); + let lookup_index = indexed( + map(all, (item) => { + return item.value; + }) + ); + let filtered = filter2( + all, + (option) => { + let _pipe = lowercase(option.label); + return contains_string(_pipe, lowercase(model.query)); + } + ); + let options = new Options2(all, filtered, lookup_label, lookup_index); + let intent = new None(); + let _block; + let _record = model; + _block = new Model3( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + intent, + _record.intent_strategy, + options + ); + let model$1 = _block; + let effect = none(); + return [model$1, effect]; + } else if ($ instanceof ParentSetPlaceholder) { + let placeholder$1 = $[0]; + return [ + (() => { + let _record = model; + return new Model3( + _record.expanded, + _record.value, + placeholder$1, + _record.query, + _record.intent, + _record.intent_strategy, + _record.options + ); + })(), + none() + ]; + } else if ($ instanceof ParentSetStrategy) { + let strategy = $[0]; + return [ + (() => { + let _record = model; + return new Model3( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + _record.intent, + strategy, + _record.options + ); + })(), + none() + ]; + } else if ($ instanceof ParentSetValue) { + let value$1 = $[0]; + let _block; + let _record = model; + _block = new Model3( + _record.expanded, + value$1, + _record.placeholder, + _record.query, + new Some(value$1), + _record.intent_strategy, + _record.options + ); + let model$1 = _block; + let _block$1; + if (value$1 === "") { + _block$1 = set_pseudo_state2("empty"); + } else { + _block$1 = remove_pseudo_state2("empty"); + } + let effect = _block$1; + return [model$1, effect]; + } else if ($ instanceof UserActivatedPopoverTrigger) { + let input2 = $.input; + let effect = after_paint( + (_, _1) => { + return focus(input2); + } + ); + return [model, effect]; + } else if ($ instanceof UserChangedQuery) { + let query = $[0]; + let filtered = filter2( + model.options.all, + (option) => { + let _pipe = lowercase(option.label); + return contains_string(_pipe, lowercase(query)); + } + ); + let _block; + let _record = model.options; + _block = new Options2( + _record.all, + filtered, + _record.lookup_label, + _record.lookup_index + ); + let options = _block; + let intent = intent_from_query(query, model.intent_strategy, model.options); + let _block$1; + let _record$1 = model; + _block$1 = new Model3( + _record$1.expanded, + _record$1.value, + _record$1.placeholder, + query, + intent, + _record$1.intent_strategy, + options + ); + let model$1 = _block$1; + let effect = none(); + return [model$1, effect]; + } else if ($ instanceof UserClosedMenu) { + let _block; + let _record = model; + _block = new Model3( + false, + _record.value, + _record.placeholder, + _record.query, + new None(), + _record.intent_strategy, + _record.options + ); + let model$1 = _block; + let effect = remove_pseudo_state2("expanded"); + return [model$1, effect]; + } else if ($ instanceof UserHoveredOption) { + let intent = $[0]; + return [ + (() => { + let _record = model; + return new Model3( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + new Some(intent), + _record.intent_strategy, + _record.options + ); + })(), + none() + ]; + } else if ($ instanceof UserOpenedMenu) { + let _block; + let _record = model; + _block = new Model3( + true, + _record.value, + _record.placeholder, + _record.query, + _record.intent, + _record.intent_strategy, + _record.options + ); + let model$1 = _block; + let effect = set_pseudo_state2("expanded"); + return [model$1, effect]; + } else if ($ instanceof UserPressedDown2) { + let event4 = $.event; + let _block; + let $1 = model.intent; + if ($1 instanceof Some) { + let intent2 = $1[0]; + let _pipe = next( + model.options.lookup_index, + intent2, + (_capture) => { + return add(_capture, 1); + } + ); + let _pipe$1 = or(_pipe, new Ok(intent2)); + _block = from_result(_pipe$1); + } else { + let _pipe = min_inverse(model.options.lookup_index, compare2); + _block = from_result(_pipe); + } + let intent = _block; + let _block$1; + let _record = model; + _block$1 = new Model3( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + intent, + _record.intent_strategy, + _record.options + ); + let model$1 = _block$1; + let effect = prevent_default2(event4); + return [model$1, effect]; + } else if ($ instanceof UserPressedEnd2) { + let event4 = $.event; + let _block; + let _pipe = model.options.lookup_index; + let _pipe$1 = max_inverse(_pipe, compare2); + _block = from_result(_pipe$1); + let intent = _block; + let _block$1; + let _record = model; + _block$1 = new Model3( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + intent, + _record.intent_strategy, + _record.options + ); + let model$1 = _block$1; + let effect = prevent_default2(event4); + return [model$1, effect]; + } else if ($ instanceof UserPressedEnter) { + let event4 = $.event; + let _block; + let $1 = model.intent; + if ($1 instanceof Some) { + let value$1 = $1[0]; + _block = batch( + toList([ + emit( + "change", + object2(toList([["value", string3(value$1)]])) + ), + prevent_default2(event4) + ]) + ); + } else { + _block = prevent_default2(event4); + } + let effect = _block; + return [model, effect]; + } else if ($ instanceof UserPressedEscape) { + let event4 = $.event; + let trigger = $.trigger; + let _block; + let _record = model; + _block = new Model3( + false, + _record.value, + _record.placeholder, + _record.query, + new None(), + _record.intent_strategy, + _record.options + ); + let model$1 = _block; + let effect = batch( + toList([ + remove_pseudo_state2("expanded"), + prevent_default2(event4), + after_paint((_, _1) => { + return focus(trigger); + }) + ]) + ); + return [model$1, effect]; + } else if ($ instanceof UserPressedHome2) { + let event4 = $.event; + let _block; + let _pipe = model.options.lookup_index; + let _pipe$1 = min_inverse(_pipe, compare2); + _block = from_result(_pipe$1); + let intent = _block; + let _block$1; + let _record = model; + _block$1 = new Model3( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + intent, + _record.intent_strategy, + _record.options + ); + let model$1 = _block$1; + let effect = prevent_default2(event4); + return [model$1, effect]; + } else if ($ instanceof UserPressedUp2) { + let event4 = $.event; + let _block; + let $1 = model.intent; + if ($1 instanceof Some) { + let intent2 = $1[0]; + let _pipe = next( + model.options.lookup_index, + intent2, + (_capture) => { + return subtract(_capture, 1); + } + ); + let _pipe$1 = or(_pipe, new Ok(intent2)); + _block = from_result(_pipe$1); + } else { + let _pipe = max_inverse(model.options.lookup_index, compare2); + _block = from_result(_pipe); + } + let intent = _block; + let _block$1; + let _record = model; + _block$1 = new Model3( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + intent, + _record.intent_strategy, + _record.options + ); + let model$1 = _block$1; + let effect = prevent_default2(event4); + return [model$1, effect]; + } else { + let value$1 = $[0]; + let _block; + let _pipe = value$1; + let _pipe$1 = ((_capture) => { + return get2(model.options.lookup_label, _capture); + })(_pipe); + _block = from_result(_pipe$1); + let intent = _block; + let _block$1; + let _record = model; + _block$1 = new Model3( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + intent, + _record.intent_strategy, + _record.options + ); + let model$1 = _block$1; + let effect = emit( + "change", + object2(toList([["value", string3(value$1)]])) + ); + return [model$1, effect]; + } +} +function handle_slot_change3() { + return field( + "target", + assigned_elements2( + field( + "tagName", + string2, + (tag) => { + return then$( + attribute4("value"), + (value2) => { + return field( + "textContent", + string2, + (label) => { + return success([tag, value2, label]); + } + ); + } + ); + } + ), + true + ), + (options) => { + let _pipe = options; + let _pipe$1 = fold_right( + _pipe, + [toList([]), new$()], + (acc, option) => { + let tag = option[0]; + let value$1 = option[1]; + let label = option[2]; + return guard( + tag !== "LUSTRE-UI-COMBOBOX-OPTION", + acc, + () => { + return guard( + contains(acc[1], value$1), + acc, + () => { + let seen = insert2(acc[1], value$1); + let options$1 = prepend( + new Item(value$1, label, toList([])), + acc[0] + ); + return [options$1, seen]; + } + ); + } + ); + } + ); + let _pipe$2 = first2(_pipe$1); + let _pipe$3 = new ParentChangedChildren2(_pipe$2); + return success(_pipe$3); + } + ); +} +function handle_popover_click(will_open) { + return field( + "currentTarget", + child("input", nil(), dynamic), + (input2) => { + if (will_open) { + return success(new UserActivatedPopoverTrigger(input2)); + } else { + return failure(new UserActivatedPopoverTrigger(input2), ""); + } + } + ); +} +function handle_popover_keydown(will_open) { + return field( + "key", + string2, + (key) => { + return field( + "currentTarget", + child("input", nil(), dynamic), + (input2) => { + if (key === "Enter") { + if (will_open) { + return success(new UserActivatedPopoverTrigger(input2)); + } else { + return failure(new UserActivatedPopoverTrigger(input2), ""); + } + } else if (key === " ") { + if (will_open) { + return success(new UserActivatedPopoverTrigger(input2)); + } else { + return failure(new UserActivatedPopoverTrigger(input2), ""); + } + } else { + return failure(new UserActivatedPopoverTrigger(input2), ""); + } + } + ); + } + ); +} +function view_trigger3(value2, placeholder, options) { + let _block; + let _pipe = value2; + let _pipe$1 = ((_capture) => { + return get2(options.lookup_label, _capture); + })(_pipe); + _block = unwrap(_pipe$1, placeholder); + let label = _block; + return button( + toList([ + attribute2("part", "combobox-trigger"), + attribute2("tabindex", "0"), + on_focus(new DomFocusedTrigger()), + on_blur(new DomBlurredTrigger()) + ]), + toList([ + span( + toList([ + attribute2("part", "combobox-trigger-label"), + class$( + (() => { + if (label === "") { + return "empty"; + } else { + return ""; + } + })() + ) + ]), + toList([text3(label)]) + ), + chevron_down(toList([attribute2("part", "combobox-trigger-icon")])) + ]) + ); +} +function handle_input_keydown() { + return then$( + dynamic, + (event4) => { + return field( + "key", + string2, + (key) => { + if (key === "ArrowDown") { + return success(new UserPressedDown2(event4)); + } else if (key === "ArrowEnd") { + return success(new UserPressedEnd2(event4)); + } else if (key === "Enter") { + return success(new UserPressedEnter(event4)); + } else if (key === "Escape") { + return field( + "target", + child("button", nil(), dynamic), + (trigger) => { + return success(new UserPressedEscape(event4, trigger)); + } + ); + } else if (key === "Home") { + return success(new UserPressedHome2(event4)); + } else if (key === "ArrowUp") { + return success(new UserPressedUp2(event4)); + } else if (key === "Tab") { + return success(new UserClosedMenu()); + } else { + return failure(new UserClosedMenu(), ""); + } + } + ); + } + ); +} +function view_input(query) { + return container( + toList([attribute2("part", "combobox-input")]), + toList([ + magnifying_glass(toList([])), + element6( + toList([ + styles( + toList([ + ["width", "100%"], + ["border-bottom-left-radius", "0px"], + ["border-bottom-right-radius", "0px"] + ]) + ), + autocomplete("off"), + on_input((var0) => { + return new UserChangedQuery(var0); + }), + on("keydown", handle_input_keydown()), + value(query) + ]) + ) + ]) + ); +} +var name5 = "lustre-ui-combobox"; +function view_option(option, value2, intent, last) { + let is_selected = option.value === value2; + let is_intent = isEqual(new Some(option.value), intent); + let _block; + if (is_selected) { + _block = check; + } else { + _block = (_capture) => { + return span(_capture, toList([])); + }; + } + let icon2 = _block; + let parts = toList([ + "combobox-option", + (() => { + if (is_intent) { + return "intent"; + } else { + return ""; + } + })(), + (() => { + if (last) { + return "last"; + } else { + return ""; + } + })() + ]); + return li( + toList([ + attribute2("part", join(parts, " ")), + attribute2("value", option.value), + on_mouse_over(new UserHoveredOption(option.value)), + on_mouse_down(new UserSelectedOption(option.value)) + ]), + toList([ + icon2( + toList([ + styles(toList([["height", "1rem"], ["width", "1rem"]])) + ]) + ), + span( + toList([style("flex", "1 1 0%")]), + toList([ + element2( + "slot", + toList([name("option-" + option.value)]), + toList([text3(option.label)]) + ) + ]) + ) + ]) + ); +} +function do_view_options(options, value2, intent) { + if (options instanceof Empty) { + return toList([]); + } else { + let $ = options.tail; + if ($ instanceof Empty) { + let option$1 = options.head; + return toList([ + [option$1.value, view_option(option$1, value2, intent, true)] + ]); + } else { + let option$1 = options.head; + let rest = $; + return prepend( + [option$1.label, view_option(option$1, value2, intent, false)], + do_view_options(rest, value2, intent) + ); + } + } +} +function view_options(options, value2, intent) { + return ul(toList([]), do_view_options(options.filtered, value2, intent)); +} +function view4(model) { + return fragment2( + toList([ + slot( + toList([ + style("display", "none"), + on("slotchange", handle_slot_change3()) + ]), + toList([]) + ), + element4( + toList([ + anchor(new BottomMiddle()), + equal_width(), + gap("var(--padding-y)"), + on_close(new UserClosedMenu()), + on_open(new UserOpenedMenu()), + open(model.expanded), + on("click", handle_popover_click(!model.expanded)), + on("keydown", handle_popover_keydown(!model.expanded)) + ]), + view_trigger3(model.value, model.placeholder, model.options), + div( + toList([attribute2("part", "combobox-options")]), + toList([ + view_input(model.query), + view_options(model.options, model.value, model.intent) + ]) + ) + ) + ]) + ); +} +function register4() { + let $ = register2(); + if ($ instanceof Ok) { + let app = component( + init4, + update5, + view4, + toList([ + adopt_styles(false), + on_attribute_change( + "value", + (value2) => { + return new Ok(new ParentSetValue(value2)); + } + ), + on_attribute_change( + "placeholder", + (value2) => { + return new Ok(new ParentSetPlaceholder(value2)); + } + ), + on_attribute_change( + "strategy", + (value2) => { + if (value2 === "by-length") { + return new Ok(new ParentSetStrategy(new ByLength())); + } else if (value2 === "by-index") { + return new Ok(new ParentSetStrategy(new ByIndex())); + } else { + return new Error(void 0); + } + } + ) + ]) + ); + return make_component(app, name5); + } else { + let $1 = $[0]; + if ($1 instanceof ComponentAlreadyRegistered) { + let app = component( + init4, + update5, + view4, + toList([ + adopt_styles(false), + on_attribute_change( + "value", + (value2) => { + return new Ok(new ParentSetValue(value2)); + } + ), + on_attribute_change( + "placeholder", + (value2) => { + return new Ok(new ParentSetPlaceholder(value2)); + } + ), + on_attribute_change( + "strategy", + (value2) => { + if (value2 === "by-length") { + return new Ok(new ParentSetStrategy(new ByLength())); + } else if (value2 === "by-index") { + return new Ok(new ParentSetStrategy(new ByIndex())); + } else { + return new Error(void 0); + } + } + ) + ]) + ); + return make_component(app, name5); + } else { + let error = $; + return error; + } + } +} +function echo2(value2, file, line) { + const grey = "\x1B[90m"; + const reset_color = "\x1B[39m"; + const file_line = `${file}:${line}`; + const string_value = echo$inspect2(value2); + if (globalThis.process?.stderr?.write) { + const string5 = `${grey}${file_line}${reset_color} +${string_value} +`; + process.stderr.write(string5); + } else if (globalThis.Deno) { + const string5 = `${grey}${file_line}${reset_color} +${string_value} +`; + globalThis.Deno.stderr.writeSync(new TextEncoder().encode(string5)); + } else { + const string5 = `${file_line} +${string_value}`; + globalThis.console.log(string5); + } + return value2; +} +function echo$inspectString2(str) { + let new_str = '"'; + for (let i = 0; i < str.length; i++) { + let char = str[i]; + if (char == "\n") + new_str += "\\n"; + else if (char == "\r") + new_str += "\\r"; + else if (char == " ") + new_str += "\\t"; + else if (char == "\f") + new_str += "\\f"; + else if (char == "\\") + new_str += "\\\\"; + else if (char == '"') + new_str += '\\"'; + else if (char < " " || char > "~" && char < "\xA0") { + new_str += "\\u{" + char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0") + "}"; + } else { + new_str += char; + } + } + new_str += '"'; + return new_str; +} +function echo$inspectDict2(map4) { + let body = "dict.from_list(["; + let first3 = true; + let key_value_pairs = []; + map4.forEach((value2, key) => { + key_value_pairs.push([key, value2]); + }); + key_value_pairs.sort(); + key_value_pairs.forEach(([key, value2]) => { + if (!first3) + body = body + ", "; + body = body + "#(" + echo$inspect2(key) + ", " + echo$inspect2(value2) + ")"; + first3 = false; + }); + return body + "])"; +} +function echo$inspectCustomType2(record) { + const props = globalThis.Object.keys(record).map((label) => { + const value2 = echo$inspect2(record[label]); + return isNaN(parseInt(label)) ? `${label}: ${value2}` : value2; + }).join(", "); + return props ? `${record.constructor.name}(${props})` : record.constructor.name; +} +function echo$inspectObject2(v) { + const name6 = Object.getPrototypeOf(v)?.constructor?.name || "Object"; + const props = []; + for (const k of Object.keys(v)) { + props.push(`${echo$inspect2(k)}: ${echo$inspect2(v[k])}`); + } + const body = props.length ? " " + props.join(", ") + " " : ""; + const head = name6 === "Object" ? "" : name6 + " "; + return `//js(${head}{${body}})`; +} +function echo$inspect2(v) { + const t = typeof v; + if (v === true) + return "True"; + if (v === false) + return "False"; + if (v === null) + return "//js(null)"; + if (v === void 0) + return "Nil"; + if (t === "string") + return echo$inspectString2(v); + if (t === "bigint" || t === "number") + return v.toString(); + if (globalThis.Array.isArray(v)) + return `#(${v.map(echo$inspect2).join(", ")})`; + if (v instanceof List) + return `[${v.toArray().map(echo$inspect2).join(", ")}]`; + if (v instanceof UtfCodepoint) + return `//utfcodepoint(${String.fromCodePoint(v.value)})`; + if (v instanceof BitArray) + return echo$inspectBitArray2(v); + if (v instanceof CustomType) + return echo$inspectCustomType2(v); + if (echo$isDict2(v)) + return echo$inspectDict2(v); + if (v instanceof Set) + return `//js(Set(${[...v].map(echo$inspect2).join(", ")}))`; + if (v instanceof RegExp) + return `//js(${v})`; + if (v instanceof Date) + return `//js(Date("${v.toISOString()}"))`; + if (v instanceof Function) { + const args = []; + for (const i of Array(v.length).keys()) + args.push(String.fromCharCode(i + 97)); + return `//fn(${args.join(", ")}) { ... }`; + } + return echo$inspectObject2(v); +} +function echo$inspectBitArray2(bitArray) { + let endOfAlignedBytes = bitArray.bitOffset + 8 * Math.trunc(bitArray.bitSize / 8); + let alignedBytes = bitArraySlice( + bitArray, + bitArray.bitOffset, + endOfAlignedBytes + ); + let remainingUnalignedBits = bitArray.bitSize % 8; + if (remainingUnalignedBits > 0) { + let remainingBits = bitArraySliceToInt( + bitArray, + endOfAlignedBytes, + bitArray.bitSize, + false, + false + ); + let alignedBytesArray = Array.from(alignedBytes.rawBuffer); + let suffix = `${remainingBits}:size(${remainingUnalignedBits})`; + if (alignedBytesArray.length === 0) { + return `<<${suffix}>>`; + } else { + return `<<${Array.from(alignedBytes.rawBuffer).join(", ")}, ${suffix}>>`; + } + } else { + return `<<${Array.from(alignedBytes.rawBuffer).join(", ")}>>`; + } +} +function echo$isDict2(value2) { + try { + return value2 instanceof Dict; + } catch { + return false; + } +} + +// src/lustre_ui_components.mjs +register(); +register2(); +register3(); +register4(); diff --git a/priv/static/lustre_ui_no_reset.css b/priv/static/lustre_ui_no_reset.css new file mode 100644 index 0000000..25939cf --- /dev/null +++ b/priv/static/lustre_ui_no_reset.css @@ -0,0 +1,1151 @@ +@layer primitives, components; + +@layer components { +.lustre-ui-input { + /* VARIABLES ------------------------------------------------------------ */ + + --border: rgb(var(--lustre-ui-accent)); + --border-focus: rgb(var(--lustre-ui-primary-solid)); + --border-width: 1px; + --padding-y: var(--lustre-ui-spacing-sm); + --radius: var(--lustre-ui-radius-sm); + + /* BASE STYLES ---------------------------------------------------------- */ + + background-color: rgb(var(--lustre-ui-base-bg-subtle)); + padding: var(--padding-y); + outline-offset: 0; + border-radius: var(--radius); + outline: var(--border-width) solid var(--border); + + &:focus { + outline-color: var(--border-focus); + } + + &:invalid { + outline-color: rgb(var(--lustre-ui-danger-solid)); + } +} + +.lustre-ui-input-container { + /* BASE STYLES ---------------------------------------------------------- */ + + position: relative; + display: flex; + align-items: center; + + /* */ + + &:has(* + input) > input { + padding-left: 2rem; + } + + &:has(input + *) > input { + padding-right: 2rem; + } + + &:has(* + input) > :not(input) { + left: 0.5rem; + } + + & > input + * { + left: unset !important; + right: 0.5rem; + } + + & > :not(input) { + position: absolute; + pointer-events: none; + width: 1rem; + height: 1rem; + } +} + +} + +@layer components { +.lustre-ui-divider { + /* Frameworks like Tailwind like to set the default height of
elements + to 0 which messes up our styles that expect the browser defaults. This rolls + back any of those changes. + */ + + height: initial; + width: initial; + + /* VARIABLES ------------------------------------------------------------ */ + + --colour: rgb(var(--lustre-ui-accent)); + --gap: var(--lustre-ui-spacing-sm); + --margin: 0; + --size-x: 2px; + --size-y: 0; + + /* BASE STYLES ---------------------------------------------------------- */ + + &:empty { + align-self: stretch; + border-color: var(--colour); + border-right-width: var(--size-y); + border-style: solid; + border-top-width: var(--size-x); + margin: var(--margin) 0; + } + + &:not(:empty) { + align-items: center; + align-self: stretch; + display: flex; + gap: var(--gap); + justify-items: center; + margin: var(--margin) 0; + + &::before, + &::after { + background-color: var(--colour); + content: ""; + display: block; + flex-grow: 1; + height: var(--size-x); + width: var(--size-y); + } + } +} + +} + +@layer components { +lustre-ui-combobox { + /* VARIABLES ------------------------------------------------------------ */ + + --padding-x: var(--lustre-ui-spacing-sm); + --padding-y: var(--lustre-ui-spacing-sm); + --border-width: 1px; + --radius: var(--lustre-ui-radius-sm); + + /* BASE STYLES ---------------------------------------------------------- */ + + display: inline-block; + + &::part(combobox-trigger) { + align-items: center; + border-radius: var(--radius); + border: rgb(var(--lustre-ui-accent)) solid var(--border-width); + color: rgb(var(--lustre-ui-text)); + display: flex; + gap: var(--padding-x); + outline-offset: 2px; + outline: 2px solid transparent; + padding: var(--padding-y) var(--padding-x); + width: 100%; + } + + &:state(trigger-focus)::part(combobox-trigger) { + border-color: rgb(var(--lustre-ui-primary-solid)); + } + + &:state(empty)::part(combobox-trigger) { + color: rgb(var(--lustre-ui-text-subtle)); + } + + &::part(combobox-trigger-label) { + flex: 1 1 0%; + text-align: left; + } + + &::part(combobox-trigger-icon) { + border-radius: var(--radius); + height: 1.5rem; + padding: 0.25rem; + transform: rotate(0deg); + transition-duration: 150ms; + transition-property: transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + width: 1.5rem; + } + + &:state(expanded)::part(combobox-trigger-icon) { + transform: rotate(-180deg); + } + + &:is(:hover, :state(trigger-focus), :state(expanded))::part( + combobox-trigger-icon + ) { + background-color: rgb(var(--lustre-ui-tint-subtle)); + } + + &::part(combobox-options) { + background-color: rgb(var(--lustre-ui-bg)); + border: rgb(var(--lustre-ui-accent)) solid var(--border-width); + border-radius: var(--radius); + } + + &::part(combobox-input) { + border: none; + } + + &::part(combobox-option) { + align-items: baseline; + cursor: pointer; + display: flex; + padding: var(--padding-y) var(--padding-x); + gap: var(--padding-x); + } + + &::part(combobox-option last) { + border-bottom-left-radius: var(--radius); + border-bottom-right-radius: var(--radius); + } + + &::part(combobox-option intent) { + background-color: rgb(var(--lustre-ui-tint-subtle)); + } +} + +} + +@layer components { +.lustre-ui-checkbox { + /* VARIABLES ------------------------------------------------------------ */ + + --background: transparent; + --background-hover: transparent; + --border: rgb(var(--lustre-ui-accent)); + --border-hover: rgb(var(--lustre-ui-accent-strong)); + --border-width: 1px; + --size: 1rem; + --check-color: rgb(var(--lustre-ui-solid-text)); + + /* BASE STYLES ---------------------------------------------------------- */ + + appearance: none; + background-color: var(--background); + border: var(--border-width) solid var(--border); + border-radius: var(--lustre-ui-radius-sm); + cursor: pointer; + display: grid; + height: var(--size); + margin: 0; + place-content: center; + width: var(--size); + + &::before { + background-color: var(--check-color); + clip-path: polygon( + 16% 44%, + 10% 55%, + 45% 90%, + 90% 16%, + 80% 10%, + 45% 60% + ); + content: ""; + height: calc(var(--size) * 0.6); + transform: scale(0); + transform-origin: center; + transition: 150ms transform cubic-bezier(0.4, 0, 0.2, 1); + width: calc(var(--size) * 0.6); + } + + &:checked { + background-color: rgb(var(--lustre-ui-solid)); + border-color: transparent; + } + + &:checked::before { + transform: scale(1); + } +} + +} + +@layer components { +.lustre-ui-card { + /* VARIABLES ------------------------------------------------------------ */ + + --background: rgb(var(--lustre-ui-bg)); + --border: rgb(var(--lustre-ui-accent-subtle)); + --border-width: 1px; + --padding-x: var(--lustre-ui-spacing-md); + --padding-y: var(--lustre-ui-spacing-md); + --radius: var(--lustre-ui-radius-md); + + /* BASE STYLES ---------------------------------------------------------- */ + + background: var(--background); + border: var(--border-width) solid var(--border); + border-radius: var(--radius); + + & > * { + padding-top: 0; + padding: var(--padding-y) var(--padding-x); + } + + & > .card-header { + display: flex; + flex-direction: column; + } + + & > .card-footer { + align-items: center; + display: flex; + } +} + +} + +@layer components { +.lustre-ui-button { + /* VARIABLES ------------------------------------------------------------ */ + + --background: rgb(var(--lustre-ui-tint)); + --background-hover: rgb(var(--lustre-ui-tint-strong)); + --border: transparent; + --border-hover: transparent; + --border-width: 1px; + --height: 2rem; + --height-min: 2rem; + --padding-x: var(--lustre-ui-spacing-sm); + --radius: var(--lustre-ui-radius-sm); + --text: rgb(var(--lustre-ui-text)); + + /* BASE STYLES ---------------------------------------------------------- */ + + align-items: center; + background-color: var(--background); + border: var(--border-width) solid var(--border); + border-radius: var(--radius); + color: var(--text); + display: inline-flex; + gap: var(--padding-x); + justify-content: center; + min-height: var(--height-min); + padding-left: var(--padding-x); + padding-right: var(--padding-x); + white-space: nowrap; + + &:disabled { + pointer-events: none; + opacity: 0.5; + cursor: default; + } + + &:hover, + &:focus { + outline-offset: 2px; + outline: 2px solid transparent; + background-color: var(--background-hover); + border-color: var(--border-hover); + } + + &:active { + transform: translateY(1px); + } + + @media (prefers-reduced-motion) { + &:active { + transform: unset; + } + } + + &:is(a) { + text-decoration: none; + + &:visited { + color: var(--text); + } + } + + /* SIZE VARIANTS -------------------------------------------------------- */ + + &.button-icon { + --height: 2rem; + width: var(--height); + + & > svg { + height: 100%; + width: 100%; + } + } + + &.button-small { + --height: 2rem; + --padding-x: var(--lustre-ui-spacing-sm); + } + + &.button-medium { + --height: 2.25rem; + --padding-x: var(--lustre-ui-spacing-md); + } + + &.button-large { + --height: 2.5rem; + --padding-x: var(--lustre-ui-spacing-lg); + } + + /* COLOUR VARIANTS ------------------------------------------------------ */ + + &.button-clear { + --background: transparent; + --background-hover: rgb(var(--lustre-ui-tint)); + --border: transparent; + --border-hover: transparent; + --text: rgb(var(--lustre-ui-text)); + } + + &.button-outline { + --background: transparent; + --background-hover: transparent; + --border: rgb(var(--lustre-ui-accent)); + --border-hover: rgb(var(--lustre-ui-accent-strong)); + --text: rgb(var(--lustre-ui-text)); + } + + &.button-soft { + --background: rgb(var(--lustre-ui-tint)); + --background-hover: rgb(var(--lustre-ui-tint-strong)); + --border: transparent; + --border-hover: transparent; + --text: rgb(var(--lustre-ui-text)); + } + + &.button-solid { + --background: rgb(var(--lustre-ui-solid)); + --background-hover: rgb(var(--lustre-ui-solid) / 0.8); + --border: transparent; + --border-hover: transparent; + --text: rgb(var(--lustre-ui-solid-text)); + } + + /* CHILDREN ------------------------------------------------------------- */ + + & .button-badge { + display: inline-flex; + align-items: center; + font-size: 0.625em; + border: 1px solid var(--text); + border-radius: var(--radius); + padding: 0 1ch; + margin: 0 calc(-1 * var(--padding-x) / 2); + + & .key + .key::before { + content: var(--separator, ""); + } + + & .icon { + width: 1em; + height: 1em; + } + } +} + +} + +@layer components { +.lustre-ui-breadcrumb { + /* VARIABLES ------------------------------------------------------------ */ + + --gap: var(--lustre-ui-spacing-xs); + --text-hover: rgb(var(--lustre-ui-text)); + --text: rgb(var(--lustre-ui-text-subtle)); + + /* BASE STYLES ---------------------------------------------------------- */ + + align-items: center; + color: var(--text); + display: flex; + flex-wrap: wrap; + gap: var(--gap); + + /* CHILDREN ------------------------------------------------------------- */ + + & > .breadcrumb-item { + align-items: center; + display: inline-flex; + gap: var(--gap); + + &[aria-current="page"] { + color: var(--text-hover); + } + + &:hover { + color: var(--text-hover); + } + } + + & > .breadcrumb-separator { + user-select: none; + pointer-events: none; + + & > svg { + height: 1rem; + width: 1rem; + } + } + + & > .breadcrumb-ellipsis { + align-items: center; + display: flex; + height: 2rem; + justify-content: center; + user-select: none; + width: 2rem; + + & > svg { + height: 1rem; + width: 1rem; + } + + & > *:not(svg) { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + } +} + +} + +@layer components { +.lustre-ui-badge { + /* VARIABLES ------------------------------------------------------------ */ + + --background: transparent; + --background-hover: transparent; + --border: rgb(var(--lustre-ui-accent)); + --border-hover: rgb(var(--lustre-ui-accent-strong)); + --padding-x: var(--lustre-ui-spacing-sm); + --padding-y: var(--lustre-ui-spacing-xs); + --radius: var(--lustre-ui-radius-sm); + --text: rgb(var(--lustre-ui-text)); + + /* BASE STYLES ---------------------------------------------------------- */ + + border: 1px solid var(--border); + border-radius: var(--radius); + background-color: var(--background); + color: var(--text); + display: inline-flex; + align-items: center; + padding: var(--padding-y) var(--padding-x); + + /* Badges that can be interacted with receive additional visual styles to + indicate that to the user. + */ + &:is(a, button, [tabindex]):is(:hover, :focus) { + background-color: var(--background-hover); + border-color: var(--border-hover); + cursor: pointer; + } + + &:active { + transform: translateY(1px); + } + + &:empty { + padding: max(var(--padding-y), var(--padding-x)); + border-radius: 100%; + } + + /* VARIANTS ------------------------------------------------------------- */ + + &.badge-outline { + --background: transparent; + --background-hover: transparent; + --border: rgb(var(--lustre-ui-accent)); + --border-hover: rgb(var(--lustre-ui-accent-strong)); + --text: rgb(var(--lustre-ui-text)); + } + + &.badge-soft { + --background: rgb(var(--lustre-ui-tint)); + --background-hover: rgb(var(--lustre-ui-tint-strong)); + --border: transparent; + --border-hover: transparent; + --text: rgb(var(--lustre-ui-text)); + } + + &.badge-solid { + --background: rgb(var(--lustre-ui-solid)); + --background-hover: rgb(var(--lustre-ui-solid) / 0.8); + --border: transparent; + --border-hover: transparent; + --text: rgb(var(--lustre-ui-solid-text)); + } +} + +} + +@layer components { +.lustre-ui-alert { + /* VARIABLES ------------------------------------------------------------ */ + + --background: rgb(var(--lustre-ui-bg)); + --border: rgb(var(--lustre-ui-accent)); + --indicator-size: 16px; + --padding-x: var(--lustre-ui-spacing-lg); + --padding-y: var(--lustre-ui-spacing-md); + --radius: 0; + --text: rgb(var(--lustre-ui-text)); + --title-margin: var(--padding-y); + --title-weight: 500; + + /* BASE STYLES ---------------------------------------------------------- */ + + display: flex; + flex-direction: column; + background-color: var(--background); + border-radius: var(--radius); + border: 1px solid var(--border); + color: var(--text); + padding: var(--padding-y) var(--padding-x); + position: relative; + width: full; + + /* CHILDREN ------------------------------------------------------------- */ + + & > .alert-indicator { + position: absolute; + width: var(--indicator-size); + height: var(--indicator-size); + } + + & > .alert-title { + font-weight: var(--title-weight); + margin-block-end: var(--title-margin); + overflow: clip; + text-overflow: ellipsis; + } + + /* If an indicator is present, it's important it doesn't end up placed over + the top of any content we have. To make sure things are placed correctly, + we calculate padding on these elements to push them further to the right + by an amount based on the the indicator's size. + */ + &:has(.alert-indicator) > :not(.alert-indicator) { + padding-inline-start: calc(var(--indicator-size) + var(--padding-x)); + } +} + +} + +@layer components { +lustre-ui-accordion { + --padding-x: var(--lustre-ui-spacing-sm); + --padding-y: var(--lustre-ui-spacing-sm); + --border: rgb(var(--lustre-ui-accent)); + --border-focus: rgb(var(--lustre-ui-primary-solid)); + --border-width: 1px; + --text: rgb(var(--lustre-ui-text)); + + display: block; + + &::part(accordion-trigger) { + align-items: center; + border-bottom: var(--border) solid var(--border-width); + color: var(--text); + display: flex; + gap: var(--padding-x); + outline-offset: 2px; + outline: 2px solid transparent; + padding: var(--padding-y) var(--padding-x); + width: 100%; + } + + &::part(accordion-trigger):focus { + border-color: var(--border-focus); + } + + &::part(accordion-trigger-label) { + flex: 1 1 0%; + text-align: left; + } + + &::part(accordion-trigger-icon) { + border-radius: var(--radius); + height: 1.5rem; + padding: 0.25rem; + transform: rotate(0deg); + transition-duration: 150ms; + transition-property: color transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + width: 1.5rem; + } + + &::part(accordion-trigger-icon expanded) { + transform: rotate(-180deg); + } + + &::part(accordion-content) { + display: block; + padding: var(--padding-y) var(--padding-x); + } +} + +} + +@layer components { +/* +! tailwindcss v3.4.15 | MIT License | https://tailwindcss.com +*/ + +*, +:after, +:before { + box-sizing: border-box; + border: 0 solid #e2e8f0; +} + +:host, +html { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + font-feature-settings: normal; + font-variation-settings: normal; + -webkit-tap-highlight-color: transparent; +} + +body { + margin: 0; + line-height: inherit; +} + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +a { + color: inherit; + text-decoration: inherit; +} + +b, +strong { + font-weight: bolder; +} + +code, +kbd, +pre, +samp { + font-family: + ui-monospace, + SFMono-Regular, + Menlo, + Monaco, + Consolas, + Liberation Mono, + Courier New, + monospace; + font-feature-settings: normal; + font-variation-settings: normal; + font-size: 1em; +} + +small { + font-size: 80%; +} + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; +} + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + font-size: 100%; + font-weight: inherit; + line-height: inherit; + letter-spacing: inherit; + color: inherit; + margin: 0; + padding: 0; +} + +button, +select { + text-transform: none; +} + +button, +input:where([type="button"]), +input:where([type="reset"]), +input:where([type="submit"]) { + -webkit-appearance: button; + background-color: transparent; + background-image: none; +} + +:-moz-focusring { + outline: auto; +} + +:-moz-ui-invalid { + box-shadow: none; +} + +progress { + vertical-align: baseline; +} + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +[type="search"] { + -webkit-appearance: textfield; + outline-offset: -2px; +} + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit; +} + +summary { + display: list-item; +} + +blockquote, +dd, +dl, +figure, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; +} + +fieldset, +legend { + padding: 0; +} + +menu, +ol, +ul { + list-style: none; + margin: 0; + padding: 0; +} + +dialog { + padding: 0; +} + +textarea { + resize: vertical; +} + +input::-moz-placeholder, +textarea::-moz-placeholder { + opacity: 1; + color: #94a3b8; +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + color: #94a3b8; +} + +[role="button"], +button { + cursor: pointer; +} + +:disabled { + cursor: default; +} + +audio, +canvas, +embed, +iframe, +img, +object, +svg, +video { + display: block; + vertical-align: middle; +} + +img, +video { + max-width: 100%; + height: auto; +} + +[hidden]:where(:not([hidden="until-found"])) { + display: none; +} + +} + +@layer primitives { +lustre-ui-popover { + --gap: var(--lustre-ui-spacing-sm); + + display: inline; + position: relative; + + & > [slot] { + display: contents; + } + + &::part(popover-content) { + left: 0; + top: 0; + display: block; + position: absolute; + transition-duration: 150ms; + transition-property: opacity, transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + z-index: 999; + } + + &[anchor|="top"] { + --translate-x: 0; + + &::part(popover-content) { + padding-bottom: var(--gap); + transform: translate(var(--translate-x), -100%); + } + + &[equal-width]::part(popover-content) { + --translate-x: 0 !important; + + left: 0 !important; + right: 0; + } + + &[anchor="top-left"]::part(popover-content) { + left: 0; + } + + &[anchor="top-middle"]::part(popover-content) { + --translate-x: -50%; + left: 50%; + } + + &[anchor="top-right"]::part(popover-content) { + --translate-x: -100%; + left: 100%; + } + + &:state(will-expand)::part(popover-content) { + transform: translate(var(--translate-x), -90%); + } + + &:state(collapsing)::part(popover-content) { + transform: translate(var(--translate-x), -90%); + } + } + + &[anchor|="right"] { + --translate-y: 0; + + &::part(popover-content) { + padding-left: var(--gap); + left: 100%; + transform: translate(0, var(--translate-y)); + } + + &[anchor="right-top"]::part(popover-content) { + top: 0; + } + + &[anchor="right-middle"]::part(popover-content) { + --translate-y: -50%; + top: 50%; + } + + &[anchor="right-bottom"]::part(popover-content) { + --translate-y: -100%; + top: 100%; + } + + &:state(will-expand)::part(popover-content) { + transform: translate(-10%, var(--translate-y)); + } + + &:state(collapsing)::part(popover-content) { + transform: translate(-10%, var(--translate-y)); + } + } + + &[anchor|="bottom"] { + --translate-x: 0; + + &::part(popover-content) { + top: unset; + padding-top: var(--gap); + transform: translate(var(--translate-x), 0); + } + + &[equal-width]::part(popover-content) { + --translate-x: 0 !important; + + left: 0 !important; + right: 0; + } + + &[anchor="bottom-left"]::part(popover-content) { + left: 0; + } + + &[anchor="bottom-middle"]::part(popover-content) { + --translate-x: -50%; + left: 50%; + } + + &[anchor="bottom-right"]::part(popover-content) { + --translate-x: -100%; + left: 100%; + } + + &:state(will-expand)::part(popover-content) { + transform: translate(var(--translate-x), -10%); + } + + &:state(collapsing)::part(popover-content) { + transform: translate(var(--translate-x), -10%); + } + } + + &[anchor|="left"] { + --translate-y: 0; + + &::part(popover-content) { + padding-right: var(--gap); + transform: translate(-100%, var(--translate-y)); + } + + &[anchor="left-top"]::part(popover-content) { + top: 0; + } + + &[anchor="left-middle"]::part(popover-content) { + --translate-y: -50%; + top: 50%; + } + + &[anchor="left-bottom"]::part(popover-content) { + --translate-y: -100%; + top: 100%; + } + + &:state(will-expand)::part(popover-content) { + transform: translate(-90%, var(--translate-y)); + } + + &:state(collapsing)::part(popover-content) { + transform: translate(-90%, var(--translate-y)); + } + } + + &:state(will-expand)::part(popover-content) { + opacity: 0; + } + + &:state(expanded)::part(popover-content) { + opacity: 100; + } + + &:state(will-collapse)::part(popover-content) { + opacity: 100; + } + + &:state(collapsing)::part(popover-content) { + opacity: 0; + } + + &:state(collapsed)::part(popover-content) { + display: none; + } +} + +} + +@layer primitives { +:where(.lustre-ui-icon) { + width: 1rem; + height: 1rem; +} + +} + +@layer primitives { +lustre-ui-collapse { + --duration: 150ms; + + display: block; + + &::part(collapse-content) { + transition-duration: var(--duration); + transition-property: height; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + overflow-y: hidden; + } +} + +} + diff --git a/priv/static/lustre_ui_no_reset.min.css b/priv/static/lustre_ui_no_reset.min.css new file mode 100644 index 0000000..49c6ced --- /dev/null +++ b/priv/static/lustre_ui_no_reset.min.css @@ -0,0 +1 @@ +@layer primitives,components;@layer components{.lustre-ui-input{--border: rgb(var(--lustre-ui-accent));--border-focus: rgb(var(--lustre-ui-primary-solid));--border-width: 1px;--padding-y: var(--lustre-ui-spacing-sm);--radius: var(--lustre-ui-radius-sm);background-color:rgb(var(--lustre-ui-base-bg-subtle));padding:var(--padding-y);outline-offset:0;border-radius:var(--radius);outline:var(--border-width) solid var(--border);&:focus{outline-color:var(--border-focus)}&:invalid{outline-color:rgb(var(--lustre-ui-danger-solid))}}.lustre-ui-input-container{position:relative;display:flex;align-items:center;&:has(*+input)>input{padding-left:2rem}&:has(input+*)>input{padding-right:2rem}&:has(*+input)>:not(input){left:.5rem}>input+*{left:unset!important;right:.5rem}>:not(input){position:absolute;pointer-events:none;width:1rem;height:1rem}}}@layer components{.lustre-ui-divider{height:initial;width:initial;--colour: rgb(var(--lustre-ui-accent));--gap: var(--lustre-ui-spacing-sm);--margin: 0;--size-x: 2px;--size-y: 0;&:empty{align-self:stretch;border-color:var(--colour);border-right-width:var(--size-y);border-style:solid;border-top-width:var(--size-x);margin:var(--margin) 0}&:not(:empty){align-items:center;align-self:stretch;display:flex;gap:var(--gap);justify-items:center;margin:var(--margin) 0;&:before,&:after{background-color:var(--colour);content:"";display:block;flex-grow:1;height:var(--size-x);width:var(--size-y)}}}}@layer components{lustre-ui-combobox{--padding-x: var(--lustre-ui-spacing-sm);--padding-y: var(--lustre-ui-spacing-sm);--border-width: 1px;--radius: var(--lustre-ui-radius-sm);display:inline-block;&::part(combobox-trigger){align-items:center;border-radius:var(--radius);border:rgb(var(--lustre-ui-accent)) solid var(--border-width);color:rgb(var(--lustre-ui-text));display:flex;gap:var(--padding-x);outline-offset:2px;outline:2px solid transparent;padding:var(--padding-y) var(--padding-x);width:100%}&:state(trigger-focus)::part(combobox-trigger){border-color:rgb(var(--lustre-ui-primary-solid))}&:state(empty)::part(combobox-trigger){color:rgb(var(--lustre-ui-text-subtle))}&::part(combobox-trigger-label){flex:1 1 0%;text-align:left}&::part(combobox-trigger-icon){border-radius:var(--radius);height:1.5rem;padding:.25rem;transform:rotate(0);transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);width:1.5rem}&:state(expanded)::part(combobox-trigger-icon){transform:rotate(-180deg)}&:is(:hover,:state(trigger-focus),:state(expanded))::part(combobox-trigger-icon){background-color:rgb(var(--lustre-ui-tint-subtle))}&::part(combobox-options){background-color:rgb(var(--lustre-ui-bg));border:rgb(var(--lustre-ui-accent)) solid var(--border-width);border-radius:var(--radius)}&::part(combobox-input){border:none}&::part(combobox-option){align-items:baseline;cursor:pointer;display:flex;padding:var(--padding-y) var(--padding-x);gap:var(--padding-x)}&::part(combobox-option last){border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius)}&::part(combobox-option intent){background-color:rgb(var(--lustre-ui-tint-subtle))}}}@layer components{.lustre-ui-checkbox{--background: transparent;--background-hover: transparent;--border: rgb(var(--lustre-ui-accent));--border-hover: rgb(var(--lustre-ui-accent-strong));--border-width: 1px;--size: 1rem;--check-color: rgb(var(--lustre-ui-solid-text));appearance:none;background-color:var(--background);border:var(--border-width) solid var(--border);border-radius:var(--lustre-ui-radius-sm);cursor:pointer;display:grid;height:var(--size);margin:0;place-content:center;width:var(--size);&:before{background-color:var(--check-color);clip-path:polygon(16% 44%,10% 55%,45% 90%,90% 16%,80% 10%,45% 60%);content:"";height:calc(var(--size) * .6);transform:scale(0);transform-origin:center;transition:.15s transform cubic-bezier(.4,0,.2,1);width:calc(var(--size) * .6)}&:checked{background-color:rgb(var(--lustre-ui-solid));border-color:transparent}&:checked:before{transform:scale(1)}}}@layer components{.lustre-ui-card{--background: rgb(var(--lustre-ui-bg));--border: rgb(var(--lustre-ui-accent-subtle));--border-width: 1px;--padding-x: var(--lustre-ui-spacing-md);--padding-y: var(--lustre-ui-spacing-md);--radius: var(--lustre-ui-radius-md);background:var(--background);border:var(--border-width) solid var(--border);border-radius:var(--radius);>*{padding-top:0;padding:var(--padding-y) var(--padding-x)}>.card-header{display:flex;flex-direction:column}>.card-footer{align-items:center;display:flex}}}@layer components{.lustre-ui-button{--background: rgb(var(--lustre-ui-tint));--background-hover: rgb(var(--lustre-ui-tint-strong));--border: transparent;--border-hover: transparent;--border-width: 1px;--height: 2rem;--height-min: 2rem;--padding-x: var(--lustre-ui-spacing-sm);--radius: var(--lustre-ui-radius-sm);--text: rgb(var(--lustre-ui-text));align-items:center;background-color:var(--background);border:var(--border-width) solid var(--border);border-radius:var(--radius);color:var(--text);display:inline-flex;gap:var(--padding-x);justify-content:center;min-height:var(--height-min);padding-left:var(--padding-x);padding-right:var(--padding-x);white-space:nowrap;&:disabled{pointer-events:none;opacity:.5;cursor:default}&:hover,&:focus{outline-offset:2px;outline:2px solid transparent;background-color:var(--background-hover);border-color:var(--border-hover)}&:active{transform:translateY(1px)}@media (prefers-reduced-motion){&:active{transform:unset}}&:is(a){text-decoration:none;&:visited{color:var(--text)}}&.button-icon{--height: 2rem;width:var(--height);>svg{height:100%;width:100%}}&.button-small{--height: 2rem;--padding-x: var(--lustre-ui-spacing-sm)}&.button-medium{--height: 2.25rem;--padding-x: var(--lustre-ui-spacing-md)}&.button-large{--height: 2.5rem;--padding-x: var(--lustre-ui-spacing-lg)}&.button-clear{--background: transparent;--background-hover: rgb(var(--lustre-ui-tint));--border: transparent;--border-hover: transparent;--text: rgb(var(--lustre-ui-text))}&.button-outline{--background: transparent;--background-hover: transparent;--border: rgb(var(--lustre-ui-accent));--border-hover: rgb(var(--lustre-ui-accent-strong));--text: rgb(var(--lustre-ui-text))}&.button-soft{--background: rgb(var(--lustre-ui-tint));--background-hover: rgb(var(--lustre-ui-tint-strong));--border: transparent;--border-hover: transparent;--text: rgb(var(--lustre-ui-text))}&.button-solid{--background: rgb(var(--lustre-ui-solid));--background-hover: rgb(var(--lustre-ui-solid) / .8);--border: transparent;--border-hover: transparent;--text: rgb(var(--lustre-ui-solid-text))}.button-badge{display:inline-flex;align-items:center;font-size:.625em;border:1px solid var(--text);border-radius:var(--radius);padding:0 1ch;margin:0 calc(-1 * var(--padding-x) / 2);.key+.key:before{content:var(--separator, "")}.icon{width:1em;height:1em}}}}@layer components{.lustre-ui-breadcrumb{--gap: var(--lustre-ui-spacing-xs);--text-hover: rgb(var(--lustre-ui-text));--text: rgb(var(--lustre-ui-text-subtle));align-items:center;color:var(--text);display:flex;flex-wrap:wrap;gap:var(--gap);>.breadcrumb-item{align-items:center;display:inline-flex;gap:var(--gap);&[aria-current=page]{color:var(--text-hover)}&:hover{color:var(--text-hover)}}>.breadcrumb-separator{user-select:none;pointer-events:none;>svg{height:1rem;width:1rem}}>.breadcrumb-ellipsis{align-items:center;display:flex;height:2rem;justify-content:center;user-select:none;width:2rem;>svg{height:1rem;width:1rem}>*:not(svg){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}}}}@layer components{.lustre-ui-badge{--background: transparent;--background-hover: transparent;--border: rgb(var(--lustre-ui-accent));--border-hover: rgb(var(--lustre-ui-accent-strong));--padding-x: var(--lustre-ui-spacing-sm);--padding-y: var(--lustre-ui-spacing-xs);--radius: var(--lustre-ui-radius-sm);--text: rgb(var(--lustre-ui-text));border:1px solid var(--border);border-radius:var(--radius);background-color:var(--background);color:var(--text);display:inline-flex;align-items:center;padding:var(--padding-y) var(--padding-x);&:is(a,button,[tabindex]):is(:hover,:focus){background-color:var(--background-hover);border-color:var(--border-hover);cursor:pointer}&:active{transform:translateY(1px)}&:empty{padding:max(var(--padding-y),var(--padding-x));border-radius:100%}&.badge-outline{--background: transparent;--background-hover: transparent;--border: rgb(var(--lustre-ui-accent));--border-hover: rgb(var(--lustre-ui-accent-strong));--text: rgb(var(--lustre-ui-text))}&.badge-soft{--background: rgb(var(--lustre-ui-tint));--background-hover: rgb(var(--lustre-ui-tint-strong));--border: transparent;--border-hover: transparent;--text: rgb(var(--lustre-ui-text))}&.badge-solid{--background: rgb(var(--lustre-ui-solid));--background-hover: rgb(var(--lustre-ui-solid) / .8);--border: transparent;--border-hover: transparent;--text: rgb(var(--lustre-ui-solid-text))}}}@layer components{.lustre-ui-alert{--background: rgb(var(--lustre-ui-bg));--border: rgb(var(--lustre-ui-accent));--indicator-size: 16px;--padding-x: var(--lustre-ui-spacing-lg);--padding-y: var(--lustre-ui-spacing-md);--radius: 0;--text: rgb(var(--lustre-ui-text));--title-margin: var(--padding-y);--title-weight: 500;display:flex;flex-direction:column;background-color:var(--background);border-radius:var(--radius);border:1px solid var(--border);color:var(--text);padding:var(--padding-y) var(--padding-x);position:relative;width:full;>.alert-indicator{position:absolute;width:var(--indicator-size);height:var(--indicator-size)}>.alert-title{font-weight:var(--title-weight);margin-block-end:var(--title-margin);overflow:clip;text-overflow:ellipsis}&:has(.alert-indicator)>:not(.alert-indicator){padding-inline-start:calc(var(--indicator-size) + var(--padding-x))}}}@layer components{lustre-ui-accordion{--padding-x: var(--lustre-ui-spacing-sm);--padding-y: var(--lustre-ui-spacing-sm);--border: rgb(var(--lustre-ui-accent));--border-focus: rgb(var(--lustre-ui-primary-solid));--border-width: 1px;--text: rgb(var(--lustre-ui-text));display:block;&::part(accordion-trigger){align-items:center;border-bottom:var(--border) solid var(--border-width);color:var(--text);display:flex;gap:var(--padding-x);outline-offset:2px;outline:2px solid transparent;padding:var(--padding-y) var(--padding-x);width:100%}&::part(accordion-trigger):focus{border-color:var(--border-focus)}&::part(accordion-trigger-label){flex:1 1 0%;text-align:left}&::part(accordion-trigger-icon){border-radius:var(--radius);height:1.5rem;padding:.25rem;transform:rotate(0);transition-duration:.15s;transition-property:color transform;transition-timing-function:cubic-bezier(.4,0,.2,1);width:1.5rem}&::part(accordion-trigger-icon expanded){transform:rotate(-180deg)}&::part(accordion-content){display:block;padding:var(--padding-y) var(--padding-x)}}}@layer components{*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#94a3b8}input::placeholder,textarea::placeholder{opacity:1;color:#94a3b8}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}}@layer primitives{lustre-ui-popover{--gap: var(--lustre-ui-spacing-sm);display:inline;position:relative;>[slot]{display:contents}&::part(popover-content){left:0;top:0;display:block;position:absolute;transition-duration:.15s;transition-property:opacity,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);z-index:999}&[anchor|=top]{--translate-x: 0;&::part(popover-content){padding-bottom:var(--gap);transform:translate(var(--translate-x),-100%)}&[equal-width]::part(popover-content){--translate-x: 0 !important;left:0!important;right:0}&[anchor=top-left]::part(popover-content){left:0}&[anchor=top-middle]::part(popover-content){--translate-x: -50%;left:50%}&[anchor=top-right]::part(popover-content){--translate-x: -100%;left:100%}&:state(will-expand)::part(popover-content){transform:translate(var(--translate-x),-90%)}&:state(collapsing)::part(popover-content){transform:translate(var(--translate-x),-90%)}}&[anchor|=right]{--translate-y: 0;&::part(popover-content){padding-left:var(--gap);left:100%;transform:translateY(var(--translate-y))}&[anchor=right-top]::part(popover-content){top:0}&[anchor=right-middle]::part(popover-content){--translate-y: -50%;top:50%}&[anchor=right-bottom]::part(popover-content){--translate-y: -100%;top:100%}&:state(will-expand)::part(popover-content){transform:translate(-10%,var(--translate-y))}&:state(collapsing)::part(popover-content){transform:translate(-10%,var(--translate-y))}}&[anchor|=bottom]{--translate-x: 0;&::part(popover-content){top:unset;padding-top:var(--gap);transform:translate(var(--translate-x))}&[equal-width]::part(popover-content){--translate-x: 0 !important;left:0!important;right:0}&[anchor=bottom-left]::part(popover-content){left:0}&[anchor=bottom-middle]::part(popover-content){--translate-x: -50%;left:50%}&[anchor=bottom-right]::part(popover-content){--translate-x: -100%;left:100%}&:state(will-expand)::part(popover-content){transform:translate(var(--translate-x),-10%)}&:state(collapsing)::part(popover-content){transform:translate(var(--translate-x),-10%)}}&[anchor|=left]{--translate-y: 0;&::part(popover-content){padding-right:var(--gap);transform:translate(-100%,var(--translate-y))}&[anchor=left-top]::part(popover-content){top:0}&[anchor=left-middle]::part(popover-content){--translate-y: -50%;top:50%}&[anchor=left-bottom]::part(popover-content){--translate-y: -100%;top:100%}&:state(will-expand)::part(popover-content){transform:translate(-90%,var(--translate-y))}&:state(collapsing)::part(popover-content){transform:translate(-90%,var(--translate-y))}}&:state(will-expand)::part(popover-content){opacity:0}&:state(expanded)::part(popover-content){opacity:100}&:state(will-collapse)::part(popover-content){opacity:100}&:state(collapsing)::part(popover-content){opacity:0}&:state(collapsed)::part(popover-content){display:none}}}@layer primitives{:where(.lustre-ui-icon){width:1rem;height:1rem}}@layer primitives{lustre-ui-collapse{--duration: .15s;display:block;&::part(collapse-content){transition-duration:var(--duration);transition-property:height;transition-timing-function:cubic-bezier(.4,0,.2,1);overflow-y:hidden}}} diff --git a/priv/static/lustre_ui_storybook.mjs b/priv/static/lustre_ui_storybook.mjs new file mode 100644 index 0000000..74b807c --- /dev/null +++ b/priv/static/lustre_ui_storybook.mjs @@ -0,0 +1,14280 @@ +// build/dev/javascript/prelude.mjs +var CustomType = class { + withFields(fields) { + let properties = Object.keys(this).map( + (label2) => label2 in fields ? fields[label2] : this[label2] + ); + return new this.constructor(...properties); + } +}; +var List = class { + static fromArray(array3, tail) { + let t = tail || new Empty(); + for (let i = array3.length - 1; i >= 0; --i) { + t = new NonEmpty(array3[i], t); + } + return t; + } + [Symbol.iterator]() { + return new ListIterator(this); + } + toArray() { + return [...this]; + } + // @internal + atLeastLength(desired) { + let current2 = this; + while (desired-- > 0 && current2) current2 = current2.tail; + return current2 !== void 0; + } + // @internal + hasLength(desired) { + let current2 = this; + while (desired-- > 0 && current2) current2 = current2.tail; + return desired === -1 && current2 instanceof Empty; + } + // @internal + countLength() { + let current2 = this; + let length4 = 0; + while (current2) { + current2 = current2.tail; + length4++; + } + return length4 - 1; + } +}; +function prepend(element15, tail) { + return new NonEmpty(element15, tail); +} +function toList(elements, tail) { + return List.fromArray(elements, tail); +} +var ListIterator = class { + #current; + constructor(current2) { + this.#current = current2; + } + next() { + if (this.#current instanceof Empty) { + return { done: true }; + } else { + let { head, tail } = this.#current; + this.#current = tail; + return { value: head, done: false }; + } + } +}; +var Empty = class extends List { +}; +var NonEmpty = class extends List { + constructor(head, tail) { + super(); + this.head = head; + this.tail = tail; + } +}; +var BitArray = class { + /** + * The size in bits of this bit array's data. + * + * @type {number} + */ + bitSize; + /** + * The size in bytes of this bit array's data. If this bit array doesn't store + * a whole number of bytes then this value is rounded up. + * + * @type {number} + */ + byteSize; + /** + * The number of unused high bits in the first byte of this bit array's + * buffer prior to the start of its data. The value of any unused high bits is + * undefined. + * + * The bit offset will be in the range 0-7. + * + * @type {number} + */ + bitOffset; + /** + * The raw bytes that hold this bit array's data. + * + * If `bitOffset` is not zero then there are unused high bits in the first + * byte of this buffer. + * + * If `bitOffset + bitSize` is not a multiple of 8 then there are unused low + * bits in the last byte of this buffer. + * + * @type {Uint8Array} + */ + rawBuffer; + /** + * Constructs a new bit array from a `Uint8Array`, an optional size in + * bits, and an optional bit offset. + * + * If no bit size is specified it is taken as `buffer.length * 8`, i.e. all + * bytes in the buffer make up the new bit array's data. + * + * If no bit offset is specified it defaults to zero, i.e. there are no unused + * high bits in the first byte of the buffer. + * + * @param {Uint8Array} buffer + * @param {number} [bitSize] + * @param {number} [bitOffset] + */ + constructor(buffer, bitSize, bitOffset) { + if (!(buffer instanceof Uint8Array)) { + throw globalThis.Error( + "BitArray can only be constructed from a Uint8Array" + ); + } + this.bitSize = bitSize ?? buffer.length * 8; + this.byteSize = Math.trunc((this.bitSize + 7) / 8); + this.bitOffset = bitOffset ?? 0; + if (this.bitSize < 0) { + throw globalThis.Error(`BitArray bit size is invalid: ${this.bitSize}`); + } + if (this.bitOffset < 0 || this.bitOffset > 7) { + throw globalThis.Error( + `BitArray bit offset is invalid: ${this.bitOffset}` + ); + } + if (buffer.length !== Math.trunc((this.bitOffset + this.bitSize + 7) / 8)) { + throw globalThis.Error("BitArray buffer length is invalid"); + } + this.rawBuffer = buffer; + } + /** + * Returns a specific byte in this bit array. If the byte index is out of + * range then `undefined` is returned. + * + * When returning the final byte of a bit array with a bit size that's not a + * multiple of 8, the content of the unused low bits are undefined. + * + * @param {number} index + * @returns {number | undefined} + */ + byteAt(index5) { + if (index5 < 0 || index5 >= this.byteSize) { + return void 0; + } + return bitArrayByteAt(this.rawBuffer, this.bitOffset, index5); + } + /** @internal */ + equals(other) { + if (this.bitSize !== other.bitSize) { + return false; + } + const wholeByteCount = Math.trunc(this.bitSize / 8); + if (this.bitOffset === 0 && other.bitOffset === 0) { + for (let i = 0; i < wholeByteCount; i++) { + if (this.rawBuffer[i] !== other.rawBuffer[i]) { + return false; + } + } + const trailingBitsCount = this.bitSize % 8; + if (trailingBitsCount) { + const unusedLowBitCount = 8 - trailingBitsCount; + if (this.rawBuffer[wholeByteCount] >> unusedLowBitCount !== other.rawBuffer[wholeByteCount] >> unusedLowBitCount) { + return false; + } + } + } else { + for (let i = 0; i < wholeByteCount; i++) { + const a2 = bitArrayByteAt(this.rawBuffer, this.bitOffset, i); + const b = bitArrayByteAt(other.rawBuffer, other.bitOffset, i); + if (a2 !== b) { + return false; + } + } + const trailingBitsCount = this.bitSize % 8; + if (trailingBitsCount) { + const a2 = bitArrayByteAt( + this.rawBuffer, + this.bitOffset, + wholeByteCount + ); + const b = bitArrayByteAt( + other.rawBuffer, + other.bitOffset, + wholeByteCount + ); + const unusedLowBitCount = 8 - trailingBitsCount; + if (a2 >> unusedLowBitCount !== b >> unusedLowBitCount) { + return false; + } + } + } + return true; + } + /** + * Returns this bit array's internal buffer. + * + * @deprecated Use `BitArray.byteAt()` or `BitArray.rawBuffer` instead. + * + * @returns {Uint8Array} + */ + get buffer() { + bitArrayPrintDeprecationWarning( + "buffer", + "Use BitArray.byteAt() or BitArray.rawBuffer instead" + ); + if (this.bitOffset !== 0 || this.bitSize % 8 !== 0) { + throw new globalThis.Error( + "BitArray.buffer does not support unaligned bit arrays" + ); + } + return this.rawBuffer; + } + /** + * Returns the length in bytes of this bit array's internal buffer. + * + * @deprecated Use `BitArray.bitSize` or `BitArray.byteSize` instead. + * + * @returns {number} + */ + get length() { + bitArrayPrintDeprecationWarning( + "length", + "Use BitArray.bitSize or BitArray.byteSize instead" + ); + if (this.bitOffset !== 0 || this.bitSize % 8 !== 0) { + throw new globalThis.Error( + "BitArray.length does not support unaligned bit arrays" + ); + } + return this.rawBuffer.length; + } +}; +function bitArrayByteAt(buffer, bitOffset, index5) { + if (bitOffset === 0) { + return buffer[index5] ?? 0; + } else { + const a2 = buffer[index5] << bitOffset & 255; + const b = buffer[index5 + 1] >> 8 - bitOffset; + return a2 | b; + } +} +var UtfCodepoint = class { + constructor(value3) { + this.value = value3; + } +}; +var isBitArrayDeprecationMessagePrinted = {}; +function bitArrayPrintDeprecationWarning(name6, message2) { + if (isBitArrayDeprecationMessagePrinted[name6]) { + return; + } + console.warn( + `Deprecated BitArray.${name6} property used in JavaScript FFI code. ${message2}.` + ); + isBitArrayDeprecationMessagePrinted[name6] = true; +} +function bitArraySlice(bitArray, start6, end) { + end ??= bitArray.bitSize; + bitArrayValidateRange(bitArray, start6, end); + if (start6 === end) { + return new BitArray(new Uint8Array()); + } + if (start6 === 0 && end === bitArray.bitSize) { + return bitArray; + } + start6 += bitArray.bitOffset; + end += bitArray.bitOffset; + const startByteIndex = Math.trunc(start6 / 8); + const endByteIndex = Math.trunc((end + 7) / 8); + const byteLength = endByteIndex - startByteIndex; + let buffer; + if (startByteIndex === 0 && byteLength === bitArray.rawBuffer.byteLength) { + buffer = bitArray.rawBuffer; + } else { + buffer = new Uint8Array( + bitArray.rawBuffer.buffer, + bitArray.rawBuffer.byteOffset + startByteIndex, + byteLength + ); + } + return new BitArray(buffer, end - start6, start6 % 8); +} +function bitArraySliceToInt(bitArray, start6, end, isBigEndian, isSigned) { + bitArrayValidateRange(bitArray, start6, end); + if (start6 === end) { + return 0; + } + start6 += bitArray.bitOffset; + end += bitArray.bitOffset; + const isStartByteAligned = start6 % 8 === 0; + const isEndByteAligned = end % 8 === 0; + if (isStartByteAligned && isEndByteAligned) { + return intFromAlignedSlice( + bitArray, + start6 / 8, + end / 8, + isBigEndian, + isSigned + ); + } + const size3 = end - start6; + const startByteIndex = Math.trunc(start6 / 8); + const endByteIndex = Math.trunc((end - 1) / 8); + if (startByteIndex == endByteIndex) { + const mask2 = 255 >> start6 % 8; + const unusedLowBitCount = (8 - end % 8) % 8; + let value3 = (bitArray.rawBuffer[startByteIndex] & mask2) >> unusedLowBitCount; + if (isSigned) { + const highBit = 2 ** (size3 - 1); + if (value3 >= highBit) { + value3 -= highBit * 2; + } + } + return value3; + } + if (size3 <= 53) { + return intFromUnalignedSliceUsingNumber( + bitArray.rawBuffer, + start6, + end, + isBigEndian, + isSigned + ); + } else { + return intFromUnalignedSliceUsingBigInt( + bitArray.rawBuffer, + start6, + end, + isBigEndian, + isSigned + ); + } +} +function intFromAlignedSlice(bitArray, start6, end, isBigEndian, isSigned) { + const byteSize = end - start6; + if (byteSize <= 6) { + return intFromAlignedSliceUsingNumber( + bitArray.rawBuffer, + start6, + end, + isBigEndian, + isSigned + ); + } else { + return intFromAlignedSliceUsingBigInt( + bitArray.rawBuffer, + start6, + end, + isBigEndian, + isSigned + ); + } +} +function intFromAlignedSliceUsingNumber(buffer, start6, end, isBigEndian, isSigned) { + const byteSize = end - start6; + let value3 = 0; + if (isBigEndian) { + for (let i = start6; i < end; i++) { + value3 *= 256; + value3 += buffer[i]; + } + } else { + for (let i = end - 1; i >= start6; i--) { + value3 *= 256; + value3 += buffer[i]; + } + } + if (isSigned) { + const highBit = 2 ** (byteSize * 8 - 1); + if (value3 >= highBit) { + value3 -= highBit * 2; + } + } + return value3; +} +function intFromAlignedSliceUsingBigInt(buffer, start6, end, isBigEndian, isSigned) { + const byteSize = end - start6; + let value3 = 0n; + if (isBigEndian) { + for (let i = start6; i < end; i++) { + value3 *= 256n; + value3 += BigInt(buffer[i]); + } + } else { + for (let i = end - 1; i >= start6; i--) { + value3 *= 256n; + value3 += BigInt(buffer[i]); + } + } + if (isSigned) { + const highBit = 1n << BigInt(byteSize * 8 - 1); + if (value3 >= highBit) { + value3 -= highBit * 2n; + } + } + return Number(value3); +} +function intFromUnalignedSliceUsingNumber(buffer, start6, end, isBigEndian, isSigned) { + const isStartByteAligned = start6 % 8 === 0; + let size3 = end - start6; + let byteIndex = Math.trunc(start6 / 8); + let value3 = 0; + if (isBigEndian) { + if (!isStartByteAligned) { + const leadingBitsCount = 8 - start6 % 8; + value3 = buffer[byteIndex++] & (1 << leadingBitsCount) - 1; + size3 -= leadingBitsCount; + } + while (size3 >= 8) { + value3 *= 256; + value3 += buffer[byteIndex++]; + size3 -= 8; + } + if (size3 > 0) { + value3 *= 2 ** size3; + value3 += buffer[byteIndex] >> 8 - size3; + } + } else { + if (isStartByteAligned) { + let size4 = end - start6; + let scale = 1; + while (size4 >= 8) { + value3 += buffer[byteIndex++] * scale; + scale *= 256; + size4 -= 8; + } + value3 += (buffer[byteIndex] >> 8 - size4) * scale; + } else { + const highBitsCount = start6 % 8; + const lowBitsCount = 8 - highBitsCount; + let size4 = end - start6; + let scale = 1; + while (size4 >= 8) { + const byte = buffer[byteIndex] << highBitsCount | buffer[byteIndex + 1] >> lowBitsCount; + value3 += (byte & 255) * scale; + scale *= 256; + size4 -= 8; + byteIndex++; + } + if (size4 > 0) { + const lowBitsUsed = size4 - Math.max(0, size4 - lowBitsCount); + let trailingByte = (buffer[byteIndex] & (1 << lowBitsCount) - 1) >> lowBitsCount - lowBitsUsed; + size4 -= lowBitsUsed; + if (size4 > 0) { + trailingByte *= 2 ** size4; + trailingByte += buffer[byteIndex + 1] >> 8 - size4; + } + value3 += trailingByte * scale; + } + } + } + if (isSigned) { + const highBit = 2 ** (end - start6 - 1); + if (value3 >= highBit) { + value3 -= highBit * 2; + } + } + return value3; +} +function intFromUnalignedSliceUsingBigInt(buffer, start6, end, isBigEndian, isSigned) { + const isStartByteAligned = start6 % 8 === 0; + let size3 = end - start6; + let byteIndex = Math.trunc(start6 / 8); + let value3 = 0n; + if (isBigEndian) { + if (!isStartByteAligned) { + const leadingBitsCount = 8 - start6 % 8; + value3 = BigInt(buffer[byteIndex++] & (1 << leadingBitsCount) - 1); + size3 -= leadingBitsCount; + } + while (size3 >= 8) { + value3 *= 256n; + value3 += BigInt(buffer[byteIndex++]); + size3 -= 8; + } + if (size3 > 0) { + value3 <<= BigInt(size3); + value3 += BigInt(buffer[byteIndex] >> 8 - size3); + } + } else { + if (isStartByteAligned) { + let size4 = end - start6; + let shift = 0n; + while (size4 >= 8) { + value3 += BigInt(buffer[byteIndex++]) << shift; + shift += 8n; + size4 -= 8; + } + value3 += BigInt(buffer[byteIndex] >> 8 - size4) << shift; + } else { + const highBitsCount = start6 % 8; + const lowBitsCount = 8 - highBitsCount; + let size4 = end - start6; + let shift = 0n; + while (size4 >= 8) { + const byte = buffer[byteIndex] << highBitsCount | buffer[byteIndex + 1] >> lowBitsCount; + value3 += BigInt(byte & 255) << shift; + shift += 8n; + size4 -= 8; + byteIndex++; + } + if (size4 > 0) { + const lowBitsUsed = size4 - Math.max(0, size4 - lowBitsCount); + let trailingByte = (buffer[byteIndex] & (1 << lowBitsCount) - 1) >> lowBitsCount - lowBitsUsed; + size4 -= lowBitsUsed; + if (size4 > 0) { + trailingByte <<= size4; + trailingByte += buffer[byteIndex + 1] >> 8 - size4; + } + value3 += BigInt(trailingByte) << shift; + } + } + } + if (isSigned) { + const highBit = 2n ** BigInt(end - start6 - 1); + if (value3 >= highBit) { + value3 -= highBit * 2n; + } + } + return Number(value3); +} +function bitArrayValidateRange(bitArray, start6, end) { + if (start6 < 0 || start6 > bitArray.bitSize || end < start6 || end > bitArray.bitSize) { + const msg = `Invalid bit array slice: start = ${start6}, end = ${end}, bit size = ${bitArray.bitSize}`; + throw new globalThis.Error(msg); + } +} +var Result = class _Result extends CustomType { + // @internal + static isResult(data) { + return data instanceof _Result; + } +}; +var Ok = class extends Result { + constructor(value3) { + super(); + this[0] = value3; + } + // @internal + isOk() { + return true; + } +}; +var Error = class extends Result { + constructor(detail) { + super(); + this[0] = detail; + } + // @internal + isOk() { + return false; + } +}; +function isEqual(x, y) { + let values3 = [x, y]; + while (values3.length) { + let a2 = values3.pop(); + let b = values3.pop(); + if (a2 === b) continue; + if (!isObject(a2) || !isObject(b)) return false; + let unequal = !structurallyCompatibleObjects(a2, b) || unequalDates(a2, b) || unequalBuffers(a2, b) || unequalArrays(a2, b) || unequalMaps(a2, b) || unequalSets(a2, b) || unequalRegExps(a2, b); + if (unequal) return false; + const proto = Object.getPrototypeOf(a2); + if (proto !== null && typeof proto.equals === "function") { + try { + if (a2.equals(b)) continue; + else return false; + } catch { + } + } + let [keys2, get5] = getters(a2); + for (let k of keys2(a2)) { + values3.push(get5(a2, k), get5(b, k)); + } + } + return true; +} +function getters(object4) { + if (object4 instanceof Map) { + return [(x) => x.keys(), (x, y) => x.get(y)]; + } else { + let extra = object4 instanceof globalThis.Error ? ["message"] : []; + return [(x) => [...extra, ...Object.keys(x)], (x, y) => x[y]]; + } +} +function unequalDates(a2, b) { + return a2 instanceof Date && (a2 > b || a2 < b); +} +function unequalBuffers(a2, b) { + return !(a2 instanceof BitArray) && a2.buffer instanceof ArrayBuffer && a2.BYTES_PER_ELEMENT && !(a2.byteLength === b.byteLength && a2.every((n, i) => n === b[i])); +} +function unequalArrays(a2, b) { + return Array.isArray(a2) && a2.length !== b.length; +} +function unequalMaps(a2, b) { + return a2 instanceof Map && a2.size !== b.size; +} +function unequalSets(a2, b) { + return a2 instanceof Set && (a2.size != b.size || [...a2].some((e) => !b.has(e))); +} +function unequalRegExps(a2, b) { + return a2 instanceof RegExp && (a2.source !== b.source || a2.flags !== b.flags); +} +function isObject(a2) { + return typeof a2 === "object" && a2 !== null; +} +function structurallyCompatibleObjects(a2, b) { + if (typeof a2 !== "object" && typeof b !== "object" && (!a2 || !b)) + return false; + let nonstructural = [Promise, WeakSet, WeakMap, Function]; + if (nonstructural.some((c) => a2 instanceof c)) return false; + return a2.constructor === b.constructor; +} +function divideFloat(a2, b) { + if (b === 0) { + return 0; + } else { + return a2 / b; + } +} +function makeError(variant, file, module, line, fn, message2, extra) { + let error = new globalThis.Error(message2); + error.gleam_error = variant; + error.file = file; + error.module = module; + error.line = line; + error.function = fn; + error.fn = fn; + for (let k in extra) error[k] = extra[k]; + return error; +} + +// build/dev/javascript/gleam_stdlib/gleam/option.mjs +var Some = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var None = class extends CustomType { +}; +function to_result(option3, e) { + if (option3 instanceof Some) { + let a2 = option3[0]; + return new Ok(a2); + } else { + return new Error(e); + } +} +function from_result(result) { + if (result instanceof Ok) { + let a2 = result[0]; + return new Some(a2); + } else { + return new None(); + } +} +function unwrap(option3, default$2) { + if (option3 instanceof Some) { + let x = option3[0]; + return x; + } else { + return default$2; + } +} + +// build/dev/javascript/gleam_stdlib/dict.mjs +var referenceMap = /* @__PURE__ */ new WeakMap(); +var tempDataView = /* @__PURE__ */ new DataView( + /* @__PURE__ */ new ArrayBuffer(8) +); +var referenceUID = 0; +function hashByReference(o) { + const known = referenceMap.get(o); + if (known !== void 0) { + return known; + } + const hash = referenceUID++; + if (referenceUID === 2147483647) { + referenceUID = 0; + } + referenceMap.set(o, hash); + return hash; +} +function hashMerge(a2, b) { + return a2 ^ b + 2654435769 + (a2 << 6) + (a2 >> 2) | 0; +} +function hashString(s) { + let hash = 0; + const len = s.length; + for (let i = 0; i < len; i++) { + hash = Math.imul(31, hash) + s.charCodeAt(i) | 0; + } + return hash; +} +function hashNumber(n) { + tempDataView.setFloat64(0, n); + const i = tempDataView.getInt32(0); + const j = tempDataView.getInt32(4); + return Math.imul(73244475, i >> 16 ^ i) ^ j; +} +function hashBigInt(n) { + return hashString(n.toString()); +} +function hashObject(o) { + const proto = Object.getPrototypeOf(o); + if (proto !== null && typeof proto.hashCode === "function") { + try { + const code2 = o.hashCode(o); + if (typeof code2 === "number") { + return code2; + } + } catch { + } + } + if (o instanceof Promise || o instanceof WeakSet || o instanceof WeakMap) { + return hashByReference(o); + } + if (o instanceof Date) { + return hashNumber(o.getTime()); + } + let h = 0; + if (o instanceof ArrayBuffer) { + o = new Uint8Array(o); + } + if (Array.isArray(o) || o instanceof Uint8Array) { + for (let i = 0; i < o.length; i++) { + h = Math.imul(31, h) + getHash(o[i]) | 0; + } + } else if (o instanceof Set) { + o.forEach((v) => { + h = h + getHash(v) | 0; + }); + } else if (o instanceof Map) { + o.forEach((v, k) => { + h = h + hashMerge(getHash(v), getHash(k)) | 0; + }); + } else { + const keys2 = Object.keys(o); + for (let i = 0; i < keys2.length; i++) { + const k = keys2[i]; + const v = o[k]; + h = h + hashMerge(getHash(v), hashString(k)) | 0; + } + } + return h; +} +function getHash(u) { + if (u === null) return 1108378658; + if (u === void 0) return 1108378659; + if (u === true) return 1108378657; + if (u === false) return 1108378656; + switch (typeof u) { + case "number": + return hashNumber(u); + case "string": + return hashString(u); + case "bigint": + return hashBigInt(u); + case "object": + return hashObject(u); + case "symbol": + return hashByReference(u); + case "function": + return hashByReference(u); + default: + return 0; + } +} +var SHIFT = 5; +var BUCKET_SIZE = Math.pow(2, SHIFT); +var MASK = BUCKET_SIZE - 1; +var MAX_INDEX_NODE = BUCKET_SIZE / 2; +var MIN_ARRAY_NODE = BUCKET_SIZE / 4; +var ENTRY = 0; +var ARRAY_NODE = 1; +var INDEX_NODE = 2; +var COLLISION_NODE = 3; +var EMPTY = { + type: INDEX_NODE, + bitmap: 0, + array: [] +}; +function mask(hash, shift) { + return hash >>> shift & MASK; +} +function bitpos(hash, shift) { + return 1 << mask(hash, shift); +} +function bitcount(x) { + x -= x >> 1 & 1431655765; + x = (x & 858993459) + (x >> 2 & 858993459); + x = x + (x >> 4) & 252645135; + x += x >> 8; + x += x >> 16; + return x & 127; +} +function index(bitmap, bit) { + return bitcount(bitmap & bit - 1); +} +function cloneAndSet(arr, at, val) { + const len = arr.length; + const out = new Array(len); + for (let i = 0; i < len; ++i) { + out[i] = arr[i]; + } + out[at] = val; + return out; +} +function spliceIn(arr, at, val) { + const len = arr.length; + const out = new Array(len + 1); + let i = 0; + let g = 0; + while (i < at) { + out[g++] = arr[i++]; + } + out[g++] = val; + while (i < len) { + out[g++] = arr[i++]; + } + return out; +} +function spliceOut(arr, at) { + const len = arr.length; + const out = new Array(len - 1); + let i = 0; + let g = 0; + while (i < at) { + out[g++] = arr[i++]; + } + ++i; + while (i < len) { + out[g++] = arr[i++]; + } + return out; +} +function createNode(shift, key1, val1, key2hash, key2, val2) { + const key1hash = getHash(key1); + if (key1hash === key2hash) { + return { + type: COLLISION_NODE, + hash: key1hash, + array: [ + { type: ENTRY, k: key1, v: val1 }, + { type: ENTRY, k: key2, v: val2 } + ] + }; + } + const addedLeaf = { val: false }; + return assoc( + assocIndex(EMPTY, shift, key1hash, key1, val1, addedLeaf), + shift, + key2hash, + key2, + val2, + addedLeaf + ); +} +function assoc(root3, shift, hash, key, val, addedLeaf) { + switch (root3.type) { + case ARRAY_NODE: + return assocArray(root3, shift, hash, key, val, addedLeaf); + case INDEX_NODE: + return assocIndex(root3, shift, hash, key, val, addedLeaf); + case COLLISION_NODE: + return assocCollision(root3, shift, hash, key, val, addedLeaf); + } +} +function assocArray(root3, shift, hash, key, val, addedLeaf) { + const idx = mask(hash, shift); + const node = root3.array[idx]; + if (node === void 0) { + addedLeaf.val = true; + return { + type: ARRAY_NODE, + size: root3.size + 1, + array: cloneAndSet(root3.array, idx, { type: ENTRY, k: key, v: val }) + }; + } + if (node.type === ENTRY) { + if (isEqual(key, node.k)) { + if (val === node.v) { + return root3; + } + return { + type: ARRAY_NODE, + size: root3.size, + array: cloneAndSet(root3.array, idx, { + type: ENTRY, + k: key, + v: val + }) + }; + } + addedLeaf.val = true; + return { + type: ARRAY_NODE, + size: root3.size, + array: cloneAndSet( + root3.array, + idx, + createNode(shift + SHIFT, node.k, node.v, hash, key, val) + ) + }; + } + const n = assoc(node, shift + SHIFT, hash, key, val, addedLeaf); + if (n === node) { + return root3; + } + return { + type: ARRAY_NODE, + size: root3.size, + array: cloneAndSet(root3.array, idx, n) + }; +} +function assocIndex(root3, shift, hash, key, val, addedLeaf) { + const bit = bitpos(hash, shift); + const idx = index(root3.bitmap, bit); + if ((root3.bitmap & bit) !== 0) { + const node = root3.array[idx]; + if (node.type !== ENTRY) { + const n = assoc(node, shift + SHIFT, hash, key, val, addedLeaf); + if (n === node) { + return root3; + } + return { + type: INDEX_NODE, + bitmap: root3.bitmap, + array: cloneAndSet(root3.array, idx, n) + }; + } + const nodeKey = node.k; + if (isEqual(key, nodeKey)) { + if (val === node.v) { + return root3; + } + return { + type: INDEX_NODE, + bitmap: root3.bitmap, + array: cloneAndSet(root3.array, idx, { + type: ENTRY, + k: key, + v: val + }) + }; + } + addedLeaf.val = true; + return { + type: INDEX_NODE, + bitmap: root3.bitmap, + array: cloneAndSet( + root3.array, + idx, + createNode(shift + SHIFT, nodeKey, node.v, hash, key, val) + ) + }; + } else { + const n = root3.array.length; + if (n >= MAX_INDEX_NODE) { + const nodes = new Array(32); + const jdx = mask(hash, shift); + nodes[jdx] = assocIndex(EMPTY, shift + SHIFT, hash, key, val, addedLeaf); + let j = 0; + let bitmap = root3.bitmap; + for (let i = 0; i < 32; i++) { + if ((bitmap & 1) !== 0) { + const node = root3.array[j++]; + nodes[i] = node; + } + bitmap = bitmap >>> 1; + } + return { + type: ARRAY_NODE, + size: n + 1, + array: nodes + }; + } else { + const newArray = spliceIn(root3.array, idx, { + type: ENTRY, + k: key, + v: val + }); + addedLeaf.val = true; + return { + type: INDEX_NODE, + bitmap: root3.bitmap | bit, + array: newArray + }; + } + } +} +function assocCollision(root3, shift, hash, key, val, addedLeaf) { + if (hash === root3.hash) { + const idx = collisionIndexOf(root3, key); + if (idx !== -1) { + const entry = root3.array[idx]; + if (entry.v === val) { + return root3; + } + return { + type: COLLISION_NODE, + hash, + array: cloneAndSet(root3.array, idx, { type: ENTRY, k: key, v: val }) + }; + } + const size3 = root3.array.length; + addedLeaf.val = true; + return { + type: COLLISION_NODE, + hash, + array: cloneAndSet(root3.array, size3, { type: ENTRY, k: key, v: val }) + }; + } + return assoc( + { + type: INDEX_NODE, + bitmap: bitpos(root3.hash, shift), + array: [root3] + }, + shift, + hash, + key, + val, + addedLeaf + ); +} +function collisionIndexOf(root3, key) { + const size3 = root3.array.length; + for (let i = 0; i < size3; i++) { + if (isEqual(key, root3.array[i].k)) { + return i; + } + } + return -1; +} +function find(root3, shift, hash, key) { + switch (root3.type) { + case ARRAY_NODE: + return findArray(root3, shift, hash, key); + case INDEX_NODE: + return findIndex(root3, shift, hash, key); + case COLLISION_NODE: + return findCollision(root3, key); + } +} +function findArray(root3, shift, hash, key) { + const idx = mask(hash, shift); + const node = root3.array[idx]; + if (node === void 0) { + return void 0; + } + if (node.type !== ENTRY) { + return find(node, shift + SHIFT, hash, key); + } + if (isEqual(key, node.k)) { + return node; + } + return void 0; +} +function findIndex(root3, shift, hash, key) { + const bit = bitpos(hash, shift); + if ((root3.bitmap & bit) === 0) { + return void 0; + } + const idx = index(root3.bitmap, bit); + const node = root3.array[idx]; + if (node.type !== ENTRY) { + return find(node, shift + SHIFT, hash, key); + } + if (isEqual(key, node.k)) { + return node; + } + return void 0; +} +function findCollision(root3, key) { + const idx = collisionIndexOf(root3, key); + if (idx < 0) { + return void 0; + } + return root3.array[idx]; +} +function without(root3, shift, hash, key) { + switch (root3.type) { + case ARRAY_NODE: + return withoutArray(root3, shift, hash, key); + case INDEX_NODE: + return withoutIndex(root3, shift, hash, key); + case COLLISION_NODE: + return withoutCollision(root3, key); + } +} +function withoutArray(root3, shift, hash, key) { + const idx = mask(hash, shift); + const node = root3.array[idx]; + if (node === void 0) { + return root3; + } + let n = void 0; + if (node.type === ENTRY) { + if (!isEqual(node.k, key)) { + return root3; + } + } else { + n = without(node, shift + SHIFT, hash, key); + if (n === node) { + return root3; + } + } + if (n === void 0) { + if (root3.size <= MIN_ARRAY_NODE) { + const arr = root3.array; + const out = new Array(root3.size - 1); + let i = 0; + let j = 0; + let bitmap = 0; + while (i < idx) { + const nv = arr[i]; + if (nv !== void 0) { + out[j] = nv; + bitmap |= 1 << i; + ++j; + } + ++i; + } + ++i; + while (i < arr.length) { + const nv = arr[i]; + if (nv !== void 0) { + out[j] = nv; + bitmap |= 1 << i; + ++j; + } + ++i; + } + return { + type: INDEX_NODE, + bitmap, + array: out + }; + } + return { + type: ARRAY_NODE, + size: root3.size - 1, + array: cloneAndSet(root3.array, idx, n) + }; + } + return { + type: ARRAY_NODE, + size: root3.size, + array: cloneAndSet(root3.array, idx, n) + }; +} +function withoutIndex(root3, shift, hash, key) { + const bit = bitpos(hash, shift); + if ((root3.bitmap & bit) === 0) { + return root3; + } + const idx = index(root3.bitmap, bit); + const node = root3.array[idx]; + if (node.type !== ENTRY) { + const n = without(node, shift + SHIFT, hash, key); + if (n === node) { + return root3; + } + if (n !== void 0) { + return { + type: INDEX_NODE, + bitmap: root3.bitmap, + array: cloneAndSet(root3.array, idx, n) + }; + } + if (root3.bitmap === bit) { + return void 0; + } + return { + type: INDEX_NODE, + bitmap: root3.bitmap ^ bit, + array: spliceOut(root3.array, idx) + }; + } + if (isEqual(key, node.k)) { + if (root3.bitmap === bit) { + return void 0; + } + return { + type: INDEX_NODE, + bitmap: root3.bitmap ^ bit, + array: spliceOut(root3.array, idx) + }; + } + return root3; +} +function withoutCollision(root3, key) { + const idx = collisionIndexOf(root3, key); + if (idx < 0) { + return root3; + } + if (root3.array.length === 1) { + return void 0; + } + return { + type: COLLISION_NODE, + hash: root3.hash, + array: spliceOut(root3.array, idx) + }; +} +function forEach(root3, fn) { + if (root3 === void 0) { + return; + } + const items = root3.array; + const size3 = items.length; + for (let i = 0; i < size3; i++) { + const item3 = items[i]; + if (item3 === void 0) { + continue; + } + if (item3.type === ENTRY) { + fn(item3.v, item3.k); + continue; + } + forEach(item3, fn); + } +} +var Dict = class _Dict { + /** + * @template V + * @param {Record} o + * @returns {Dict} + */ + static fromObject(o) { + const keys2 = Object.keys(o); + let m = _Dict.new(); + for (let i = 0; i < keys2.length; i++) { + const k = keys2[i]; + m = m.set(k, o[k]); + } + return m; + } + /** + * @template K,V + * @param {Map} o + * @returns {Dict} + */ + static fromMap(o) { + let m = _Dict.new(); + o.forEach((v, k) => { + m = m.set(k, v); + }); + return m; + } + static new() { + return new _Dict(void 0, 0); + } + /** + * @param {undefined | Node} root + * @param {number} size + */ + constructor(root3, size3) { + this.root = root3; + this.size = size3; + } + /** + * @template NotFound + * @param {K} key + * @param {NotFound} notFound + * @returns {NotFound | V} + */ + get(key, notFound) { + if (this.root === void 0) { + return notFound; + } + const found = find(this.root, 0, getHash(key), key); + if (found === void 0) { + return notFound; + } + return found.v; + } + /** + * @param {K} key + * @param {V} val + * @returns {Dict} + */ + set(key, val) { + const addedLeaf = { val: false }; + const root3 = this.root === void 0 ? EMPTY : this.root; + const newRoot = assoc(root3, 0, getHash(key), key, val, addedLeaf); + if (newRoot === this.root) { + return this; + } + return new _Dict(newRoot, addedLeaf.val ? this.size + 1 : this.size); + } + /** + * @param {K} key + * @returns {Dict} + */ + delete(key) { + if (this.root === void 0) { + return this; + } + const newRoot = without(this.root, 0, getHash(key), key); + if (newRoot === this.root) { + return this; + } + if (newRoot === void 0) { + return _Dict.new(); + } + return new _Dict(newRoot, this.size - 1); + } + /** + * @param {K} key + * @returns {boolean} + */ + has(key) { + if (this.root === void 0) { + return false; + } + return find(this.root, 0, getHash(key), key) !== void 0; + } + /** + * @returns {[K,V][]} + */ + entries() { + if (this.root === void 0) { + return []; + } + const result = []; + this.forEach((v, k) => result.push([k, v])); + return result; + } + /** + * + * @param {(val:V,key:K)=>void} fn + */ + forEach(fn) { + forEach(this.root, fn); + } + hashCode() { + let h = 0; + this.forEach((v, k) => { + h = h + hashMerge(getHash(v), getHash(k)) | 0; + }); + return h; + } + /** + * @param {unknown} o + * @returns {boolean} + */ + equals(o) { + if (!(o instanceof _Dict) || this.size !== o.size) { + return false; + } + try { + this.forEach((v, k) => { + if (!isEqual(o.get(k, !v), v)) { + throw unequalDictSymbol; + } + }); + return true; + } catch (e) { + if (e === unequalDictSymbol) { + return false; + } + throw e; + } + } +}; +var unequalDictSymbol = /* @__PURE__ */ Symbol(); + +// build/dev/javascript/gleam_stdlib/gleam/order.mjs +var Lt = class extends CustomType { +}; +var Eq = class extends CustomType { +}; +var Gt = class extends CustomType { +}; + +// build/dev/javascript/gleam_stdlib/gleam/float.mjs +function negate(x) { + return -1 * x; +} +function round2(x) { + let $ = x >= 0; + if ($) { + return round(x); + } else { + return 0 - round(negate(x)); + } +} +function sum_loop(loop$numbers, loop$initial) { + while (true) { + let numbers = loop$numbers; + let initial = loop$initial; + if (numbers instanceof Empty) { + return initial; + } else { + let first3 = numbers.head; + let rest = numbers.tail; + loop$numbers = rest; + loop$initial = first3 + initial; + } + } +} +function sum(numbers) { + return sum_loop(numbers, 0); +} +function divide(a2, b) { + if (b === 0) { + return new Error(void 0); + } else { + let b$1 = b; + return new Ok(divideFloat(a2, b$1)); + } +} + +// build/dev/javascript/gleam_stdlib/gleam/int.mjs +function compare(a2, b) { + let $ = a2 === b; + if ($) { + return new Eq(); + } else { + let $1 = a2 < b; + if ($1) { + return new Lt(); + } else { + return new Gt(); + } + } +} +function min(a2, b) { + let $ = a2 < b; + if ($) { + return a2; + } else { + return b; + } +} +function max(a2, b) { + let $ = a2 > b; + if ($) { + return a2; + } else { + return b; + } +} +function add(a2, b) { + return a2 + b; +} +function subtract(a2, b) { + return a2 - b; +} + +// build/dev/javascript/gleam_stdlib/gleam/list.mjs +var Ascending = class extends CustomType { +}; +var Descending = class extends CustomType { +}; +function length_loop(loop$list, loop$count) { + while (true) { + let list4 = loop$list; + let count = loop$count; + if (list4 instanceof Empty) { + return count; + } else { + let list$1 = list4.tail; + loop$list = list$1; + loop$count = count + 1; + } + } +} +function length(list4) { + return length_loop(list4, 0); +} +function reverse_and_prepend(loop$prefix, loop$suffix) { + while (true) { + let prefix = loop$prefix; + let suffix = loop$suffix; + if (prefix instanceof Empty) { + return suffix; + } else { + let first$1 = prefix.head; + let rest$1 = prefix.tail; + loop$prefix = rest$1; + loop$suffix = prepend(first$1, suffix); + } + } +} +function reverse(list4) { + return reverse_and_prepend(list4, toList([])); +} +function is_empty(list4) { + return isEqual(list4, toList([])); +} +function first(list4) { + if (list4 instanceof Empty) { + return new Error(void 0); + } else { + let first$1 = list4.head; + return new Ok(first$1); + } +} +function filter_loop(loop$list, loop$fun, loop$acc) { + while (true) { + let list4 = loop$list; + let fun = loop$fun; + let acc = loop$acc; + if (list4 instanceof Empty) { + return reverse(acc); + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + let _block; + let $ = fun(first$1); + if ($) { + _block = prepend(first$1, acc); + } else { + _block = acc; + } + let new_acc = _block; + loop$list = rest$1; + loop$fun = fun; + loop$acc = new_acc; + } + } +} +function filter(list4, predicate) { + return filter_loop(list4, predicate, toList([])); +} +function filter_map_loop(loop$list, loop$fun, loop$acc) { + while (true) { + let list4 = loop$list; + let fun = loop$fun; + let acc = loop$acc; + if (list4 instanceof Empty) { + return reverse(acc); + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + let _block; + let $ = fun(first$1); + if ($ instanceof Ok) { + let first$2 = $[0]; + _block = prepend(first$2, acc); + } else { + _block = acc; + } + let new_acc = _block; + loop$list = rest$1; + loop$fun = fun; + loop$acc = new_acc; + } + } +} +function filter_map(list4, fun) { + return filter_map_loop(list4, fun, toList([])); +} +function map_loop(loop$list, loop$fun, loop$acc) { + while (true) { + let list4 = loop$list; + let fun = loop$fun; + let acc = loop$acc; + if (list4 instanceof Empty) { + return reverse(acc); + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + loop$list = rest$1; + loop$fun = fun; + loop$acc = prepend(fun(first$1), acc); + } + } +} +function map(list4, fun) { + return map_loop(list4, fun, toList([])); +} +function try_map_loop(loop$list, loop$fun, loop$acc) { + while (true) { + let list4 = loop$list; + let fun = loop$fun; + let acc = loop$acc; + if (list4 instanceof Empty) { + return new Ok(reverse(acc)); + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + let $ = fun(first$1); + if ($ instanceof Ok) { + let first$2 = $[0]; + loop$list = rest$1; + loop$fun = fun; + loop$acc = prepend(first$2, acc); + } else { + let error = $[0]; + return new Error(error); + } + } + } +} +function try_map(list4, fun) { + return try_map_loop(list4, fun, toList([])); +} +function append_loop(loop$first, loop$second) { + while (true) { + let first3 = loop$first; + let second2 = loop$second; + if (first3 instanceof Empty) { + return second2; + } else { + let first$1 = first3.head; + let rest$1 = first3.tail; + loop$first = rest$1; + loop$second = prepend(first$1, second2); + } + } +} +function append(first3, second2) { + return append_loop(reverse(first3), second2); +} +function prepend2(list4, item3) { + return prepend(item3, list4); +} +function flatten_loop(loop$lists, loop$acc) { + while (true) { + let lists = loop$lists; + let acc = loop$acc; + if (lists instanceof Empty) { + return reverse(acc); + } else { + let list4 = lists.head; + let further_lists = lists.tail; + loop$lists = further_lists; + loop$acc = reverse_and_prepend(list4, acc); + } + } +} +function flatten(lists) { + return flatten_loop(lists, toList([])); +} +function flat_map(list4, fun) { + return flatten(map(list4, fun)); +} +function fold(loop$list, loop$initial, loop$fun) { + while (true) { + let list4 = loop$list; + let initial = loop$initial; + let fun = loop$fun; + if (list4 instanceof Empty) { + return initial; + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + loop$list = rest$1; + loop$initial = fun(initial, first$1); + loop$fun = fun; + } + } +} +function fold_right(list4, initial, fun) { + if (list4 instanceof Empty) { + return initial; + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + return fun(fold_right(rest$1, initial, fun), first$1); + } +} +function index_fold_loop(loop$over, loop$acc, loop$with, loop$index) { + while (true) { + let over = loop$over; + let acc = loop$acc; + let with$ = loop$with; + let index5 = loop$index; + if (over instanceof Empty) { + return acc; + } else { + let first$1 = over.head; + let rest$1 = over.tail; + loop$over = rest$1; + loop$acc = with$(acc, first$1, index5); + loop$with = with$; + loop$index = index5 + 1; + } + } +} +function index_fold(list4, initial, fun) { + return index_fold_loop(list4, initial, fun, 0); +} +function find2(loop$list, loop$is_desired) { + while (true) { + let list4 = loop$list; + let is_desired = loop$is_desired; + if (list4 instanceof Empty) { + return new Error(void 0); + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + let $ = is_desired(first$1); + if ($) { + return new Ok(first$1); + } else { + loop$list = rest$1; + loop$is_desired = is_desired; + } + } + } +} +function find_map(loop$list, loop$fun) { + while (true) { + let list4 = loop$list; + let fun = loop$fun; + if (list4 instanceof Empty) { + return new Error(void 0); + } else { + let first$1 = list4.head; + let rest$1 = list4.tail; + let $ = fun(first$1); + if ($ instanceof Ok) { + let first$2 = $[0]; + return new Ok(first$2); + } else { + loop$list = rest$1; + loop$fun = fun; + } + } + } +} +function sequences(loop$list, loop$compare, loop$growing, loop$direction, loop$prev, loop$acc) { + while (true) { + let list4 = loop$list; + let compare5 = loop$compare; + let growing = loop$growing; + let direction = loop$direction; + let prev = loop$prev; + let acc = loop$acc; + let growing$1 = prepend(prev, growing); + if (list4 instanceof Empty) { + if (direction instanceof Ascending) { + return prepend(reverse(growing$1), acc); + } else { + return prepend(growing$1, acc); + } + } else { + let new$1 = list4.head; + let rest$1 = list4.tail; + let $ = compare5(prev, new$1); + if (direction instanceof Ascending) { + if ($ instanceof Lt) { + loop$list = rest$1; + loop$compare = compare5; + loop$growing = growing$1; + loop$direction = direction; + loop$prev = new$1; + loop$acc = acc; + } else if ($ instanceof Eq) { + loop$list = rest$1; + loop$compare = compare5; + loop$growing = growing$1; + loop$direction = direction; + loop$prev = new$1; + loop$acc = acc; + } else { + let _block; + if (direction instanceof Ascending) { + _block = prepend(reverse(growing$1), acc); + } else { + _block = prepend(growing$1, acc); + } + let acc$1 = _block; + if (rest$1 instanceof Empty) { + return prepend(toList([new$1]), acc$1); + } else { + let next2 = rest$1.head; + let rest$2 = rest$1.tail; + let _block$1; + let $1 = compare5(new$1, next2); + if ($1 instanceof Lt) { + _block$1 = new Ascending(); + } else if ($1 instanceof Eq) { + _block$1 = new Ascending(); + } else { + _block$1 = new Descending(); + } + let direction$1 = _block$1; + loop$list = rest$2; + loop$compare = compare5; + loop$growing = toList([new$1]); + loop$direction = direction$1; + loop$prev = next2; + loop$acc = acc$1; + } + } + } else if ($ instanceof Lt) { + let _block; + if (direction instanceof Ascending) { + _block = prepend(reverse(growing$1), acc); + } else { + _block = prepend(growing$1, acc); + } + let acc$1 = _block; + if (rest$1 instanceof Empty) { + return prepend(toList([new$1]), acc$1); + } else { + let next2 = rest$1.head; + let rest$2 = rest$1.tail; + let _block$1; + let $1 = compare5(new$1, next2); + if ($1 instanceof Lt) { + _block$1 = new Ascending(); + } else if ($1 instanceof Eq) { + _block$1 = new Ascending(); + } else { + _block$1 = new Descending(); + } + let direction$1 = _block$1; + loop$list = rest$2; + loop$compare = compare5; + loop$growing = toList([new$1]); + loop$direction = direction$1; + loop$prev = next2; + loop$acc = acc$1; + } + } else if ($ instanceof Eq) { + let _block; + if (direction instanceof Ascending) { + _block = prepend(reverse(growing$1), acc); + } else { + _block = prepend(growing$1, acc); + } + let acc$1 = _block; + if (rest$1 instanceof Empty) { + return prepend(toList([new$1]), acc$1); + } else { + let next2 = rest$1.head; + let rest$2 = rest$1.tail; + let _block$1; + let $1 = compare5(new$1, next2); + if ($1 instanceof Lt) { + _block$1 = new Ascending(); + } else if ($1 instanceof Eq) { + _block$1 = new Ascending(); + } else { + _block$1 = new Descending(); + } + let direction$1 = _block$1; + loop$list = rest$2; + loop$compare = compare5; + loop$growing = toList([new$1]); + loop$direction = direction$1; + loop$prev = next2; + loop$acc = acc$1; + } + } else { + loop$list = rest$1; + loop$compare = compare5; + loop$growing = growing$1; + loop$direction = direction; + loop$prev = new$1; + loop$acc = acc; + } + } + } +} +function merge_ascendings(loop$list1, loop$list2, loop$compare, loop$acc) { + while (true) { + let list1 = loop$list1; + let list22 = loop$list2; + let compare5 = loop$compare; + let acc = loop$acc; + if (list1 instanceof Empty) { + let list4 = list22; + return reverse_and_prepend(list4, acc); + } else if (list22 instanceof Empty) { + let list4 = list1; + return reverse_and_prepend(list4, acc); + } else { + let first1 = list1.head; + let rest1 = list1.tail; + let first22 = list22.head; + let rest2 = list22.tail; + let $ = compare5(first1, first22); + if ($ instanceof Lt) { + loop$list1 = rest1; + loop$list2 = list22; + loop$compare = compare5; + loop$acc = prepend(first1, acc); + } else if ($ instanceof Eq) { + loop$list1 = list1; + loop$list2 = rest2; + loop$compare = compare5; + loop$acc = prepend(first22, acc); + } else { + loop$list1 = list1; + loop$list2 = rest2; + loop$compare = compare5; + loop$acc = prepend(first22, acc); + } + } + } +} +function merge_ascending_pairs(loop$sequences, loop$compare, loop$acc) { + while (true) { + let sequences2 = loop$sequences; + let compare5 = loop$compare; + let acc = loop$acc; + if (sequences2 instanceof Empty) { + return reverse(acc); + } else { + let $ = sequences2.tail; + if ($ instanceof Empty) { + let sequence = sequences2.head; + return reverse(prepend(reverse(sequence), acc)); + } else { + let ascending1 = sequences2.head; + let ascending2 = $.head; + let rest$1 = $.tail; + let descending = merge_ascendings( + ascending1, + ascending2, + compare5, + toList([]) + ); + loop$sequences = rest$1; + loop$compare = compare5; + loop$acc = prepend(descending, acc); + } + } + } +} +function merge_descendings(loop$list1, loop$list2, loop$compare, loop$acc) { + while (true) { + let list1 = loop$list1; + let list22 = loop$list2; + let compare5 = loop$compare; + let acc = loop$acc; + if (list1 instanceof Empty) { + let list4 = list22; + return reverse_and_prepend(list4, acc); + } else if (list22 instanceof Empty) { + let list4 = list1; + return reverse_and_prepend(list4, acc); + } else { + let first1 = list1.head; + let rest1 = list1.tail; + let first22 = list22.head; + let rest2 = list22.tail; + let $ = compare5(first1, first22); + if ($ instanceof Lt) { + loop$list1 = list1; + loop$list2 = rest2; + loop$compare = compare5; + loop$acc = prepend(first22, acc); + } else if ($ instanceof Eq) { + loop$list1 = rest1; + loop$list2 = list22; + loop$compare = compare5; + loop$acc = prepend(first1, acc); + } else { + loop$list1 = rest1; + loop$list2 = list22; + loop$compare = compare5; + loop$acc = prepend(first1, acc); + } + } + } +} +function merge_descending_pairs(loop$sequences, loop$compare, loop$acc) { + while (true) { + let sequences2 = loop$sequences; + let compare5 = loop$compare; + let acc = loop$acc; + if (sequences2 instanceof Empty) { + return reverse(acc); + } else { + let $ = sequences2.tail; + if ($ instanceof Empty) { + let sequence = sequences2.head; + return reverse(prepend(reverse(sequence), acc)); + } else { + let descending1 = sequences2.head; + let descending2 = $.head; + let rest$1 = $.tail; + let ascending = merge_descendings( + descending1, + descending2, + compare5, + toList([]) + ); + loop$sequences = rest$1; + loop$compare = compare5; + loop$acc = prepend(ascending, acc); + } + } + } +} +function merge_all(loop$sequences, loop$direction, loop$compare) { + while (true) { + let sequences2 = loop$sequences; + let direction = loop$direction; + let compare5 = loop$compare; + if (sequences2 instanceof Empty) { + return toList([]); + } else if (direction instanceof Ascending) { + let $ = sequences2.tail; + if ($ instanceof Empty) { + let sequence = sequences2.head; + return sequence; + } else { + let sequences$1 = merge_ascending_pairs(sequences2, compare5, toList([])); + loop$sequences = sequences$1; + loop$direction = new Descending(); + loop$compare = compare5; + } + } else { + let $ = sequences2.tail; + if ($ instanceof Empty) { + let sequence = sequences2.head; + return reverse(sequence); + } else { + let sequences$1 = merge_descending_pairs(sequences2, compare5, toList([])); + loop$sequences = sequences$1; + loop$direction = new Ascending(); + loop$compare = compare5; + } + } + } +} +function sort(list4, compare5) { + if (list4 instanceof Empty) { + return toList([]); + } else { + let $ = list4.tail; + if ($ instanceof Empty) { + let x = list4.head; + return toList([x]); + } else { + let x = list4.head; + let y = $.head; + let rest$1 = $.tail; + let _block; + let $1 = compare5(x, y); + if ($1 instanceof Lt) { + _block = new Ascending(); + } else if ($1 instanceof Eq) { + _block = new Ascending(); + } else { + _block = new Descending(); + } + let direction = _block; + let sequences$1 = sequences( + rest$1, + compare5, + toList([x]), + direction, + y, + toList([]) + ); + return merge_all(sequences$1, new Ascending(), compare5); + } + } +} +function key_find(keyword_list, desired_key) { + return find_map( + keyword_list, + (keyword) => { + let key = keyword[0]; + let value3 = keyword[1]; + let $ = isEqual(key, desired_key); + if ($) { + return new Ok(value3); + } else { + return new Error(void 0); + } + } + ); +} + +// build/dev/javascript/gleam_stdlib/gleam/string.mjs +function replace(string5, pattern, substitute) { + let _pipe = string5; + let _pipe$1 = identity(_pipe); + let _pipe$2 = string_replace(_pipe$1, pattern, substitute); + return identity(_pipe$2); +} +function slice(string5, idx, len) { + let $ = len < 0; + if ($) { + return ""; + } else { + let $1 = idx < 0; + if ($1) { + let translated_idx = string_length(string5) + idx; + let $2 = translated_idx < 0; + if ($2) { + return ""; + } else { + return string_slice(string5, translated_idx, len); + } + } else { + return string_slice(string5, idx, len); + } + } +} +function drop_end(string5, num_graphemes) { + let $ = num_graphemes < 0; + if ($) { + return string5; + } else { + return slice(string5, 0, string_length(string5) - num_graphemes); + } +} +function concat_loop(loop$strings, loop$accumulator) { + while (true) { + let strings = loop$strings; + let accumulator = loop$accumulator; + if (strings instanceof Empty) { + return accumulator; + } else { + let string5 = strings.head; + let strings$1 = strings.tail; + loop$strings = strings$1; + loop$accumulator = accumulator + string5; + } + } +} +function concat2(strings) { + return concat_loop(strings, ""); +} +function join_loop(loop$strings, loop$separator, loop$accumulator) { + while (true) { + let strings = loop$strings; + let separator2 = loop$separator; + let accumulator = loop$accumulator; + if (strings instanceof Empty) { + return accumulator; + } else { + let string5 = strings.head; + let strings$1 = strings.tail; + loop$strings = strings$1; + loop$separator = separator2; + loop$accumulator = accumulator + separator2 + string5; + } + } +} +function join(strings, separator2) { + if (strings instanceof Empty) { + return ""; + } else { + let first$1 = strings.head; + let rest = strings.tail; + return join_loop(rest, separator2, first$1); + } +} +function split2(x, substring) { + if (substring === "") { + return graphemes(x); + } else { + let _pipe = x; + let _pipe$1 = identity(_pipe); + let _pipe$2 = split(_pipe$1, substring); + return map(_pipe$2, identity); + } +} + +// build/dev/javascript/gleam_stdlib/gleam/dynamic/decode.mjs +var DecodeError = class extends CustomType { + constructor(expected, found, path2) { + super(); + this.expected = expected; + this.found = found; + this.path = path2; + } +}; +var Decoder = class extends CustomType { + constructor(function$) { + super(); + this.function = function$; + } +}; +function run(data, decoder2) { + let $ = decoder2.function(data); + let maybe_invalid_data = $[0]; + let errors = $[1]; + if (errors instanceof Empty) { + return new Ok(maybe_invalid_data); + } else { + return new Error(errors); + } +} +function success(data) { + return new Decoder((_) => { + return [data, toList([])]; + }); +} +function decode_dynamic(data) { + return [data, toList([])]; +} +function map2(decoder2, transformer) { + return new Decoder( + (d) => { + let $ = decoder2.function(d); + let data = $[0]; + let errors = $[1]; + return [transformer(data), errors]; + } + ); +} +function then$(decoder2, next2) { + return new Decoder( + (dynamic_data) => { + let $ = decoder2.function(dynamic_data); + let data = $[0]; + let errors = $[1]; + let decoder$1 = next2(data); + let $1 = decoder$1.function(dynamic_data); + let layer = $1; + let data$1 = $1[0]; + if (errors instanceof Empty) { + return layer; + } else { + return [data$1, errors]; + } + } + ); +} +function run_decoders(loop$data, loop$failure, loop$decoders) { + while (true) { + let data = loop$data; + let failure2 = loop$failure; + let decoders = loop$decoders; + if (decoders instanceof Empty) { + return failure2; + } else { + let decoder2 = decoders.head; + let decoders$1 = decoders.tail; + let $ = decoder2.function(data); + let layer = $; + let errors = $[1]; + if (errors instanceof Empty) { + return layer; + } else { + loop$data = data; + loop$failure = failure2; + loop$decoders = decoders$1; + } + } + } +} +function one_of(first3, alternatives) { + return new Decoder( + (dynamic_data) => { + let $ = first3.function(dynamic_data); + let layer = $; + let errors = $[1]; + if (errors instanceof Empty) { + return layer; + } else { + return run_decoders(dynamic_data, layer, alternatives); + } + } + ); +} +var dynamic = /* @__PURE__ */ new Decoder(decode_dynamic); +function decode_error(expected, found) { + return toList([ + new DecodeError(expected, classify_dynamic(found), toList([])) + ]); +} +function run_dynamic_function(data, name6, f) { + let $ = f(data); + if ($ instanceof Ok) { + let data$1 = $[0]; + return [data$1, toList([])]; + } else { + let zero = $[0]; + return [ + zero, + toList([new DecodeError(name6, classify_dynamic(data), toList([]))]) + ]; + } +} +function decode_bool(data) { + let $ = isEqual(identity(true), data); + if ($) { + return [true, toList([])]; + } else { + let $1 = isEqual(identity(false), data); + if ($1) { + return [false, toList([])]; + } else { + return [false, decode_error("Bool", data)]; + } + } +} +function decode_int(data) { + return run_dynamic_function(data, "Int", int); +} +function failure(zero, expected) { + return new Decoder((d) => { + return [zero, decode_error(expected, d)]; + }); +} +function new_primitive_decoder(name6, decoding_function) { + return new Decoder( + (d) => { + let $ = decoding_function(d); + if ($ instanceof Ok) { + let t = $[0]; + return [t, toList([])]; + } else { + let zero = $[0]; + return [ + zero, + toList([new DecodeError(name6, classify_dynamic(d), toList([]))]) + ]; + } + } + ); +} +var bool = /* @__PURE__ */ new Decoder(decode_bool); +var int2 = /* @__PURE__ */ new Decoder(decode_int); +function decode_string(data) { + return run_dynamic_function(data, "String", string); +} +var string2 = /* @__PURE__ */ new Decoder(decode_string); +function push_path(layer, path2) { + let decoder2 = one_of( + string2, + toList([ + (() => { + let _pipe = int2; + return map2(_pipe, to_string); + })() + ]) + ); + let path$1 = map( + path2, + (key) => { + let key$1 = identity(key); + let $ = run(key$1, decoder2); + if ($ instanceof Ok) { + let key$2 = $[0]; + return key$2; + } else { + return "<" + classify_dynamic(key$1) + ">"; + } + } + ); + let errors = map( + layer[1], + (error) => { + let _record = error; + return new DecodeError( + _record.expected, + _record.found, + append(path$1, error.path) + ); + } + ); + return [layer[0], errors]; +} +function index3(loop$path, loop$position, loop$inner, loop$data, loop$handle_miss) { + while (true) { + let path2 = loop$path; + let position = loop$position; + let inner = loop$inner; + let data = loop$data; + let handle_miss = loop$handle_miss; + if (path2 instanceof Empty) { + let _pipe = inner(data); + return push_path(_pipe, reverse(position)); + } else { + let key = path2.head; + let path$1 = path2.tail; + let $ = index2(data, key); + if ($ instanceof Ok) { + let $1 = $[0]; + if ($1 instanceof Some) { + let data$1 = $1[0]; + loop$path = path$1; + loop$position = prepend(key, position); + loop$inner = inner; + loop$data = data$1; + loop$handle_miss = handle_miss; + } else { + return handle_miss(data, prepend(key, position)); + } + } else { + let kind = $[0]; + let $1 = inner(data); + let default$2 = $1[0]; + let _pipe = [ + default$2, + toList([new DecodeError(kind, classify_dynamic(data), toList([]))]) + ]; + return push_path(_pipe, reverse(position)); + } + } + } +} +function subfield(field_path, field_decoder, next2) { + return new Decoder( + (data) => { + let $ = index3( + field_path, + toList([]), + field_decoder.function, + data, + (data2, position) => { + let $12 = field_decoder.function(data2); + let default$2 = $12[0]; + let _pipe = [ + default$2, + toList([new DecodeError("Field", "Nothing", toList([]))]) + ]; + return push_path(_pipe, reverse(position)); + } + ); + let out = $[0]; + let errors1 = $[1]; + let $1 = next2(out).function(data); + let out$1 = $1[0]; + let errors2 = $1[1]; + return [out$1, append(errors1, errors2)]; + } + ); +} +function field(field_name, field_decoder, next2) { + return subfield(toList([field_name]), field_decoder, next2); +} + +// build/dev/javascript/gleam_stdlib/gleam_stdlib.mjs +var Nil = void 0; +var NOT_FOUND = {}; +function identity(x) { + return x; +} +function to_string(term) { + return term.toString(); +} +function float_to_string(float4) { + const string5 = float4.toString().replace("+", ""); + if (string5.indexOf(".") >= 0) { + return string5; + } else { + const index5 = string5.indexOf("e"); + if (index5 >= 0) { + return string5.slice(0, index5) + ".0" + string5.slice(index5); + } else { + return string5 + ".0"; + } + } +} +function string_replace(string5, target, substitute) { + if (typeof string5.replaceAll !== "undefined") { + return string5.replaceAll(target, substitute); + } + return string5.replace( + // $& means the whole matched string + new RegExp(target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), + substitute + ); +} +function string_length(string5) { + if (string5 === "") { + return 0; + } + const iterator = graphemes_iterator(string5); + if (iterator) { + let i = 0; + for (const _ of iterator) { + i++; + } + return i; + } else { + return string5.match(/./gsu).length; + } +} +function graphemes(string5) { + const iterator = graphemes_iterator(string5); + if (iterator) { + return List.fromArray(Array.from(iterator).map((item3) => item3.segment)); + } else { + return List.fromArray(string5.match(/./gsu)); + } +} +var segmenter = void 0; +function graphemes_iterator(string5) { + if (globalThis.Intl && Intl.Segmenter) { + segmenter ||= new Intl.Segmenter(); + return segmenter.segment(string5)[Symbol.iterator](); + } +} +function pop_codeunit(str) { + return [str.charCodeAt(0) | 0, str.slice(1)]; +} +function lowercase(string5) { + return string5.toLowerCase(); +} +function split(xs, pattern) { + return List.fromArray(xs.split(pattern)); +} +function string_slice(string5, idx, len) { + if (len <= 0 || idx >= string5.length) { + return ""; + } + const iterator = graphemes_iterator(string5); + if (iterator) { + while (idx-- > 0) { + iterator.next(); + } + let result = ""; + while (len-- > 0) { + const v = iterator.next().value; + if (v === void 0) { + break; + } + result += v.segment; + } + return result; + } else { + return string5.match(/./gsu).slice(idx, idx + len).join(""); + } +} +function string_codeunit_slice(str, from2, length4) { + return str.slice(from2, from2 + length4); +} +function contains_string(haystack, needle) { + return haystack.indexOf(needle) >= 0; +} +function starts_with(haystack, needle) { + return haystack.startsWith(needle); +} +function split_once(haystack, needle) { + const index5 = haystack.indexOf(needle); + if (index5 >= 0) { + const before = haystack.slice(0, index5); + const after = haystack.slice(index5 + needle.length); + return new Ok([before, after]); + } else { + return new Error(Nil); + } +} +var unicode_whitespaces = [ + " ", + // Space + " ", + // Horizontal tab + "\n", + // Line feed + "\v", + // Vertical tab + "\f", + // Form feed + "\r", + // Carriage return + "\x85", + // Next line + "\u2028", + // Line separator + "\u2029" + // Paragraph separator +].join(""); +var trim_start_regex = /* @__PURE__ */ new RegExp( + `^[${unicode_whitespaces}]*` +); +var trim_end_regex = /* @__PURE__ */ new RegExp(`[${unicode_whitespaces}]*$`); +function round(float4) { + return Math.round(float4); +} +function new_map() { + return Dict.new(); +} +function map_size(map6) { + return map6.size; +} +function map_to_list(map6) { + return List.fromArray(map6.entries()); +} +function map_remove(key, map6) { + return map6.delete(key); +} +function map_get(map6, key) { + const value3 = map6.get(key, NOT_FOUND); + if (value3 === NOT_FOUND) { + return new Error(Nil); + } + return new Ok(value3); +} +function map_insert(key, value3, map6) { + return map6.set(key, value3); +} +function classify_dynamic(data) { + if (typeof data === "string") { + return "String"; + } else if (typeof data === "boolean") { + return "Bool"; + } else if (data instanceof Result) { + return "Result"; + } else if (data instanceof List) { + return "List"; + } else if (data instanceof BitArray) { + return "BitArray"; + } else if (data instanceof Dict) { + return "Dict"; + } else if (Number.isInteger(data)) { + return "Int"; + } else if (Array.isArray(data)) { + return `Array`; + } else if (typeof data === "number") { + return "Float"; + } else if (data === null) { + return "Nil"; + } else if (data === void 0) { + return "Nil"; + } else { + const type = typeof data; + return type.charAt(0).toUpperCase() + type.slice(1); + } +} +function index2(data, key) { + if (data instanceof Dict || data instanceof WeakMap || data instanceof Map) { + const token2 = {}; + const entry = data.get(key, token2); + if (entry === token2) return new Ok(new None()); + return new Ok(new Some(entry)); + } + const key_is_int = Number.isInteger(key); + if (key_is_int && key >= 0 && key < 8 && data instanceof List) { + let i = 0; + for (const value3 of data) { + if (i === key) return new Ok(new Some(value3)); + i++; + } + return new Error("Indexable"); + } + if (key_is_int && Array.isArray(data) || data && typeof data === "object" || data && Object.getPrototypeOf(data) === Object.prototype) { + if (key in data) return new Ok(new Some(data[key])); + return new Ok(new None()); + } + return new Error(key_is_int ? "Indexable" : "Dict"); +} +function int(data) { + if (Number.isInteger(data)) return new Ok(data); + return new Error(0); +} +function string(data) { + if (typeof data === "string") return new Ok(data); + return new Error(""); +} + +// build/dev/javascript/gleam_stdlib/gleam/dict.mjs +function is_empty2(dict2) { + return map_size(dict2) === 0; +} +function do_has_key(key, dict2) { + return !isEqual(map_get(dict2, key), new Error(void 0)); +} +function has_key(dict2, key) { + return do_has_key(key, dict2); +} +function insert(dict2, key, value3) { + return map_insert(key, value3, dict2); +} +function delete$(dict2, key) { + return map_remove(key, dict2); +} +function fold_loop(loop$list, loop$initial, loop$fun) { + while (true) { + let list4 = loop$list; + let initial = loop$initial; + let fun = loop$fun; + if (list4 instanceof Empty) { + return initial; + } else { + let rest = list4.tail; + let k = list4.head[0]; + let v = list4.head[1]; + loop$list = rest; + loop$initial = fun(initial, k, v); + loop$fun = fun; + } + } +} +function fold2(dict2, initial, fun) { + return fold_loop(map_to_list(dict2), initial, fun); +} +function do_filter(f, dict2) { + let insert$1 = (dict3, k, v) => { + let $ = f(k, v); + if ($) { + return insert(dict3, k, v); + } else { + return dict3; + } + }; + return fold2(dict2, new_map(), insert$1); +} +function filter2(dict2, predicate) { + return do_filter(predicate, dict2); +} + +// build/dev/javascript/gleam_stdlib/gleam/dynamic.mjs +function nil() { + return identity(void 0); +} + +// build/dev/javascript/gleam_stdlib/gleam/result.mjs +function is_ok(result) { + if (result instanceof Ok) { + return true; + } else { + return false; + } +} +function map3(result, fun) { + if (result instanceof Ok) { + let x = result[0]; + return new Ok(fun(x)); + } else { + let e = result[0]; + return new Error(e); + } +} +function map_error(result, fun) { + if (result instanceof Ok) { + let x = result[0]; + return new Ok(x); + } else { + let error = result[0]; + return new Error(fun(error)); + } +} +function try$(result, fun) { + if (result instanceof Ok) { + let x = result[0]; + return fun(x); + } else { + let e = result[0]; + return new Error(e); + } +} +function then$2(result, fun) { + return try$(result, fun); +} +function unwrap2(result, default$2) { + if (result instanceof Ok) { + let v = result[0]; + return v; + } else { + return default$2; + } +} +function unwrap_both(result) { + if (result instanceof Ok) { + let a2 = result[0]; + return a2; + } else { + let a2 = result[0]; + return a2; + } +} +function or(first3, second2) { + if (first3 instanceof Ok) { + return first3; + } else { + return second2; + } +} +function replace_error(result, error) { + if (result instanceof Ok) { + let x = result[0]; + return new Ok(x); + } else { + return new Error(error); + } +} + +// build/dev/javascript/gleam_stdlib/gleam/bool.mjs +function to_string2(bool4) { + if (bool4) { + return "True"; + } else { + return "False"; + } +} +function guard(requirement, consequence, alternative) { + if (requirement) { + return consequence; + } else { + return alternative(); + } +} + +// build/dev/javascript/gleam_stdlib/gleam/function.mjs +function identity2(x) { + return x; +} + +// build/dev/javascript/gleam_json/gleam_json_ffi.mjs +function json_to_string(json2) { + return JSON.stringify(json2); +} +function object(entries) { + return Object.fromEntries(entries); +} +function identity3(x) { + return x; +} +function do_null() { + return null; +} +function decode(string5) { + try { + const result = JSON.parse(string5); + return new Ok(result); + } catch (err) { + return new Error(getJsonDecodeError(err, string5)); + } +} +function getJsonDecodeError(stdErr, json2) { + if (isUnexpectedEndOfInput(stdErr)) return new UnexpectedEndOfInput(); + return toUnexpectedByteError(stdErr, json2); +} +function isUnexpectedEndOfInput(err) { + const unexpectedEndOfInputRegex = /((unexpected (end|eof))|(end of data)|(unterminated string)|(json( parse error|\.parse)\: expected '(\:|\}|\])'))/i; + return unexpectedEndOfInputRegex.test(err.message); +} +function toUnexpectedByteError(err, json2) { + let converters = [ + v8UnexpectedByteError, + oldV8UnexpectedByteError, + jsCoreUnexpectedByteError, + spidermonkeyUnexpectedByteError + ]; + for (let converter of converters) { + let result = converter(err, json2); + if (result) return result; + } + return new UnexpectedByte("", 0); +} +function v8UnexpectedByteError(err) { + const regex = /unexpected token '(.)', ".+" is not valid JSON/i; + const match = regex.exec(err.message); + if (!match) return null; + const byte = toHex(match[1]); + return new UnexpectedByte(byte, -1); +} +function oldV8UnexpectedByteError(err) { + const regex = /unexpected token (.) in JSON at position (\d+)/i; + const match = regex.exec(err.message); + if (!match) return null; + const byte = toHex(match[1]); + const position = Number(match[2]); + return new UnexpectedByte(byte, position); +} +function spidermonkeyUnexpectedByteError(err, json2) { + const regex = /(unexpected character|expected .*) at line (\d+) column (\d+)/i; + const match = regex.exec(err.message); + if (!match) return null; + const line = Number(match[2]); + const column = Number(match[3]); + const position = getPositionFromMultiline(line, column, json2); + const byte = toHex(json2[position]); + return new UnexpectedByte(byte, position); +} +function jsCoreUnexpectedByteError(err) { + const regex = /unexpected (identifier|token) "(.)"/i; + const match = regex.exec(err.message); + if (!match) return null; + const byte = toHex(match[2]); + return new UnexpectedByte(byte, 0); +} +function toHex(char) { + return "0x" + char.charCodeAt(0).toString(16).toUpperCase(); +} +function getPositionFromMultiline(line, column, string5) { + if (line === 1) return column - 1; + let currentLn = 1; + let position = 0; + string5.split("").find((char, idx) => { + if (char === "\n") currentLn += 1; + if (currentLn === line) { + position = idx + column; + return true; + } + return false; + }); + return position; +} + +// build/dev/javascript/gleam_json/gleam/json.mjs +var UnexpectedEndOfInput = class extends CustomType { +}; +var UnexpectedByte = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var UnableToDecode = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +function do_parse(json2, decoder2) { + return then$2( + decode(json2), + (dynamic_value) => { + let _pipe = run(dynamic_value, decoder2); + return map_error( + _pipe, + (var0) => { + return new UnableToDecode(var0); + } + ); + } + ); +} +function parse(json2, decoder2) { + return do_parse(json2, decoder2); +} +function to_string3(json2) { + return json_to_string(json2); +} +function string3(input3) { + return identity3(input3); +} +function bool2(input3) { + return identity3(input3); +} +function int3(input3) { + return identity3(input3); +} +function null$() { + return do_null(); +} +function object2(entries) { + return object(entries); +} + +// build/dev/javascript/gleam_stdlib/gleam/set.mjs +var Set2 = class extends CustomType { + constructor(dict2) { + super(); + this.dict = dict2; + } +}; +function new$() { + return new Set2(new_map()); +} +function size(set3) { + return map_size(set3.dict); +} +function contains(set3, member) { + let _pipe = set3.dict; + let _pipe$1 = map_get(_pipe, member); + return is_ok(_pipe$1); +} +function delete$2(set3, member) { + return new Set2(delete$(set3.dict, member)); +} +function filter3(set3, predicate) { + return new Set2(filter2(set3.dict, (m, _) => { + return predicate(m); + })); +} +var token = void 0; +function insert2(set3, member) { + return new Set2(insert(set3.dict, member, token)); +} +function from_list2(members) { + let dict2 = fold( + members, + new_map(), + (m, k) => { + return insert(m, k, token); + } + ); + return new Set2(dict2); +} + +// build/dev/javascript/lustre/lustre/internals/constants.ffi.mjs +var EMPTY_DICT = /* @__PURE__ */ Dict.new(); +var EMPTY_SET = /* @__PURE__ */ new$(); +var empty_dict = () => EMPTY_DICT; +var empty_set = () => EMPTY_SET; +var document2 = () => globalThis?.document; +var NAMESPACE_HTML = "http://www.w3.org/1999/xhtml"; +var ELEMENT_NODE = 1; +var TEXT_NODE = 3; +var DOCUMENT_FRAGMENT_NODE = 11; +var SUPPORTS_MOVE_BEFORE = !!globalThis.HTMLElement?.prototype?.moveBefore; + +// build/dev/javascript/lustre/lustre/internals/constants.mjs +var empty_list = /* @__PURE__ */ toList([]); +var option_none = /* @__PURE__ */ new None(); + +// build/dev/javascript/lustre/lustre/vdom/vattr.ffi.mjs +var GT = /* @__PURE__ */ new Gt(); +var LT = /* @__PURE__ */ new Lt(); +var EQ = /* @__PURE__ */ new Eq(); +function compare3(a2, b) { + if (a2.name === b.name) { + return EQ; + } else if (a2.name < b.name) { + return LT; + } else { + return GT; + } +} + +// build/dev/javascript/lustre/lustre/vdom/vattr.mjs +var Attribute = class extends CustomType { + constructor(kind, name6, value3) { + super(); + this.kind = kind; + this.name = name6; + this.value = value3; + } +}; +var Property = class extends CustomType { + constructor(kind, name6, value3) { + super(); + this.kind = kind; + this.name = name6; + this.value = value3; + } +}; +var Event2 = class extends CustomType { + constructor(kind, name6, handler, include, prevent_default3, stop_propagation, immediate2, debounce, throttle) { + super(); + this.kind = kind; + this.name = name6; + this.handler = handler; + this.include = include; + this.prevent_default = prevent_default3; + this.stop_propagation = stop_propagation; + this.immediate = immediate2; + this.debounce = debounce; + this.throttle = throttle; + } +}; +var Handler = class extends CustomType { + constructor(prevent_default3, stop_propagation, message2) { + super(); + this.prevent_default = prevent_default3; + this.stop_propagation = stop_propagation; + this.message = message2; + } +}; +var Never = class extends CustomType { + constructor(kind) { + super(); + this.kind = kind; + } +}; +function merge(loop$attributes, loop$merged) { + while (true) { + let attributes = loop$attributes; + let merged = loop$merged; + if (attributes instanceof Empty) { + return merged; + } else { + let $ = attributes.head; + if ($ instanceof Attribute) { + let $1 = $.name; + if ($1 === "") { + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = merged; + } else if ($1 === "class") { + let $2 = $.value; + if ($2 === "") { + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = merged; + } else { + let $3 = attributes.tail; + if ($3 instanceof Empty) { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } else { + let $4 = $3.head; + if ($4 instanceof Attribute) { + let $5 = $4.name; + if ($5 === "class") { + let kind = $.kind; + let class1 = $2; + let rest = $3.tail; + let class2 = $4.value; + let value3 = class1 + " " + class2; + let attribute$1 = new Attribute(kind, "class", value3); + loop$attributes = prepend(attribute$1, rest); + loop$merged = merged; + } else { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } else { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } + } + } else if ($1 === "style") { + let $2 = $.value; + if ($2 === "") { + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = merged; + } else { + let $3 = attributes.tail; + if ($3 instanceof Empty) { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } else { + let $4 = $3.head; + if ($4 instanceof Attribute) { + let $5 = $4.name; + if ($5 === "style") { + let kind = $.kind; + let style1 = $2; + let rest = $3.tail; + let style22 = $4.value; + let value3 = style1 + ";" + style22; + let attribute$1 = new Attribute(kind, "style", value3); + loop$attributes = prepend(attribute$1, rest); + loop$merged = merged; + } else { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } else { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } + } + } else { + let attribute$1 = $; + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } else { + let attribute$1 = $; + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } + } +} +function prepare(attributes) { + if (attributes instanceof Empty) { + return attributes; + } else { + let $ = attributes.tail; + if ($ instanceof Empty) { + return attributes; + } else { + let _pipe = attributes; + let _pipe$1 = sort(_pipe, (a2, b) => { + return compare3(b, a2); + }); + return merge(_pipe$1, empty_list); + } + } +} +var attribute_kind = 0; +function attribute(name6, value3) { + return new Attribute(attribute_kind, name6, value3); +} +var property_kind = 1; +function property(name6, value3) { + return new Property(property_kind, name6, value3); +} +var event_kind = 2; +function event(name6, handler, include, prevent_default3, stop_propagation, immediate2, debounce, throttle) { + return new Event2( + event_kind, + name6, + handler, + include, + prevent_default3, + stop_propagation, + immediate2, + debounce, + throttle + ); +} +var never_kind = 0; +var never = /* @__PURE__ */ new Never(never_kind); +var always_kind = 2; + +// build/dev/javascript/lustre/lustre/attribute.mjs +function attribute2(name6, value3) { + return attribute(name6, value3); +} +function property2(name6, value3) { + return property(name6, value3); +} +function class$(name6) { + return attribute2("class", name6); +} +function do_classes(loop$names, loop$class) { + while (true) { + let names = loop$names; + let class$2 = loop$class; + if (names instanceof Empty) { + return class$2; + } else { + let $ = names.head[1]; + if ($) { + let rest = names.tail; + let name$1 = names.head[0]; + return class$2 + name$1 + " " + do_classes(rest, class$2); + } else { + let rest = names.tail; + loop$names = rest; + loop$class = class$2; + } + } + } +} +function classes(names) { + return class$(do_classes(names, "")); +} +function style(property3, value3) { + if (property3 === "") { + return class$(""); + } else if (value3 === "") { + return class$(""); + } else { + return attribute2("style", property3 + ":" + value3 + ";"); + } +} +function do_styles(loop$properties, loop$styles) { + while (true) { + let properties = loop$properties; + let styles2 = loop$styles; + if (properties instanceof Empty) { + return styles2; + } else { + let $ = properties.head[0]; + if ($ === "") { + let rest = properties.tail; + loop$properties = rest; + loop$styles = styles2; + } else { + let $1 = properties.head[1]; + if ($1 === "") { + let rest = properties.tail; + loop$properties = rest; + loop$styles = styles2; + } else { + let rest = properties.tail; + let name$1 = $; + let value$1 = $1; + loop$properties = rest; + loop$styles = styles2 + name$1 + ":" + value$1 + ";"; + } + } + } + } +} +function styles(properties) { + return attribute2("style", do_styles(properties, "")); +} +function title(text4) { + return attribute2("title", text4); +} +function href(url) { + return attribute2("href", url); +} +function alt(text4) { + return attribute2("alt", text4); +} +function src(url) { + return attribute2("src", url); +} +function autocomplete(value3) { + return attribute2("autocomplete", value3); +} +function name(element_name) { + return attribute2("name", element_name); +} +function placeholder(text4) { + return attribute2("placeholder", text4); +} +function type_(control_type) { + return attribute2("type", control_type); +} +function value(control_value) { + return attribute2("value", control_value); +} +function role(name6) { + return attribute2("role", name6); +} + +// build/dev/javascript/lustre/lustre/effect.mjs +var Effect = class extends CustomType { + constructor(synchronous, before_paint2, after_paint2) { + super(); + this.synchronous = synchronous; + this.before_paint = before_paint2; + this.after_paint = after_paint2; + } +}; +var empty = /* @__PURE__ */ new Effect( + /* @__PURE__ */ toList([]), + /* @__PURE__ */ toList([]), + /* @__PURE__ */ toList([]) +); +function none() { + return empty; +} +function from(effect) { + let task = (actions) => { + let dispatch2 = actions.dispatch; + return effect(dispatch2); + }; + let _record = empty; + return new Effect(toList([task]), _record.before_paint, _record.after_paint); +} +function before_paint(effect) { + let task = (actions) => { + let root3 = actions.root(); + let dispatch2 = actions.dispatch; + return effect(dispatch2, root3); + }; + let _record = empty; + return new Effect(_record.synchronous, toList([task]), _record.after_paint); +} +function after_paint(effect) { + let task = (actions) => { + let root3 = actions.root(); + let dispatch2 = actions.dispatch; + return effect(dispatch2, root3); + }; + let _record = empty; + return new Effect(_record.synchronous, _record.before_paint, toList([task])); +} +function event2(name6, data) { + let task = (actions) => { + return actions.emit(name6, data); + }; + let _record = empty; + return new Effect(toList([task]), _record.before_paint, _record.after_paint); +} +function batch(effects) { + return fold( + effects, + empty, + (acc, eff) => { + return new Effect( + fold(eff.synchronous, acc.synchronous, prepend2), + fold(eff.before_paint, acc.before_paint, prepend2), + fold(eff.after_paint, acc.after_paint, prepend2) + ); + } + ); +} + +// build/dev/javascript/lustre/lustre/internals/mutable_map.ffi.mjs +function empty2() { + return null; +} +function get(map6, key) { + const value3 = map6?.get(key); + if (value3 != null) { + return new Ok(value3); + } else { + return new Error(void 0); + } +} +function insert3(map6, key, value3) { + map6 ??= /* @__PURE__ */ new Map(); + map6.set(key, value3); + return map6; +} +function remove(map6, key) { + map6?.delete(key); + return map6; +} + +// build/dev/javascript/lustre/lustre/vdom/path.mjs +var Root = class extends CustomType { +}; +var Key = class extends CustomType { + constructor(key, parent) { + super(); + this.key = key; + this.parent = parent; + } +}; +var Index = class extends CustomType { + constructor(index5, parent) { + super(); + this.index = index5; + this.parent = parent; + } +}; +function do_matches(loop$path, loop$candidates) { + while (true) { + let path2 = loop$path; + let candidates = loop$candidates; + if (candidates instanceof Empty) { + return false; + } else { + let candidate = candidates.head; + let rest = candidates.tail; + let $ = starts_with(path2, candidate); + if ($) { + return true; + } else { + loop$path = path2; + loop$candidates = rest; + } + } + } +} +function add3(parent, index5, key) { + if (key === "") { + return new Index(index5, parent); + } else { + return new Key(key, parent); + } +} +var root2 = /* @__PURE__ */ new Root(); +var separator_element = " "; +function do_to_string(loop$path, loop$acc) { + while (true) { + let path2 = loop$path; + let acc = loop$acc; + if (path2 instanceof Root) { + if (acc instanceof Empty) { + return ""; + } else { + let segments = acc.tail; + return concat2(segments); + } + } else if (path2 instanceof Key) { + let key = path2.key; + let parent = path2.parent; + loop$path = parent; + loop$acc = prepend(separator_element, prepend(key, acc)); + } else { + let index5 = path2.index; + let parent = path2.parent; + loop$path = parent; + loop$acc = prepend( + separator_element, + prepend(to_string(index5), acc) + ); + } + } +} +function to_string4(path2) { + return do_to_string(path2, toList([])); +} +function matches(path2, candidates) { + if (candidates instanceof Empty) { + return false; + } else { + return do_matches(to_string4(path2), candidates); + } +} +var separator_event = "\n"; +function event3(path2, event4) { + return do_to_string(path2, toList([separator_event, event4])); +} + +// build/dev/javascript/lustre/lustre/vdom/vnode.mjs +var Fragment = class extends CustomType { + constructor(kind, key, mapper, children, keyed_children, children_count) { + super(); + this.kind = kind; + this.key = key; + this.mapper = mapper; + this.children = children; + this.keyed_children = keyed_children; + this.children_count = children_count; + } +}; +var Element2 = class extends CustomType { + constructor(kind, key, mapper, namespace2, tag, attributes, children, keyed_children, self_closing, void$) { + super(); + this.kind = kind; + this.key = key; + this.mapper = mapper; + this.namespace = namespace2; + this.tag = tag; + this.attributes = attributes; + this.children = children; + this.keyed_children = keyed_children; + this.self_closing = self_closing; + this.void = void$; + } +}; +var Text = class extends CustomType { + constructor(kind, key, mapper, content3) { + super(); + this.kind = kind; + this.key = key; + this.mapper = mapper; + this.content = content3; + } +}; +var UnsafeInnerHtml = class extends CustomType { + constructor(kind, key, mapper, namespace2, tag, attributes, inner_html) { + super(); + this.kind = kind; + this.key = key; + this.mapper = mapper; + this.namespace = namespace2; + this.tag = tag; + this.attributes = attributes; + this.inner_html = inner_html; + } +}; +function is_void_element(tag, namespace2) { + if (namespace2 === "") { + if (tag === "area") { + return true; + } else if (tag === "base") { + return true; + } else if (tag === "br") { + return true; + } else if (tag === "col") { + return true; + } else if (tag === "embed") { + return true; + } else if (tag === "hr") { + return true; + } else if (tag === "img") { + return true; + } else if (tag === "input") { + return true; + } else if (tag === "link") { + return true; + } else if (tag === "meta") { + return true; + } else if (tag === "param") { + return true; + } else if (tag === "source") { + return true; + } else if (tag === "track") { + return true; + } else if (tag === "wbr") { + return true; + } else { + return false; + } + } else { + return false; + } +} +function advance(node) { + if (node instanceof Fragment) { + let children_count = node.children_count; + return 1 + children_count; + } else { + return 1; + } +} +var fragment_kind = 0; +function fragment(key, mapper, children, keyed_children, children_count) { + return new Fragment( + fragment_kind, + key, + mapper, + children, + keyed_children, + children_count + ); +} +var element_kind = 1; +function element(key, mapper, namespace2, tag, attributes, children, keyed_children, self_closing, void$) { + return new Element2( + element_kind, + key, + mapper, + namespace2, + tag, + prepare(attributes), + children, + keyed_children, + self_closing, + void$ || is_void_element(tag, namespace2) + ); +} +var text_kind = 2; +function text(key, mapper, content3) { + return new Text(text_kind, key, mapper, content3); +} +var unsafe_inner_html_kind = 3; +function unsafe_inner_html(key, mapper, namespace2, tag, attributes, inner_html) { + return new UnsafeInnerHtml( + unsafe_inner_html_kind, + key, + mapper, + namespace2, + tag, + prepare(attributes), + inner_html + ); +} +function set_fragment_key(loop$key, loop$children, loop$index, loop$new_children, loop$keyed_children) { + while (true) { + let key = loop$key; + let children = loop$children; + let index5 = loop$index; + let new_children = loop$new_children; + let keyed_children = loop$keyed_children; + if (children instanceof Empty) { + return [reverse(new_children), keyed_children]; + } else { + let $ = children.head; + if ($ instanceof Fragment) { + let node = $; + if (node.key === "") { + let children$1 = children.tail; + let child_key = key + "::" + to_string(index5); + let $1 = set_fragment_key( + child_key, + node.children, + 0, + empty_list, + empty2() + ); + let node_children = $1[0]; + let node_keyed_children = $1[1]; + let _block; + let _record = node; + _block = new Fragment( + _record.kind, + _record.key, + _record.mapper, + node_children, + node_keyed_children, + _record.children_count + ); + let new_node = _block; + let new_children$1 = prepend(new_node, new_children); + let index$1 = index5 + 1; + loop$key = key; + loop$children = children$1; + loop$index = index$1; + loop$new_children = new_children$1; + loop$keyed_children = keyed_children; + } else { + let node$1 = $; + if (node$1.key !== "") { + let children$1 = children.tail; + let child_key = key + "::" + node$1.key; + let keyed_node = to_keyed(child_key, node$1); + let new_children$1 = prepend(keyed_node, new_children); + let keyed_children$1 = insert3( + keyed_children, + child_key, + keyed_node + ); + let index$1 = index5 + 1; + loop$key = key; + loop$children = children$1; + loop$index = index$1; + loop$new_children = new_children$1; + loop$keyed_children = keyed_children$1; + } else { + let node$2 = $; + let children$1 = children.tail; + let new_children$1 = prepend(node$2, new_children); + let index$1 = index5 + 1; + loop$key = key; + loop$children = children$1; + loop$index = index$1; + loop$new_children = new_children$1; + loop$keyed_children = keyed_children; + } + } + } else { + let node = $; + if (node.key !== "") { + let children$1 = children.tail; + let child_key = key + "::" + node.key; + let keyed_node = to_keyed(child_key, node); + let new_children$1 = prepend(keyed_node, new_children); + let keyed_children$1 = insert3( + keyed_children, + child_key, + keyed_node + ); + let index$1 = index5 + 1; + loop$key = key; + loop$children = children$1; + loop$index = index$1; + loop$new_children = new_children$1; + loop$keyed_children = keyed_children$1; + } else { + let node$1 = $; + let children$1 = children.tail; + let new_children$1 = prepend(node$1, new_children); + let index$1 = index5 + 1; + loop$key = key; + loop$children = children$1; + loop$index = index$1; + loop$new_children = new_children$1; + loop$keyed_children = keyed_children; + } + } + } + } +} +function to_keyed(key, node) { + if (node instanceof Fragment) { + let children = node.children; + let $ = set_fragment_key( + key, + children, + 0, + empty_list, + empty2() + ); + let children$1 = $[0]; + let keyed_children = $[1]; + let _record = node; + return new Fragment( + _record.kind, + key, + _record.mapper, + children$1, + keyed_children, + _record.children_count + ); + } else if (node instanceof Element2) { + let _record = node; + return new Element2( + _record.kind, + key, + _record.mapper, + _record.namespace, + _record.tag, + _record.attributes, + _record.children, + _record.keyed_children, + _record.self_closing, + _record.void + ); + } else if (node instanceof Text) { + let _record = node; + return new Text(_record.kind, key, _record.mapper, _record.content); + } else { + let _record = node; + return new UnsafeInnerHtml( + _record.kind, + key, + _record.mapper, + _record.namespace, + _record.tag, + _record.attributes, + _record.inner_html + ); + } +} + +// build/dev/javascript/lustre/lustre/internals/equals.ffi.mjs +var isReferenceEqual = (a2, b) => a2 === b; +var isEqual2 = (a2, b) => { + if (a2 === b) { + return true; + } + if (a2 == null || b == null) { + return false; + } + const type = typeof a2; + if (type !== typeof b) { + return false; + } + if (type !== "object") { + return false; + } + const ctor = a2.constructor; + if (ctor !== b.constructor) { + return false; + } + if (Array.isArray(a2)) { + return areArraysEqual(a2, b); + } + return areObjectsEqual(a2, b); +}; +var areArraysEqual = (a2, b) => { + let index5 = a2.length; + if (index5 !== b.length) { + return false; + } + while (index5--) { + if (!isEqual2(a2[index5], b[index5])) { + return false; + } + } + return true; +}; +var areObjectsEqual = (a2, b) => { + const properties = Object.keys(a2); + let index5 = properties.length; + if (Object.keys(b).length !== index5) { + return false; + } + while (index5--) { + const property3 = properties[index5]; + if (!Object.hasOwn(b, property3)) { + return false; + } + if (!isEqual2(a2[property3], b[property3])) { + return false; + } + } + return true; +}; + +// build/dev/javascript/lustre/lustre/vdom/events.mjs +var Events = class extends CustomType { + constructor(handlers, dispatched_paths, next_dispatched_paths) { + super(); + this.handlers = handlers; + this.dispatched_paths = dispatched_paths; + this.next_dispatched_paths = next_dispatched_paths; + } +}; +function new$3() { + return new Events( + empty2(), + empty_list, + empty_list + ); +} +function tick(events) { + return new Events( + events.handlers, + events.next_dispatched_paths, + empty_list + ); +} +function do_remove_event(handlers, path2, name6) { + return remove(handlers, event3(path2, name6)); +} +function remove_event(events, path2, name6) { + let handlers = do_remove_event(events.handlers, path2, name6); + let _record = events; + return new Events( + handlers, + _record.dispatched_paths, + _record.next_dispatched_paths + ); +} +function remove_attributes(handlers, path2, attributes) { + return fold( + attributes, + handlers, + (events, attribute5) => { + if (attribute5 instanceof Event2) { + let name6 = attribute5.name; + return do_remove_event(events, path2, name6); + } else { + return events; + } + } + ); +} +function handle(events, path2, name6, event4) { + let next_dispatched_paths = prepend(path2, events.next_dispatched_paths); + let _block; + let _record = events; + _block = new Events( + _record.handlers, + _record.dispatched_paths, + next_dispatched_paths + ); + let events$1 = _block; + let $ = get( + events$1.handlers, + path2 + separator_event + name6 + ); + if ($ instanceof Ok) { + let handler = $[0]; + return [events$1, run(event4, handler)]; + } else { + return [events$1, new Error(toList([]))]; + } +} +function has_dispatched_events(events, path2) { + return matches(path2, events.dispatched_paths); +} +function do_add_event(handlers, mapper, path2, name6, handler) { + return insert3( + handlers, + event3(path2, name6), + map2( + handler, + (handler2) => { + let _record = handler2; + return new Handler( + _record.prevent_default, + _record.stop_propagation, + identity2(mapper)(handler2.message) + ); + } + ) + ); +} +function add_event(events, mapper, path2, name6, handler) { + let handlers = do_add_event(events.handlers, mapper, path2, name6, handler); + let _record = events; + return new Events( + handlers, + _record.dispatched_paths, + _record.next_dispatched_paths + ); +} +function add_attributes(handlers, mapper, path2, attributes) { + return fold( + attributes, + handlers, + (events, attribute5) => { + if (attribute5 instanceof Event2) { + let name6 = attribute5.name; + let handler = attribute5.handler; + return do_add_event(events, mapper, path2, name6, handler); + } else { + return events; + } + } + ); +} +function compose_mapper(mapper, child_mapper) { + let $ = isReferenceEqual(mapper, identity2); + let $1 = isReferenceEqual(child_mapper, identity2); + if ($1) { + return mapper; + } else if ($) { + return child_mapper; + } else { + return (msg) => { + return mapper(child_mapper(msg)); + }; + } +} +function do_remove_children(loop$handlers, loop$path, loop$child_index, loop$children) { + while (true) { + let handlers = loop$handlers; + let path2 = loop$path; + let child_index = loop$child_index; + let children = loop$children; + if (children instanceof Empty) { + return handlers; + } else { + let child2 = children.head; + let rest = children.tail; + let _pipe = handlers; + let _pipe$1 = do_remove_child(_pipe, path2, child_index, child2); + loop$handlers = _pipe$1; + loop$path = path2; + loop$child_index = child_index + advance(child2); + loop$children = rest; + } + } +} +function do_remove_child(handlers, parent, child_index, child2) { + if (child2 instanceof Fragment) { + let children = child2.children; + return do_remove_children(handlers, parent, child_index + 1, children); + } else if (child2 instanceof Element2) { + let attributes = child2.attributes; + let children = child2.children; + let path2 = add3(parent, child_index, child2.key); + let _pipe = handlers; + let _pipe$1 = remove_attributes(_pipe, path2, attributes); + return do_remove_children(_pipe$1, path2, 0, children); + } else if (child2 instanceof Text) { + return handlers; + } else { + let attributes = child2.attributes; + let path2 = add3(parent, child_index, child2.key); + return remove_attributes(handlers, path2, attributes); + } +} +function remove_child(events, parent, child_index, child2) { + let handlers = do_remove_child(events.handlers, parent, child_index, child2); + let _record = events; + return new Events( + handlers, + _record.dispatched_paths, + _record.next_dispatched_paths + ); +} +function do_add_children(loop$handlers, loop$mapper, loop$path, loop$child_index, loop$children) { + while (true) { + let handlers = loop$handlers; + let mapper = loop$mapper; + let path2 = loop$path; + let child_index = loop$child_index; + let children = loop$children; + if (children instanceof Empty) { + return handlers; + } else { + let child2 = children.head; + let rest = children.tail; + let _pipe = handlers; + let _pipe$1 = do_add_child(_pipe, mapper, path2, child_index, child2); + loop$handlers = _pipe$1; + loop$mapper = mapper; + loop$path = path2; + loop$child_index = child_index + advance(child2); + loop$children = rest; + } + } +} +function do_add_child(handlers, mapper, parent, child_index, child2) { + if (child2 instanceof Fragment) { + let children = child2.children; + let composed_mapper = compose_mapper(mapper, child2.mapper); + let child_index$1 = child_index + 1; + return do_add_children( + handlers, + composed_mapper, + parent, + child_index$1, + children + ); + } else if (child2 instanceof Element2) { + let attributes = child2.attributes; + let children = child2.children; + let path2 = add3(parent, child_index, child2.key); + let composed_mapper = compose_mapper(mapper, child2.mapper); + let _pipe = handlers; + let _pipe$1 = add_attributes(_pipe, composed_mapper, path2, attributes); + return do_add_children(_pipe$1, composed_mapper, path2, 0, children); + } else if (child2 instanceof Text) { + return handlers; + } else { + let attributes = child2.attributes; + let path2 = add3(parent, child_index, child2.key); + let composed_mapper = compose_mapper(mapper, child2.mapper); + return add_attributes(handlers, composed_mapper, path2, attributes); + } +} +function add_child(events, mapper, parent, index5, child2) { + let handlers = do_add_child(events.handlers, mapper, parent, index5, child2); + let _record = events; + return new Events( + handlers, + _record.dispatched_paths, + _record.next_dispatched_paths + ); +} +function add_children(events, mapper, path2, child_index, children) { + let handlers = do_add_children( + events.handlers, + mapper, + path2, + child_index, + children + ); + let _record = events; + return new Events( + handlers, + _record.dispatched_paths, + _record.next_dispatched_paths + ); +} + +// build/dev/javascript/lustre/lustre/element.mjs +function element2(tag, attributes, children) { + return element( + "", + identity2, + "", + tag, + attributes, + children, + empty2(), + false, + false + ); +} +function namespaced(namespace2, tag, attributes, children) { + return element( + "", + identity2, + namespace2, + tag, + attributes, + children, + empty2(), + false, + false + ); +} +function text2(content3) { + return text("", identity2, content3); +} +function none2() { + return text("", identity2, ""); +} +function count_fragment_children(loop$children, loop$count) { + while (true) { + let children = loop$children; + let count = loop$count; + if (children instanceof Empty) { + return count; + } else { + let child2 = children.head; + let rest = children.tail; + loop$children = rest; + loop$count = count + advance(child2); + } + } +} +function fragment2(children) { + return fragment( + "", + identity2, + children, + empty2(), + count_fragment_children(children, 0) + ); +} +function unsafe_raw_html(namespace2, tag, attributes, inner_html) { + return unsafe_inner_html( + "", + identity2, + namespace2, + tag, + attributes, + inner_html + ); +} + +// build/dev/javascript/lustre/lustre/element/html.mjs +function text3(content3) { + return text2(content3); +} +function style2(attrs, css2) { + return unsafe_raw_html("", "style", attrs, css2); +} +function article(attrs, children) { + return element2("article", attrs, children); +} +function aside(attrs, children) { + return element2("aside", attrs, children); +} +function footer(attrs, children) { + return element2("footer", attrs, children); +} +function header(attrs, children) { + return element2("header", attrs, children); +} +function h1(attrs, children) { + return element2("h1", attrs, children); +} +function h2(attrs, children) { + return element2("h2", attrs, children); +} +function main(attrs, children) { + return element2("main", attrs, children); +} +function nav(attrs, children) { + return element2("nav", attrs, children); +} +function section(attrs, children) { + return element2("section", attrs, children); +} +function div(attrs, children) { + return element2("div", attrs, children); +} +function hr(attrs) { + return element2("hr", attrs, empty_list); +} +function li(attrs, children) { + return element2("li", attrs, children); +} +function ol(attrs, children) { + return element2("ol", attrs, children); +} +function p(attrs, children) { + return element2("p", attrs, children); +} +function ul(attrs, children) { + return element2("ul", attrs, children); +} +function a(attrs, children) { + return element2("a", attrs, children); +} +function small(attrs, children) { + return element2("small", attrs, children); +} +function span(attrs, children) { + return element2("span", attrs, children); +} +function img(attrs) { + return element2("img", attrs, empty_list); +} +function svg(attrs, children) { + return namespaced("http://www.w3.org/2000/svg", "svg", attrs, children); +} +function button(attrs, children) { + return element2("button", attrs, children); +} +function input(attrs) { + return element2("input", attrs, empty_list); +} +function label(attrs, children) { + return element2("label", attrs, children); +} +function slot(attrs, fallback) { + return element2("slot", attrs, fallback); +} + +// build/dev/javascript/lustre/lustre/vdom/patch.mjs +var Patch = class extends CustomType { + constructor(index5, removed, changes, children) { + super(); + this.index = index5; + this.removed = removed; + this.changes = changes; + this.children = children; + } +}; +var ReplaceText = class extends CustomType { + constructor(kind, content3) { + super(); + this.kind = kind; + this.content = content3; + } +}; +var ReplaceInnerHtml = class extends CustomType { + constructor(kind, inner_html) { + super(); + this.kind = kind; + this.inner_html = inner_html; + } +}; +var Update = class extends CustomType { + constructor(kind, added, removed) { + super(); + this.kind = kind; + this.added = added; + this.removed = removed; + } +}; +var Move = class extends CustomType { + constructor(kind, key, before, count) { + super(); + this.kind = kind; + this.key = key; + this.before = before; + this.count = count; + } +}; +var RemoveKey = class extends CustomType { + constructor(kind, key, count) { + super(); + this.kind = kind; + this.key = key; + this.count = count; + } +}; +var Replace = class extends CustomType { + constructor(kind, from2, count, with$) { + super(); + this.kind = kind; + this.from = from2; + this.count = count; + this.with = with$; + } +}; +var Insert = class extends CustomType { + constructor(kind, children, before) { + super(); + this.kind = kind; + this.children = children; + this.before = before; + } +}; +var Remove = class extends CustomType { + constructor(kind, from2, count) { + super(); + this.kind = kind; + this.from = from2; + this.count = count; + } +}; +function new$5(index5, removed, changes, children) { + return new Patch(index5, removed, changes, children); +} +var replace_text_kind = 0; +function replace_text(content3) { + return new ReplaceText(replace_text_kind, content3); +} +var replace_inner_html_kind = 1; +function replace_inner_html(inner_html) { + return new ReplaceInnerHtml(replace_inner_html_kind, inner_html); +} +var update_kind = 2; +function update(added, removed) { + return new Update(update_kind, added, removed); +} +var move_kind = 3; +function move(key, before, count) { + return new Move(move_kind, key, before, count); +} +var remove_key_kind = 4; +function remove_key(key, count) { + return new RemoveKey(remove_key_kind, key, count); +} +var replace_kind = 5; +function replace2(from2, count, with$) { + return new Replace(replace_kind, from2, count, with$); +} +var insert_kind = 6; +function insert4(children, before) { + return new Insert(insert_kind, children, before); +} +var remove_kind = 7; +function remove2(from2, count) { + return new Remove(remove_kind, from2, count); +} + +// build/dev/javascript/lustre/lustre/vdom/diff.mjs +var Diff = class extends CustomType { + constructor(patch, events) { + super(); + this.patch = patch; + this.events = events; + } +}; +var AttributeChange = class extends CustomType { + constructor(added, removed, events) { + super(); + this.added = added; + this.removed = removed; + this.events = events; + } +}; +function is_controlled(events, namespace2, tag, path2) { + if (tag === "input") { + if (namespace2 === "") { + return has_dispatched_events(events, path2); + } else { + return false; + } + } else if (tag === "select") { + if (namespace2 === "") { + return has_dispatched_events(events, path2); + } else { + return false; + } + } else if (tag === "textarea") { + if (namespace2 === "") { + return has_dispatched_events(events, path2); + } else { + return false; + } + } else { + return false; + } +} +function diff_attributes(loop$controlled, loop$path, loop$mapper, loop$events, loop$old, loop$new, loop$added, loop$removed) { + while (true) { + let controlled = loop$controlled; + let path2 = loop$path; + let mapper = loop$mapper; + let events = loop$events; + let old = loop$old; + let new$9 = loop$new; + let added = loop$added; + let removed = loop$removed; + if (new$9 instanceof Empty) { + if (old instanceof Empty) { + return new AttributeChange(added, removed, events); + } else { + let $ = old.head; + if ($ instanceof Event2) { + let prev = $; + let old$1 = old.tail; + let name6 = $.name; + let removed$1 = prepend(prev, removed); + let events$1 = remove_event(events, path2, name6); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = old$1; + loop$new = new$9; + loop$added = added; + loop$removed = removed$1; + } else { + let prev = $; + let old$1 = old.tail; + let removed$1 = prepend(prev, removed); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = old$1; + loop$new = new$9; + loop$added = added; + loop$removed = removed$1; + } + } + } else if (old instanceof Empty) { + let $ = new$9.head; + if ($ instanceof Event2) { + let next2 = $; + let new$1 = new$9.tail; + let name6 = $.name; + let handler = $.handler; + let added$1 = prepend(next2, added); + let events$1 = add_event(events, mapper, path2, name6, handler); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = old; + loop$new = new$1; + loop$added = added$1; + loop$removed = removed; + } else { + let next2 = $; + let new$1 = new$9.tail; + let added$1 = prepend(next2, added); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = old; + loop$new = new$1; + loop$added = added$1; + loop$removed = removed; + } + } else { + let next2 = new$9.head; + let remaining_new = new$9.tail; + let prev = old.head; + let remaining_old = old.tail; + let $ = compare3(prev, next2); + if ($ instanceof Lt) { + if (prev instanceof Event2) { + let name6 = prev.name; + let removed$1 = prepend(prev, removed); + let events$1 = remove_event(events, path2, name6); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = remaining_old; + loop$new = new$9; + loop$added = added; + loop$removed = removed$1; + } else { + let removed$1 = prepend(prev, removed); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = remaining_old; + loop$new = new$9; + loop$added = added; + loop$removed = removed$1; + } + } else if ($ instanceof Eq) { + if (next2 instanceof Attribute) { + if (prev instanceof Attribute) { + let _block; + let $1 = next2.name; + if ($1 === "value") { + _block = controlled || prev.value !== next2.value; + } else if ($1 === "checked") { + _block = controlled || prev.value !== next2.value; + } else if ($1 === "selected") { + _block = controlled || prev.value !== next2.value; + } else { + _block = prev.value !== next2.value; + } + let has_changes = _block; + let _block$1; + if (has_changes) { + _block$1 = prepend(next2, added); + } else { + _block$1 = added; + } + let added$1 = _block$1; + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } else if (prev instanceof Event2) { + let name6 = prev.name; + let added$1 = prepend(next2, added); + let removed$1 = prepend(prev, removed); + let events$1 = remove_event(events, path2, name6); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed$1; + } else { + let added$1 = prepend(next2, added); + let removed$1 = prepend(prev, removed); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed$1; + } + } else if (next2 instanceof Property) { + if (prev instanceof Property) { + let _block; + let $1 = next2.name; + if ($1 === "scrollLeft") { + _block = true; + } else if ($1 === "scrollRight") { + _block = true; + } else if ($1 === "value") { + _block = controlled || !isEqual2( + prev.value, + next2.value + ); + } else if ($1 === "checked") { + _block = controlled || !isEqual2( + prev.value, + next2.value + ); + } else if ($1 === "selected") { + _block = controlled || !isEqual2( + prev.value, + next2.value + ); + } else { + _block = !isEqual2(prev.value, next2.value); + } + let has_changes = _block; + let _block$1; + if (has_changes) { + _block$1 = prepend(next2, added); + } else { + _block$1 = added; + } + let added$1 = _block$1; + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } else if (prev instanceof Event2) { + let name6 = prev.name; + let added$1 = prepend(next2, added); + let removed$1 = prepend(prev, removed); + let events$1 = remove_event(events, path2, name6); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed$1; + } else { + let added$1 = prepend(next2, added); + let removed$1 = prepend(prev, removed); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed$1; + } + } else if (prev instanceof Event2) { + let name6 = next2.name; + let handler = next2.handler; + let has_changes = !isEqual( + prev.prevent_default, + next2.prevent_default + ) || !isEqual(prev.stop_propagation, next2.stop_propagation) || prev.immediate !== next2.immediate || prev.debounce !== next2.debounce || prev.throttle !== next2.throttle; + let _block; + if (has_changes) { + _block = prepend(next2, added); + } else { + _block = added; + } + let added$1 = _block; + let events$1 = add_event(events, mapper, path2, name6, handler); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } else { + let name6 = next2.name; + let handler = next2.handler; + let added$1 = prepend(next2, added); + let removed$1 = prepend(prev, removed); + let events$1 = add_event(events, mapper, path2, name6, handler); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed$1; + } + } else if (next2 instanceof Event2) { + let name6 = next2.name; + let handler = next2.handler; + let added$1 = prepend(next2, added); + let events$1 = add_event(events, mapper, path2, name6, handler); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events$1; + loop$old = old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } else { + let added$1 = prepend(next2, added); + loop$controlled = controlled; + loop$path = path2; + loop$mapper = mapper; + loop$events = events; + loop$old = old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } + } + } +} +function do_diff(loop$old, loop$old_keyed, loop$new, loop$new_keyed, loop$moved, loop$moved_offset, loop$removed, loop$node_index, loop$patch_index, loop$path, loop$changes, loop$children, loop$mapper, loop$events) { + while (true) { + let old = loop$old; + let old_keyed = loop$old_keyed; + let new$9 = loop$new; + let new_keyed = loop$new_keyed; + let moved = loop$moved; + let moved_offset = loop$moved_offset; + let removed = loop$removed; + let node_index = loop$node_index; + let patch_index = loop$patch_index; + let path2 = loop$path; + let changes = loop$changes; + let children = loop$children; + let mapper = loop$mapper; + let events = loop$events; + if (new$9 instanceof Empty) { + if (old instanceof Empty) { + return new Diff( + new Patch(patch_index, removed, changes, children), + events + ); + } else { + let prev = old.head; + let old$1 = old.tail; + let _block; + let $ = prev.key === "" || !contains(moved, prev.key); + if ($) { + _block = removed + advance(prev); + } else { + _block = removed; + } + let removed$1 = _block; + let events$1 = remove_child(events, path2, node_index, prev); + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$9; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed$1; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes; + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else if (old instanceof Empty) { + let events$1 = add_children( + events, + mapper, + path2, + node_index, + new$9 + ); + let insert5 = insert4(new$9, node_index - moved_offset); + let changes$1 = prepend(insert5, changes); + return new Diff( + new Patch(patch_index, removed, changes$1, children), + events$1 + ); + } else { + let next2 = new$9.head; + let prev = old.head; + if (prev.key !== next2.key) { + let new_remaining = new$9.tail; + let old_remaining = old.tail; + let next_did_exist = get(old_keyed, next2.key); + let prev_does_exist = get(new_keyed, prev.key); + let prev_has_moved = contains(moved, prev.key); + if (next_did_exist instanceof Ok) { + if (prev_does_exist instanceof Ok) { + if (prev_has_moved) { + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new$9; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - advance(prev); + loop$removed = removed; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes; + loop$children = children; + loop$mapper = mapper; + loop$events = events; + } else { + let match = next_did_exist[0]; + let count = advance(next2); + let before = node_index - moved_offset; + let move2 = move(next2.key, before, count); + let changes$1 = prepend(move2, changes); + let moved$1 = insert2(moved, next2.key); + let moved_offset$1 = moved_offset + count; + loop$old = prepend(match, old); + loop$old_keyed = old_keyed; + loop$new = new$9; + loop$new_keyed = new_keyed; + loop$moved = moved$1; + loop$moved_offset = moved_offset$1; + loop$removed = removed; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes$1; + loop$children = children; + loop$mapper = mapper; + loop$events = events; + } + } else { + let count = advance(prev); + let moved_offset$1 = moved_offset - count; + let events$1 = remove_child(events, path2, node_index, prev); + let remove3 = remove_key(prev.key, count); + let changes$1 = prepend(remove3, changes); + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new$9; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset$1; + loop$removed = removed; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes$1; + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else if (prev_does_exist instanceof Ok) { + let before = node_index - moved_offset; + let count = advance(next2); + let events$1 = add_child( + events, + mapper, + path2, + node_index, + next2 + ); + let insert5 = insert4(toList([next2]), before); + let changes$1 = prepend(insert5, changes); + loop$old = old; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset + count; + loop$removed = removed; + loop$node_index = node_index + count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes$1; + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } else { + let prev_count = advance(prev); + let next_count = advance(next2); + let change = replace2( + node_index - moved_offset, + prev_count, + next2 + ); + let _block; + let _pipe = events; + let _pipe$1 = remove_child(_pipe, path2, node_index, prev); + _block = add_child(_pipe$1, mapper, path2, node_index, next2); + let events$1 = _block; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - prev_count + next_count; + loop$removed = removed; + loop$node_index = node_index + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = prepend(change, changes); + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else { + let $ = old.head; + if ($ instanceof Fragment) { + let $1 = new$9.head; + if ($1 instanceof Fragment) { + let next$1 = $1; + let new$1 = new$9.tail; + let prev$1 = $; + let old$1 = old.tail; + let node_index$1 = node_index + 1; + let prev_count = prev$1.children_count; + let next_count = next$1.children_count; + let composed_mapper = compose_mapper(mapper, next$1.mapper); + let child2 = do_diff( + prev$1.children, + prev$1.keyed_children, + next$1.children, + next$1.keyed_children, + empty_set(), + moved_offset, + 0, + node_index$1, + -1, + path2, + empty_list, + children, + composed_mapper, + events + ); + let _block; + let $2 = child2.patch.removed > 0; + if ($2) { + let remove_from = node_index$1 + next_count - moved_offset; + let patch = remove2(remove_from, child2.patch.removed); + _block = append( + child2.patch.changes, + prepend(patch, changes) + ); + } else { + _block = append(child2.patch.changes, changes); + } + let changes$1 = _block; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset + next_count - prev_count; + loop$removed = removed; + loop$node_index = node_index$1 + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes$1; + loop$children = child2.patch.children; + loop$mapper = mapper; + loop$events = child2.events; + } else { + let next$1 = $1; + let new_remaining = new$9.tail; + let prev$1 = $; + let old_remaining = old.tail; + let prev_count = advance(prev$1); + let next_count = advance(next$1); + let change = replace2( + node_index - moved_offset, + prev_count, + next$1 + ); + let _block; + let _pipe = events; + let _pipe$1 = remove_child(_pipe, path2, node_index, prev$1); + _block = add_child( + _pipe$1, + mapper, + path2, + node_index, + next$1 + ); + let events$1 = _block; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - prev_count + next_count; + loop$removed = removed; + loop$node_index = node_index + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = prepend(change, changes); + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else if ($ instanceof Element2) { + let $1 = new$9.head; + if ($1 instanceof Element2) { + let next$1 = $1; + let prev$1 = $; + if (prev$1.namespace === next$1.namespace && prev$1.tag === next$1.tag) { + let new$1 = new$9.tail; + let old$1 = old.tail; + let composed_mapper = compose_mapper( + mapper, + next$1.mapper + ); + let child_path = add3(path2, node_index, next$1.key); + let controlled = is_controlled( + events, + next$1.namespace, + next$1.tag, + child_path + ); + let $2 = diff_attributes( + controlled, + child_path, + composed_mapper, + events, + prev$1.attributes, + next$1.attributes, + empty_list, + empty_list + ); + let added_attrs = $2.added; + let removed_attrs = $2.removed; + let events$1 = $2.events; + let _block; + if (removed_attrs instanceof Empty) { + if (added_attrs instanceof Empty) { + _block = empty_list; + } else { + _block = toList([update(added_attrs, removed_attrs)]); + } + } else { + _block = toList([update(added_attrs, removed_attrs)]); + } + let initial_child_changes = _block; + let child2 = do_diff( + prev$1.children, + prev$1.keyed_children, + next$1.children, + next$1.keyed_children, + empty_set(), + 0, + 0, + 0, + node_index, + child_path, + initial_child_changes, + empty_list, + composed_mapper, + events$1 + ); + let _block$1; + let $3 = child2.patch; + let $4 = $3.children; + if ($4 instanceof Empty) { + let $5 = $3.changes; + if ($5 instanceof Empty) { + let $6 = $3.removed; + if ($6 === 0) { + _block$1 = children; + } else { + _block$1 = prepend(child2.patch, children); + } + } else { + _block$1 = prepend(child2.patch, children); + } + } else { + _block$1 = prepend(child2.patch, children); + } + let children$1 = _block$1; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes; + loop$children = children$1; + loop$mapper = mapper; + loop$events = child2.events; + } else { + let next$2 = $1; + let new_remaining = new$9.tail; + let prev$2 = $; + let old_remaining = old.tail; + let prev_count = advance(prev$2); + let next_count = advance(next$2); + let change = replace2( + node_index - moved_offset, + prev_count, + next$2 + ); + let _block; + let _pipe = events; + let _pipe$1 = remove_child( + _pipe, + path2, + node_index, + prev$2 + ); + _block = add_child( + _pipe$1, + mapper, + path2, + node_index, + next$2 + ); + let events$1 = _block; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - prev_count + next_count; + loop$removed = removed; + loop$node_index = node_index + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = prepend(change, changes); + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else { + let next$1 = $1; + let new_remaining = new$9.tail; + let prev$1 = $; + let old_remaining = old.tail; + let prev_count = advance(prev$1); + let next_count = advance(next$1); + let change = replace2( + node_index - moved_offset, + prev_count, + next$1 + ); + let _block; + let _pipe = events; + let _pipe$1 = remove_child(_pipe, path2, node_index, prev$1); + _block = add_child( + _pipe$1, + mapper, + path2, + node_index, + next$1 + ); + let events$1 = _block; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - prev_count + next_count; + loop$removed = removed; + loop$node_index = node_index + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = prepend(change, changes); + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else if ($ instanceof Text) { + let $1 = new$9.head; + if ($1 instanceof Text) { + let next$1 = $1; + let prev$1 = $; + if (prev$1.content === next$1.content) { + let new$1 = new$9.tail; + let old$1 = old.tail; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes; + loop$children = children; + loop$mapper = mapper; + loop$events = events; + } else { + let next$2 = $1; + let new$1 = new$9.tail; + let old$1 = old.tail; + let child2 = new$5( + node_index, + 0, + toList([replace_text(next$2.content)]), + empty_list + ); + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes; + loop$children = prepend(child2, children); + loop$mapper = mapper; + loop$events = events; + } + } else { + let next$1 = $1; + let new_remaining = new$9.tail; + let prev$1 = $; + let old_remaining = old.tail; + let prev_count = advance(prev$1); + let next_count = advance(next$1); + let change = replace2( + node_index - moved_offset, + prev_count, + next$1 + ); + let _block; + let _pipe = events; + let _pipe$1 = remove_child(_pipe, path2, node_index, prev$1); + _block = add_child( + _pipe$1, + mapper, + path2, + node_index, + next$1 + ); + let events$1 = _block; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - prev_count + next_count; + loop$removed = removed; + loop$node_index = node_index + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = prepend(change, changes); + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } else { + let $1 = new$9.head; + if ($1 instanceof UnsafeInnerHtml) { + let next$1 = $1; + let new$1 = new$9.tail; + let prev$1 = $; + let old$1 = old.tail; + let composed_mapper = compose_mapper(mapper, next$1.mapper); + let child_path = add3(path2, node_index, next$1.key); + let $2 = diff_attributes( + false, + child_path, + composed_mapper, + events, + prev$1.attributes, + next$1.attributes, + empty_list, + empty_list + ); + let added_attrs = $2.added; + let removed_attrs = $2.removed; + let events$1 = $2.events; + let _block; + if (removed_attrs instanceof Empty) { + if (added_attrs instanceof Empty) { + _block = empty_list; + } else { + _block = toList([update(added_attrs, removed_attrs)]); + } + } else { + _block = toList([update(added_attrs, removed_attrs)]); + } + let child_changes = _block; + let _block$1; + let $3 = prev$1.inner_html === next$1.inner_html; + if ($3) { + _block$1 = child_changes; + } else { + _block$1 = prepend( + replace_inner_html(next$1.inner_html), + child_changes + ); + } + let child_changes$1 = _block$1; + let _block$2; + if (child_changes$1 instanceof Empty) { + _block$2 = children; + } else { + _block$2 = prepend( + new$5(node_index, 0, child_changes$1, toList([])), + children + ); + } + let children$1 = _block$2; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = changes; + loop$children = children$1; + loop$mapper = mapper; + loop$events = events$1; + } else { + let next$1 = $1; + let new_remaining = new$9.tail; + let prev$1 = $; + let old_remaining = old.tail; + let prev_count = advance(prev$1); + let next_count = advance(next$1); + let change = replace2( + node_index - moved_offset, + prev_count, + next$1 + ); + let _block; + let _pipe = events; + let _pipe$1 = remove_child(_pipe, path2, node_index, prev$1); + _block = add_child( + _pipe$1, + mapper, + path2, + node_index, + next$1 + ); + let events$1 = _block; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - prev_count + next_count; + loop$removed = removed; + loop$node_index = node_index + next_count; + loop$patch_index = patch_index; + loop$path = path2; + loop$changes = prepend(change, changes); + loop$children = children; + loop$mapper = mapper; + loop$events = events$1; + } + } + } + } + } +} +function diff(events, old, new$9) { + return do_diff( + toList([old]), + empty2(), + toList([new$9]), + empty2(), + empty_set(), + 0, + 0, + 0, + 0, + root2, + empty_list, + empty_list, + identity2, + tick(events) + ); +} + +// build/dev/javascript/lustre/lustre/vdom/reconciler.ffi.mjs +var Reconciler = class { + offset = 0; + #root = null; + #dispatch = () => { + }; + #useServerEvents = false; + #exposeKeys = false; + constructor(root3, dispatch2, { useServerEvents = false, exposeKeys = false } = {}) { + this.#root = root3; + this.#dispatch = dispatch2; + this.#useServerEvents = useServerEvents; + this.#exposeKeys = exposeKeys; + } + mount(vdom) { + appendChild(this.#root, this.#createChild(this.#root, 0, vdom)); + } + #stack = []; + push(patch) { + const offset = this.offset; + if (offset) { + iterate(patch.changes, (change) => { + switch (change.kind) { + case insert_kind: + case move_kind: + change.before = (change.before | 0) + offset; + break; + case remove_kind: + case replace_kind: + change.from = (change.from | 0) + offset; + break; + } + }); + iterate(patch.children, (child2) => { + child2.index = (child2.index | 0) + offset; + }); + } + this.#stack.push({ node: this.#root, patch }); + this.#reconcile(); + } + // PATCHING ------------------------------------------------------------------ + #reconcile() { + const self = this; + while (self.#stack.length) { + const { node, patch } = self.#stack.pop(); + iterate(patch.changes, (change) => { + switch (change.kind) { + case insert_kind: + self.#insert(node, change.children, change.before); + break; + case move_kind: + self.#move(node, change.key, change.before, change.count); + break; + case remove_key_kind: + self.#removeKey(node, change.key, change.count); + break; + case remove_kind: + self.#remove(node, change.from, change.count); + break; + case replace_kind: + self.#replace(node, change.from, change.count, change.with); + break; + case replace_text_kind: + self.#replaceText(node, change.content); + break; + case replace_inner_html_kind: + self.#replaceInnerHtml(node, change.inner_html); + break; + case update_kind: + self.#update(node, change.added, change.removed); + break; + } + }); + if (patch.removed) { + self.#remove( + node, + node.childNodes.length - patch.removed, + patch.removed + ); + } + let lastIndex = -1; + let lastChild = null; + iterate(patch.children, (child2) => { + const index5 = child2.index | 0; + const next2 = lastChild && lastIndex - index5 === 1 ? lastChild.previousSibling : childAt(node, index5); + self.#stack.push({ node: next2, patch: child2 }); + lastChild = next2; + lastIndex = index5; + }); + } + } + // CHANGES ------------------------------------------------------------------- + #insert(node, children, before) { + const fragment4 = createDocumentFragment(); + let childIndex = before | 0; + iterate(children, (child2) => { + const el = this.#createChild(node, childIndex, child2); + appendChild(fragment4, el); + childIndex += advance(child2); + }); + insertBefore(node, fragment4, childAt(node, before)); + } + #move(node, key, before, count) { + let el = getKeyedChild(node, key); + const beforeEl = childAt(node, before); + for (let i = 0; i < count && el !== null; ++i) { + const next2 = el.nextSibling; + if (SUPPORTS_MOVE_BEFORE) { + node.moveBefore(el, beforeEl); + } else { + insertBefore(node, el, beforeEl); + } + el = next2; + } + } + #removeKey(node, key, count) { + this.#removeFromChild(node, getKeyedChild(node, key), count); + } + #remove(node, from2, count) { + this.#removeFromChild(node, childAt(node, from2), count); + } + #removeFromChild(parent, child2, count) { + while (count-- > 0 && child2 !== null) { + const next2 = child2.nextSibling; + const key = child2[meta].key; + if (key) { + parent[meta].keyedChildren.delete(key); + } + for (const [_, { timeout }] of child2[meta].debouncers ?? []) { + clearTimeout(timeout); + } + parent.removeChild(child2); + child2 = next2; + } + } + #replace(parent, from2, count, child2) { + this.#remove(parent, from2, count); + const el = this.#createChild(parent, from2, child2); + insertBefore(parent, el, childAt(parent, from2)); + } + #replaceText(node, content3) { + node.data = content3 ?? ""; + } + #replaceInnerHtml(node, inner_html) { + node.innerHTML = inner_html ?? ""; + } + #update(node, added, removed) { + iterate(removed, (attribute5) => { + const name6 = attribute5.name; + if (node[meta].handlers.has(name6)) { + node.removeEventListener(name6, handleEvent); + node[meta].handlers.delete(name6); + if (node[meta].throttles.has(name6)) { + node[meta].throttles.delete(name6); + } + if (node[meta].debouncers.has(name6)) { + clearTimeout(node[meta].debouncers.get(name6).timeout); + node[meta].debouncers.delete(name6); + } + } else { + node.removeAttribute(name6); + SYNCED_ATTRIBUTES[name6]?.removed?.(node, name6); + } + }); + iterate(added, (attribute5) => { + this.#createAttribute(node, attribute5); + }); + } + // CONSTRUCTORS -------------------------------------------------------------- + #createChild(parent, index5, vnode) { + switch (vnode.kind) { + case element_kind: { + const node = createChildElement(parent, index5, vnode); + this.#createAttributes(node, vnode); + this.#insert(node, vnode.children); + return node; + } + case text_kind: { + return createChildText(parent, index5, vnode); + } + case fragment_kind: { + const node = createDocumentFragment(); + const head = createChildText(parent, index5, vnode); + appendChild(node, head); + let childIndex = index5 + 1; + iterate(vnode.children, (child2) => { + appendChild(node, this.#createChild(parent, childIndex, child2)); + childIndex += advance(child2); + }); + return node; + } + case unsafe_inner_html_kind: { + const node = createChildElement(parent, index5, vnode); + this.#createAttributes(node, vnode); + this.#replaceInnerHtml(node, vnode.inner_html); + return node; + } + } + } + #createAttributes(node, { key, attributes }) { + if (this.#exposeKeys && key) { + node.setAttribute("data-lustre-key", key); + } + iterate(attributes, (attribute5) => this.#createAttribute(node, attribute5)); + } + #createAttribute(node, attribute5) { + const { debouncers, handlers, throttles } = node[meta]; + const { + kind, + name: name6, + value: value3, + prevent_default: prevent, + stop_propagation: stop, + immediate: immediate2, + include, + debounce: debounceDelay, + throttle: throttleDelay + } = attribute5; + switch (kind) { + case attribute_kind: { + const valueOrDefault = value3 ?? ""; + if (name6 === "virtual:defaultValue") { + node.defaultValue = valueOrDefault; + return; + } + if (valueOrDefault !== node.getAttribute(name6)) { + node.setAttribute(name6, valueOrDefault); + } + SYNCED_ATTRIBUTES[name6]?.added?.(node, value3); + break; + } + case property_kind: + node[name6] = value3; + break; + case event_kind: { + if (handlers.has(name6)) { + node.removeEventListener(name6, handleEvent); + } + node.addEventListener(name6, handleEvent, { + passive: prevent.kind === never_kind + }); + if (throttleDelay > 0) { + const throttle = throttles.get(name6) ?? {}; + throttle.delay = throttleDelay; + throttles.set(name6, throttle); + } else { + throttles.delete(name6); + } + if (debounceDelay > 0) { + const debounce = debouncers.get(name6) ?? {}; + debounce.delay = debounceDelay; + debouncers.set(name6, debounce); + } else { + clearTimeout(debouncers.get(name6)?.timeout); + debouncers.delete(name6); + } + handlers.set(name6, (event4) => { + if (prevent.kind === always_kind) event4.preventDefault(); + if (stop.kind === always_kind) event4.stopPropagation(); + const type = event4.type; + const path2 = event4.currentTarget[meta].path; + const data = this.#useServerEvents ? createServerEvent(event4, include ?? []) : event4; + const throttle = throttles.get(type); + if (throttle) { + const now = Date.now(); + const last = throttle.last || 0; + if (now > last + throttle.delay) { + throttle.last = now; + throttle.lastEvent = event4; + this.#dispatch(data, path2, type, immediate2); + } + } + const debounce = debouncers.get(type); + if (debounce) { + clearTimeout(debounce.timeout); + debounce.timeout = setTimeout(() => { + if (event4 === throttles.get(type)?.lastEvent) return; + this.#dispatch(data, path2, type, immediate2); + }, debounce.delay); + } + if (!throttle && !debounce) { + this.#dispatch(data, path2, type, immediate2); + } + }); + break; + } + } + } +}; +var iterate = (list4, callback) => { + if (Array.isArray(list4)) { + for (let i = 0; i < list4.length; i++) { + callback(list4[i]); + } + } else if (list4) { + for (list4; list4.tail; list4 = list4.tail) { + callback(list4.head); + } + } +}; +var appendChild = (node, child2) => node.appendChild(child2); +var insertBefore = (parent, node, referenceNode) => parent.insertBefore(node, referenceNode ?? null); +var createChildElement = (parent, index5, { key, tag, namespace: namespace2 }) => { + const node = document2().createElementNS(namespace2 || NAMESPACE_HTML, tag); + initialiseMetadata(parent, node, index5, key); + return node; +}; +var createChildText = (parent, index5, { key, content: content3 }) => { + const node = document2().createTextNode(content3 ?? ""); + initialiseMetadata(parent, node, index5, key); + return node; +}; +var createDocumentFragment = () => document2().createDocumentFragment(); +var childAt = (node, at) => node.childNodes[at | 0]; +var meta = Symbol("lustre"); +var initialiseMetadata = (parent, node, index5 = 0, key = "") => { + const segment = `${key || index5}`; + switch (node.nodeType) { + case ELEMENT_NODE: + case DOCUMENT_FRAGMENT_NODE: + node[meta] = { + key, + path: segment, + keyedChildren: /* @__PURE__ */ new Map(), + handlers: /* @__PURE__ */ new Map(), + throttles: /* @__PURE__ */ new Map(), + debouncers: /* @__PURE__ */ new Map() + }; + break; + case TEXT_NODE: + node[meta] = { key }; + break; + } + if (parent && parent[meta] && key) { + parent[meta].keyedChildren.set(key, new WeakRef(node)); + } + if (parent && parent[meta] && parent[meta].path) { + node[meta].path = `${parent[meta].path}${separator_element}${segment}`; + } +}; +var getKeyedChild = (node, key) => node[meta].keyedChildren.get(key).deref(); +var handleEvent = (event4) => { + const target = event4.currentTarget; + const handler = target[meta].handlers.get(event4.type); + if (event4.type === "submit") { + event4.detail ??= {}; + event4.detail.formData = [...new FormData(event4.target).entries()]; + } + handler(event4); +}; +var createServerEvent = (event4, include = []) => { + const data = {}; + if (event4.type === "input" || event4.type === "change") { + include.push("target.value"); + } + if (event4.type === "submit") { + include.push("detail.formData"); + } + for (const property3 of include) { + const path2 = property3.split("."); + for (let i = 0, input3 = event4, output = data; i < path2.length; i++) { + if (i === path2.length - 1) { + output[path2[i]] = input3[path2[i]]; + break; + } + output = output[path2[i]] ??= {}; + input3 = input3[path2[i]]; + } + } + return data; +}; +var syncedBooleanAttribute = (name6) => { + return { + added(node) { + node[name6] = true; + }, + removed(node) { + node[name6] = false; + } + }; +}; +var syncedAttribute = (name6) => { + return { + added(node, value3) { + node[name6] = value3; + } + }; +}; +var SYNCED_ATTRIBUTES = { + checked: syncedBooleanAttribute("checked"), + selected: syncedBooleanAttribute("selected"), + value: syncedAttribute("value"), + autofocus: { + added(node) { + queueMicrotask(() => node.focus?.()); + } + }, + autoplay: { + added(node) { + try { + node.play?.(); + } catch (e) { + console.error(e); + } + } + } +}; + +// build/dev/javascript/lustre/lustre/vdom/virtualise.ffi.mjs +var virtualise = (root3) => { + const vdom = virtualiseNode(null, root3, ""); + if (vdom === null || vdom.children instanceof Empty) { + const empty4 = emptyTextNode(root3); + root3.appendChild(empty4); + return none2(); + } else if (vdom.children instanceof NonEmpty && vdom.children.tail instanceof Empty) { + return vdom.children.head; + } else { + const head = emptyTextNode(root3); + root3.insertBefore(head, root3.firstChild); + return fragment2(vdom.children); + } +}; +var emptyTextNode = (parent) => { + const node = document2().createTextNode(""); + initialiseMetadata(parent, node); + return node; +}; +var virtualiseNode = (parent, node, index5) => { + switch (node.nodeType) { + case ELEMENT_NODE: { + const key = node.getAttribute("data-lustre-key"); + initialiseMetadata(parent, node, index5, key); + if (key) { + node.removeAttribute("data-lustre-key"); + } + const tag = node.localName; + const namespace2 = node.namespaceURI; + const isHtmlElement = !namespace2 || namespace2 === NAMESPACE_HTML; + if (isHtmlElement && INPUT_ELEMENTS.includes(tag)) { + virtualiseInputEvents(tag, node); + } + const attributes = virtualiseAttributes(node); + const children = virtualiseChildNodes(node); + const vnode = isHtmlElement ? element2(tag, attributes, children) : namespaced(namespace2, tag, attributes, children); + return key ? to_keyed(key, vnode) : vnode; + } + case TEXT_NODE: + initialiseMetadata(parent, node, index5); + return node.data ? text2(node.data) : null; + case DOCUMENT_FRAGMENT_NODE: + initialiseMetadata(parent, node, index5); + return node.childNodes.length > 0 ? fragment2(virtualiseChildNodes(node)) : null; + default: + return null; + } +}; +var INPUT_ELEMENTS = ["input", "select", "textarea"]; +var virtualiseInputEvents = (tag, node) => { + const value3 = node.value; + const checked2 = node.checked; + if (tag === "input" && node.type === "checkbox" && !checked2) return; + if (tag === "input" && node.type === "radio" && !checked2) return; + if (node.type !== "checkbox" && node.type !== "radio" && !value3) return; + queueMicrotask(() => { + node.value = value3; + node.checked = checked2; + node.dispatchEvent(new Event("input", { bubbles: true })); + node.dispatchEvent(new Event("change", { bubbles: true })); + if (document2().activeElement !== node) { + node.dispatchEvent(new Event("blur", { bubbles: true })); + } + }); +}; +var virtualiseChildNodes = (node) => { + let children = null; + let index5 = 0; + let child2 = node.firstChild; + let ptr = null; + while (child2) { + const vnode = virtualiseNode(node, child2, index5); + const next2 = child2.nextSibling; + if (vnode) { + const list_node = new NonEmpty(vnode, null); + if (ptr) { + ptr = ptr.tail = list_node; + } else { + ptr = children = list_node; + } + index5 += 1; + } else { + node.removeChild(child2); + } + child2 = next2; + } + if (!ptr) return empty_list; + ptr.tail = empty_list; + return children; +}; +var virtualiseAttributes = (node) => { + let index5 = node.attributes.length; + let attributes = empty_list; + while (index5-- > 0) { + attributes = new NonEmpty( + virtualiseAttribute(node.attributes[index5]), + attributes + ); + } + return attributes; +}; +var virtualiseAttribute = (attr) => { + const name6 = attr.localName; + const value3 = attr.value; + return attribute2(name6, value3); +}; + +// build/dev/javascript/lustre/lustre/runtime/client/runtime.ffi.mjs +var is_browser = () => !!document2(); +var is_registered = (name6) => is_browser() && customElements.get(name6); +var Runtime = class { + constructor(root3, [model, effects], view8, update7) { + this.root = root3; + this.#model = model; + this.#view = view8; + this.#update = update7; + this.#reconciler = new Reconciler(this.root, (event4, path2, name6) => { + const [events, result] = handle(this.#events, path2, name6, event4); + this.#events = events; + if (result.isOk()) { + const handler = result[0]; + if (handler.stop_propagation) event4.stopPropagation(); + if (handler.prevent_default) event4.preventDefault(); + this.dispatch(handler.message, false); + } + }); + this.#vdom = virtualise(this.root); + this.#events = new$3(); + this.#shouldFlush = true; + this.#tick(effects); + } + // PUBLIC API ---------------------------------------------------------------- + root = null; + set offset(offset) { + this.#reconciler.offset = offset; + } + dispatch(msg, immediate2 = false) { + this.#shouldFlush ||= immediate2; + if (this.#shouldQueue) { + this.#queue.push(msg); + } else { + const [model, effects] = this.#update(this.#model, msg); + this.#model = model; + this.#tick(effects); + } + } + emit(event4, data) { + const target = this.root.host ?? this.root; + target.dispatchEvent( + new CustomEvent(event4, { + detail: data, + bubbles: true, + composed: true + }) + ); + } + // PRIVATE API --------------------------------------------------------------- + #model; + #view; + #update; + #vdom; + #events; + #reconciler; + #shouldQueue = false; + #queue = []; + #beforePaint = empty_list; + #afterPaint = empty_list; + #renderTimer = null; + #shouldFlush = false; + #actions = { + dispatch: (msg, immediate2) => this.dispatch(msg, immediate2), + emit: (event4, data) => this.emit(event4, data), + select: () => { + }, + root: () => this.root + }; + // A `#tick` is where we process effects and trigger any synchronous updates. + // Once a tick has been processed a render will be scheduled if none is already. + // p0 + #tick(effects) { + this.#shouldQueue = true; + while (true) { + for (let list4 = effects.synchronous; list4.tail; list4 = list4.tail) { + list4.head(this.#actions); + } + this.#beforePaint = listAppend(this.#beforePaint, effects.before_paint); + this.#afterPaint = listAppend(this.#afterPaint, effects.after_paint); + if (!this.#queue.length) break; + [this.#model, effects] = this.#update(this.#model, this.#queue.shift()); + } + this.#shouldQueue = false; + if (this.#shouldFlush) { + cancelAnimationFrame(this.#renderTimer); + this.#render(); + } else if (!this.#renderTimer) { + this.#renderTimer = requestAnimationFrame(() => { + this.#render(); + }); + } + } + #render() { + this.#shouldFlush = false; + this.#renderTimer = null; + const next2 = this.#view(this.#model); + const { patch, events } = diff(this.#events, this.#vdom, next2); + this.#events = events; + this.#vdom = next2; + this.#reconciler.push(patch); + if (this.#beforePaint instanceof NonEmpty) { + const effects = makeEffect(this.#beforePaint); + this.#beforePaint = empty_list; + queueMicrotask(() => { + this.#shouldFlush = true; + this.#tick(effects); + }); + } + if (this.#afterPaint instanceof NonEmpty) { + const effects = makeEffect(this.#afterPaint); + this.#afterPaint = empty_list; + requestAnimationFrame(() => { + this.#shouldFlush = true; + this.#tick(effects); + }); + } + } +}; +var send = (runtime, message2) => { + runtime.send(message2); +}; +function makeEffect(synchronous) { + return { + synchronous, + after_paint: empty_list, + before_paint: empty_list + }; +} +function listAppend(a2, b) { + if (a2 instanceof Empty) { + return b; + } else if (b instanceof Empty) { + return a2; + } else { + return append(a2, b); + } +} +var copiedStyleSheets = /* @__PURE__ */ new WeakMap(); +async function adoptStylesheets(shadowRoot) { + const pendingParentStylesheets = []; + for (const node of document2().querySelectorAll( + "link[rel=stylesheet], style" + )) { + if (node.sheet) continue; + pendingParentStylesheets.push( + new Promise((resolve2, reject2) => { + node.addEventListener("load", resolve2); + node.addEventListener("error", reject2); + }) + ); + } + await Promise.allSettled(pendingParentStylesheets); + if (!shadowRoot.host.isConnected) { + return []; + } + shadowRoot.adoptedStyleSheets = shadowRoot.host.getRootNode().adoptedStyleSheets; + const pending = []; + for (const sheet of document2().styleSheets) { + try { + shadowRoot.adoptedStyleSheets.push(sheet); + } catch { + try { + let copiedSheet = copiedStyleSheets.get(sheet); + if (!copiedSheet) { + copiedSheet = new CSSStyleSheet(); + for (const rule of sheet.cssRules) { + copiedSheet.insertRule(rule.cssText, copiedSheet.cssRules.length); + } + copiedStyleSheets.set(sheet, copiedSheet); + } + shadowRoot.adoptedStyleSheets.push(copiedSheet); + } catch { + const node = sheet.ownerNode.cloneNode(); + shadowRoot.prepend(node); + pending.push(node); + } + } + } + return pending; +} + +// build/dev/javascript/lustre/lustre/runtime/server/runtime.mjs +var EffectDispatchedMessage = class extends CustomType { + constructor(message2) { + super(); + this.message = message2; + } +}; +var EffectEmitEvent = class extends CustomType { + constructor(name6, data) { + super(); + this.name = name6; + this.data = data; + } +}; +var SystemRequestedShutdown = class extends CustomType { +}; + +// build/dev/javascript/lustre/lustre/runtime/client/component.ffi.mjs +var make_component = ({ init: init8, update: update7, view: view8, config }, name6) => { + if (!is_browser()) return new Error(new NotABrowser()); + if (!name6.includes("-")) return new Error(new BadComponentName(name6)); + if (customElements.get(name6)) { + return new Error(new ComponentAlreadyRegistered(name6)); + } + const [model, effects] = init8(void 0); + const observedAttributes = config.attributes.entries().map(([name7]) => name7); + const component2 = class Component extends HTMLElement { + static get observedAttributes() { + return observedAttributes; + } + static formAssociated = config.is_form_associated; + #runtime; + #adoptedStyleNodes = []; + #shadowRoot; + constructor() { + super(); + this.internals = this.attachInternals(); + if (!this.internals.shadowRoot) { + this.#shadowRoot = this.attachShadow({ + mode: config.open_shadow_root ? "open" : "closed" + }); + } else { + this.#shadowRoot = this.internals.shadowRoot; + } + if (config.adopt_styles) { + this.#adoptStyleSheets(); + } + this.#runtime = new Runtime( + this.#shadowRoot, + [model, effects], + view8, + update7 + ); + } + adoptedCallback() { + if (config.adopt_styles) { + this.#adoptStyleSheets(); + } + } + attributeChangedCallback(name7, _, value3) { + const decoded = config.attributes.get(name7)(value3); + if (decoded.constructor === Ok) { + this.dispatch(decoded[0]); + } + } + formResetCallback() { + if (config.on_form_reset instanceof Some) { + this.dispatch(config.on_form_reset[0]); + } + } + formStateRestoreCallback(state, reason) { + switch (reason) { + case "restore": + if (config.on_form_restore instanceof Some) { + this.dispatch(config.on_form_restore[0](state)); + } + break; + case "autocomplete": + if (config.on_form_populate instanceof Some) { + this.dispatch(config.on_form_autofill[0](state)); + } + break; + } + } + send(message2) { + switch (message2.constructor) { + case EffectDispatchedMessage: { + this.dispatch(message2.message, false); + break; + } + case EffectEmitEvent: { + this.emit(message2.name, message2.data); + break; + } + case SystemRequestedShutdown: + break; + } + } + dispatch(msg, immediate2 = false) { + this.#runtime.dispatch(msg, immediate2); + } + emit(event4, data) { + this.#runtime.emit(event4, data); + } + async #adoptStyleSheets() { + while (this.#adoptedStyleNodes.length) { + this.#adoptedStyleNodes.pop().remove(); + this.shadowRoot.firstChild.remove(); + } + this.#adoptedStyleNodes = await adoptStylesheets(this.#shadowRoot); + this.#runtime.offset = this.#adoptedStyleNodes.length; + } + }; + config.properties.forEach((decoder2, name7) => { + Object.defineProperty(component2.prototype, name7, { + get() { + return this[`_${name7}`]; + }, + set(value3) { + this[`_${name7}`] = value3; + const decoded = run(value3, decoder2); + if (decoded.constructor === Ok) { + this.dispatch(decoded[0]); + } + } + }); + }); + customElements.define(name6, component2); + return new Ok(void 0); +}; +var set_pseudo_state = (root3, value3) => { + if (!is_browser()) return; + if (root3 instanceof ShadowRoot) { + root3.host.internals.states.add(value3); + } +}; +var remove_pseudo_state = (root3, value3) => { + if (!is_browser()) return; + if (root3 instanceof ShadowRoot) { + root3.host.internals.states.delete(value3); + } +}; + +// build/dev/javascript/lustre/lustre/component.mjs +var Config2 = class extends CustomType { + constructor(open_shadow_root, adopt_styles2, attributes, properties, is_form_associated, on_form_autofill, on_form_reset, on_form_restore) { + super(); + this.open_shadow_root = open_shadow_root; + this.adopt_styles = adopt_styles2; + this.attributes = attributes; + this.properties = properties; + this.is_form_associated = is_form_associated; + this.on_form_autofill = on_form_autofill; + this.on_form_reset = on_form_reset; + this.on_form_restore = on_form_restore; + } +}; +var Option = class extends CustomType { + constructor(apply) { + super(); + this.apply = apply; + } +}; +function new$6(options) { + let init8 = new Config2( + false, + true, + empty_dict(), + empty_dict(), + false, + option_none, + option_none, + option_none + ); + return fold( + options, + init8, + (config, option3) => { + return option3.apply(config); + } + ); +} +function on_attribute_change(name6, decoder2) { + return new Option( + (config) => { + let attributes = insert(config.attributes, name6, decoder2); + let _record = config; + return new Config2( + _record.open_shadow_root, + _record.adopt_styles, + attributes, + _record.properties, + _record.is_form_associated, + _record.on_form_autofill, + _record.on_form_reset, + _record.on_form_restore + ); + } + ); +} +function on_property_change(name6, decoder2) { + return new Option( + (config) => { + let properties = insert(config.properties, name6, decoder2); + let _record = config; + return new Config2( + _record.open_shadow_root, + _record.adopt_styles, + _record.attributes, + properties, + _record.is_form_associated, + _record.on_form_autofill, + _record.on_form_reset, + _record.on_form_restore + ); + } + ); +} +function adopt_styles(adopt) { + return new Option( + (config) => { + let _record = config; + return new Config2( + _record.open_shadow_root, + adopt, + _record.attributes, + _record.properties, + _record.is_form_associated, + _record.on_form_autofill, + _record.on_form_reset, + _record.on_form_restore + ); + } + ); +} +function set_pseudo_state2(value3) { + return before_paint( + (_, root3) => { + return set_pseudo_state(root3, value3); + } + ); +} +function remove_pseudo_state2(value3) { + return before_paint( + (_, root3) => { + return remove_pseudo_state(root3, value3); + } + ); +} + +// build/dev/javascript/lustre/lustre/runtime/client/spa.ffi.mjs +var Spa = class _Spa { + static start({ init: init8, update: update7, view: view8 }, selector, flags) { + if (!is_browser()) return new Error(new NotABrowser()); + const root3 = selector instanceof HTMLElement ? selector : document2().querySelector(selector); + if (!root3) return new Error(new ElementNotFound(selector)); + return new Ok(new _Spa(root3, init8(flags), update7, view8)); + } + #runtime; + constructor(root3, [init8, effects], update7, view8) { + this.#runtime = new Runtime(root3, [init8, effects], view8, update7); + } + send(message2) { + switch (message2.constructor) { + case EffectDispatchedMessage: { + this.dispatch(message2.message, false); + break; + } + case EffectEmitEvent: { + this.emit(message2.name, message2.data); + break; + } + case SystemRequestedShutdown: + break; + } + } + dispatch(msg, immediate2) { + this.#runtime.dispatch(msg, immediate2); + } + emit(event4, data) { + this.#runtime.emit(event4, data); + } +}; +var start = Spa.start; + +// build/dev/javascript/lustre/lustre.mjs +var App = class extends CustomType { + constructor(init8, update7, view8, config) { + super(); + this.init = init8; + this.update = update7; + this.view = view8; + this.config = config; + } +}; +var BadComponentName = class extends CustomType { + constructor(name6) { + super(); + this.name = name6; + } +}; +var ComponentAlreadyRegistered = class extends CustomType { + constructor(name6) { + super(); + this.name = name6; + } +}; +var ElementNotFound = class extends CustomType { + constructor(selector) { + super(); + this.selector = selector; + } +}; +var NotABrowser = class extends CustomType { +}; +function component(init8, update7, view8, options) { + return new App(init8, update7, view8, new$6(options)); +} +function application(init8, update7, view8) { + return new App(init8, update7, view8, new$6(empty_list)); +} +function dispatch(msg) { + return new EffectDispatchedMessage(msg); +} +function start3(app, selector, start_args) { + return guard( + !is_browser(), + new Error(new NotABrowser()), + () => { + return start(app, selector, start_args); + } + ); +} + +// build/dev/javascript/gleam_stdlib/gleam/pair.mjs +function first2(pair) { + let a2 = pair[0]; + return a2; +} +function second(pair) { + let a2 = pair[1]; + return a2; +} + +// build/dev/javascript/lustre/lustre/event.mjs +function emit(event4, data) { + return event2(event4, data); +} +function is_immediate_event(name6) { + if (name6 === "input") { + return true; + } else if (name6 === "change") { + return true; + } else if (name6 === "focus") { + return true; + } else if (name6 === "focusin") { + return true; + } else if (name6 === "focusout") { + return true; + } else if (name6 === "blur") { + return true; + } else if (name6 === "select") { + return true; + } else { + return false; + } +} +function on(name6, handler) { + return event( + name6, + map2(handler, (msg) => { + return new Handler(false, false, msg); + }), + empty_list, + never, + never, + is_immediate_event(name6), + 0, + 0 + ); +} +function on_click(msg) { + return on("click", success(msg)); +} +function on_mouse_down(msg) { + return on("mousedown", success(msg)); +} +function on_mouse_over(msg) { + return on("mouseover", success(msg)); +} +function on_input(msg) { + return on( + "input", + subfield( + toList(["target", "value"]), + string2, + (value3) => { + return success(msg(value3)); + } + ) + ); +} +function on_focus(msg) { + return on("focus", success(msg)); +} +function on_blur(msg) { + return on("blur", success(msg)); +} + +// build/dev/javascript/gleam_stdlib/gleam/uri.mjs +var Uri = class extends CustomType { + constructor(scheme, userinfo, host, port, path2, query, fragment4) { + super(); + this.scheme = scheme; + this.userinfo = userinfo; + this.host = host; + this.port = port; + this.path = path2; + this.query = query; + this.fragment = fragment4; + } +}; +function is_valid_host_within_brackets_char(char) { + return 48 >= char && char <= 57 || 65 >= char && char <= 90 || 97 >= char && char <= 122 || char === 58 || char === 46; +} +function parse_fragment(rest, pieces) { + return new Ok( + (() => { + let _record = pieces; + return new Uri( + _record.scheme, + _record.userinfo, + _record.host, + _record.port, + _record.path, + _record.query, + new Some(rest) + ); + })() + ); +} +function parse_query_with_question_mark_loop(loop$original, loop$uri_string, loop$pieces, loop$size) { + while (true) { + let original = loop$original; + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let size3 = loop$size; + if (uri_string.startsWith("#")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_fragment(rest, pieces); + } else { + let rest = uri_string.slice(1); + let query = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + _record.host, + _record.port, + _record.path, + new Some(query), + _record.fragment + ); + let pieces$1 = _block; + return parse_fragment(rest, pieces$1); + } + } else if (uri_string === "") { + return new Ok( + (() => { + let _record = pieces; + return new Uri( + _record.scheme, + _record.userinfo, + _record.host, + _record.port, + _record.path, + new Some(original), + _record.fragment + ); + })() + ); + } else { + let $ = pop_codeunit(uri_string); + let rest = $[1]; + loop$original = original; + loop$uri_string = rest; + loop$pieces = pieces; + loop$size = size3 + 1; + } + } +} +function parse_query_with_question_mark(uri_string, pieces) { + return parse_query_with_question_mark_loop(uri_string, uri_string, pieces, 0); +} +function parse_path_loop(loop$original, loop$uri_string, loop$pieces, loop$size) { + while (true) { + let original = loop$original; + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let size3 = loop$size; + if (uri_string.startsWith("?")) { + let rest = uri_string.slice(1); + let path2 = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + _record.host, + _record.port, + path2, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_query_with_question_mark(rest, pieces$1); + } else if (uri_string.startsWith("#")) { + let rest = uri_string.slice(1); + let path2 = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + _record.host, + _record.port, + path2, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_fragment(rest, pieces$1); + } else if (uri_string === "") { + return new Ok( + (() => { + let _record = pieces; + return new Uri( + _record.scheme, + _record.userinfo, + _record.host, + _record.port, + original, + _record.query, + _record.fragment + ); + })() + ); + } else { + let $ = pop_codeunit(uri_string); + let rest = $[1]; + loop$original = original; + loop$uri_string = rest; + loop$pieces = pieces; + loop$size = size3 + 1; + } + } +} +function parse_path(uri_string, pieces) { + return parse_path_loop(uri_string, uri_string, pieces, 0); +} +function parse_port_loop(loop$uri_string, loop$pieces, loop$port) { + while (true) { + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let port = loop$port; + if (uri_string.startsWith("0")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10; + } else if (uri_string.startsWith("1")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 1; + } else if (uri_string.startsWith("2")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 2; + } else if (uri_string.startsWith("3")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 3; + } else if (uri_string.startsWith("4")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 4; + } else if (uri_string.startsWith("5")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 5; + } else if (uri_string.startsWith("6")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 6; + } else if (uri_string.startsWith("7")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 7; + } else if (uri_string.startsWith("8")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 8; + } else if (uri_string.startsWith("9")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 9; + } else if (uri_string.startsWith("?")) { + let rest = uri_string.slice(1); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + _record.host, + new Some(port), + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_query_with_question_mark(rest, pieces$1); + } else if (uri_string.startsWith("#")) { + let rest = uri_string.slice(1); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + _record.host, + new Some(port), + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_fragment(rest, pieces$1); + } else if (uri_string.startsWith("/")) { + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + _record.host, + new Some(port), + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_path(uri_string, pieces$1); + } else if (uri_string === "") { + return new Ok( + (() => { + let _record = pieces; + return new Uri( + _record.scheme, + _record.userinfo, + _record.host, + new Some(port), + _record.path, + _record.query, + _record.fragment + ); + })() + ); + } else { + return new Error(void 0); + } + } +} +function parse_port(uri_string, pieces) { + if (uri_string.startsWith(":0")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 0); + } else if (uri_string.startsWith(":1")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 1); + } else if (uri_string.startsWith(":2")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 2); + } else if (uri_string.startsWith(":3")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 3); + } else if (uri_string.startsWith(":4")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 4); + } else if (uri_string.startsWith(":5")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 5); + } else if (uri_string.startsWith(":6")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 6); + } else if (uri_string.startsWith(":7")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 7); + } else if (uri_string.startsWith(":8")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 8); + } else if (uri_string.startsWith(":9")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 9); + } else if (uri_string.startsWith(":")) { + return new Error(void 0); + } else if (uri_string.startsWith("?")) { + let rest = uri_string.slice(1); + return parse_query_with_question_mark(rest, pieces); + } else if (uri_string.startsWith("#")) { + let rest = uri_string.slice(1); + return parse_fragment(rest, pieces); + } else if (uri_string.startsWith("/")) { + return parse_path(uri_string, pieces); + } else if (uri_string === "") { + return new Ok(pieces); + } else { + return new Error(void 0); + } +} +function parse_host_outside_of_brackets_loop(loop$original, loop$uri_string, loop$pieces, loop$size) { + while (true) { + let original = loop$original; + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let size3 = loop$size; + if (uri_string === "") { + return new Ok( + (() => { + let _record = pieces; + return new Uri( + _record.scheme, + _record.userinfo, + new Some(original), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + })() + ); + } else if (uri_string.startsWith(":")) { + let host = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + new Some(host), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_port(uri_string, pieces$1); + } else if (uri_string.startsWith("/")) { + let host = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + new Some(host), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_path(uri_string, pieces$1); + } else if (uri_string.startsWith("?")) { + let rest = uri_string.slice(1); + let host = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + new Some(host), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_query_with_question_mark(rest, pieces$1); + } else if (uri_string.startsWith("#")) { + let rest = uri_string.slice(1); + let host = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + new Some(host), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_fragment(rest, pieces$1); + } else { + let $ = pop_codeunit(uri_string); + let rest = $[1]; + loop$original = original; + loop$uri_string = rest; + loop$pieces = pieces; + loop$size = size3 + 1; + } + } +} +function parse_host_within_brackets_loop(loop$original, loop$uri_string, loop$pieces, loop$size) { + while (true) { + let original = loop$original; + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let size3 = loop$size; + if (uri_string === "") { + return new Ok( + (() => { + let _record = pieces; + return new Uri( + _record.scheme, + _record.userinfo, + new Some(uri_string), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + })() + ); + } else if (uri_string.startsWith("]")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_port(rest, pieces); + } else { + let rest = uri_string.slice(1); + let host = string_codeunit_slice(original, 0, size3 + 1); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + new Some(host), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_port(rest, pieces$1); + } + } else if (uri_string.startsWith("/")) { + if (size3 === 0) { + return parse_path(uri_string, pieces); + } else { + let host = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + new Some(host), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_path(uri_string, pieces$1); + } + } else if (uri_string.startsWith("?")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_query_with_question_mark(rest, pieces); + } else { + let rest = uri_string.slice(1); + let host = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + new Some(host), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_query_with_question_mark(rest, pieces$1); + } + } else if (uri_string.startsWith("#")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_fragment(rest, pieces); + } else { + let rest = uri_string.slice(1); + let host = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + new Some(host), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_fragment(rest, pieces$1); + } + } else { + let $ = pop_codeunit(uri_string); + let char = $[0]; + let rest = $[1]; + let $1 = is_valid_host_within_brackets_char(char); + if ($1) { + loop$original = original; + loop$uri_string = rest; + loop$pieces = pieces; + loop$size = size3 + 1; + } else { + return parse_host_outside_of_brackets_loop( + original, + original, + pieces, + 0 + ); + } + } + } +} +function parse_host_within_brackets(uri_string, pieces) { + return parse_host_within_brackets_loop(uri_string, uri_string, pieces, 0); +} +function parse_host_outside_of_brackets(uri_string, pieces) { + return parse_host_outside_of_brackets_loop(uri_string, uri_string, pieces, 0); +} +function parse_host(uri_string, pieces) { + if (uri_string.startsWith("[")) { + return parse_host_within_brackets(uri_string, pieces); + } else if (uri_string.startsWith(":")) { + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + _record.userinfo, + new Some(""), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_port(uri_string, pieces$1); + } else if (uri_string === "") { + return new Ok( + (() => { + let _record = pieces; + return new Uri( + _record.scheme, + _record.userinfo, + new Some(""), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + })() + ); + } else { + return parse_host_outside_of_brackets(uri_string, pieces); + } +} +function parse_userinfo_loop(loop$original, loop$uri_string, loop$pieces, loop$size) { + while (true) { + let original = loop$original; + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let size3 = loop$size; + if (uri_string.startsWith("@")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_host(rest, pieces); + } else { + let rest = uri_string.slice(1); + let userinfo = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + _record.scheme, + new Some(userinfo), + _record.host, + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_host(rest, pieces$1); + } + } else if (uri_string === "") { + return parse_host(original, pieces); + } else if (uri_string.startsWith("/")) { + return parse_host(original, pieces); + } else if (uri_string.startsWith("?")) { + return parse_host(original, pieces); + } else if (uri_string.startsWith("#")) { + return parse_host(original, pieces); + } else { + let $ = pop_codeunit(uri_string); + let rest = $[1]; + loop$original = original; + loop$uri_string = rest; + loop$pieces = pieces; + loop$size = size3 + 1; + } + } +} +function parse_authority_pieces(string5, pieces) { + return parse_userinfo_loop(string5, string5, pieces, 0); +} +function parse_authority_with_slashes(uri_string, pieces) { + if (uri_string === "//") { + return new Ok( + (() => { + let _record = pieces; + return new Uri( + _record.scheme, + _record.userinfo, + new Some(""), + _record.port, + _record.path, + _record.query, + _record.fragment + ); + })() + ); + } else if (uri_string.startsWith("//")) { + let rest = uri_string.slice(2); + return parse_authority_pieces(rest, pieces); + } else { + return parse_path(uri_string, pieces); + } +} +function parse_scheme_loop(loop$original, loop$uri_string, loop$pieces, loop$size) { + while (true) { + let original = loop$original; + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let size3 = loop$size; + if (uri_string.startsWith("/")) { + if (size3 === 0) { + return parse_authority_with_slashes(uri_string, pieces); + } else { + let scheme = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + new Some(lowercase(scheme)), + _record.userinfo, + _record.host, + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_authority_with_slashes(uri_string, pieces$1); + } + } else if (uri_string.startsWith("?")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_query_with_question_mark(rest, pieces); + } else { + let rest = uri_string.slice(1); + let scheme = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + new Some(lowercase(scheme)), + _record.userinfo, + _record.host, + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_query_with_question_mark(rest, pieces$1); + } + } else if (uri_string.startsWith("#")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_fragment(rest, pieces); + } else { + let rest = uri_string.slice(1); + let scheme = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + new Some(lowercase(scheme)), + _record.userinfo, + _record.host, + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_fragment(rest, pieces$1); + } + } else if (uri_string.startsWith(":")) { + if (size3 === 0) { + return new Error(void 0); + } else { + let rest = uri_string.slice(1); + let scheme = string_codeunit_slice(original, 0, size3); + let _block; + let _record = pieces; + _block = new Uri( + new Some(lowercase(scheme)), + _record.userinfo, + _record.host, + _record.port, + _record.path, + _record.query, + _record.fragment + ); + let pieces$1 = _block; + return parse_authority_with_slashes(rest, pieces$1); + } + } else if (uri_string === "") { + return new Ok( + (() => { + let _record = pieces; + return new Uri( + _record.scheme, + _record.userinfo, + _record.host, + _record.port, + original, + _record.query, + _record.fragment + ); + })() + ); + } else { + let $ = pop_codeunit(uri_string); + let rest = $[1]; + loop$original = original; + loop$uri_string = rest; + loop$pieces = pieces; + loop$size = size3 + 1; + } + } +} +function remove_dot_segments_loop(loop$input, loop$accumulator) { + while (true) { + let input3 = loop$input; + let accumulator = loop$accumulator; + if (input3 instanceof Empty) { + return reverse(accumulator); + } else { + let segment = input3.head; + let rest = input3.tail; + let _block; + if (segment === "") { + let accumulator$12 = accumulator; + _block = accumulator$12; + } else if (segment === ".") { + let accumulator$12 = accumulator; + _block = accumulator$12; + } else if (segment === "..") { + if (accumulator instanceof Empty) { + _block = toList([]); + } else { + let accumulator$12 = accumulator.tail; + _block = accumulator$12; + } + } else { + let segment$1 = segment; + let accumulator$12 = accumulator; + _block = prepend(segment$1, accumulator$12); + } + let accumulator$1 = _block; + loop$input = rest; + loop$accumulator = accumulator$1; + } + } +} +function remove_dot_segments(input3) { + return remove_dot_segments_loop(input3, toList([])); +} +function path_segments(path2) { + return remove_dot_segments(split2(path2, "/")); +} +function to_string6(uri) { + let _block; + let $ = uri.fragment; + if ($ instanceof Some) { + let fragment4 = $[0]; + _block = toList(["#", fragment4]); + } else { + _block = toList([]); + } + let parts = _block; + let _block$1; + let $1 = uri.query; + if ($1 instanceof Some) { + let query = $1[0]; + _block$1 = prepend("?", prepend(query, parts)); + } else { + _block$1 = parts; + } + let parts$1 = _block$1; + let parts$2 = prepend(uri.path, parts$1); + let _block$2; + let $2 = uri.host; + let $3 = starts_with(uri.path, "/"); + if (!$3) { + if ($2 instanceof Some) { + let host = $2[0]; + if (host !== "") { + _block$2 = prepend("/", parts$2); + } else { + _block$2 = parts$2; + } + } else { + _block$2 = parts$2; + } + } else { + _block$2 = parts$2; + } + let parts$3 = _block$2; + let _block$3; + let $4 = uri.host; + let $5 = uri.port; + if ($5 instanceof Some) { + if ($4 instanceof Some) { + let port = $5[0]; + _block$3 = prepend(":", prepend(to_string(port), parts$3)); + } else { + _block$3 = parts$3; + } + } else { + _block$3 = parts$3; + } + let parts$4 = _block$3; + let _block$4; + let $6 = uri.scheme; + let $7 = uri.userinfo; + let $8 = uri.host; + if ($8 instanceof Some) { + if ($7 instanceof Some) { + if ($6 instanceof Some) { + let h = $8[0]; + let u = $7[0]; + let s = $6[0]; + _block$4 = prepend( + s, + prepend( + "://", + prepend(u, prepend("@", prepend(h, parts$4))) + ) + ); + } else { + _block$4 = parts$4; + } + } else if ($6 instanceof Some) { + let h = $8[0]; + let s = $6[0]; + _block$4 = prepend(s, prepend("://", prepend(h, parts$4))); + } else { + let h = $8[0]; + _block$4 = prepend("//", prepend(h, parts$4)); + } + } else if ($7 instanceof Some) { + if ($6 instanceof Some) { + let s = $6[0]; + _block$4 = prepend(s, prepend(":", parts$4)); + } else { + _block$4 = parts$4; + } + } else if ($6 instanceof Some) { + let s = $6[0]; + _block$4 = prepend(s, prepend(":", parts$4)); + } else { + _block$4 = parts$4; + } + let parts$5 = _block$4; + return concat2(parts$5); +} +var empty3 = /* @__PURE__ */ new Uri( + /* @__PURE__ */ new None(), + /* @__PURE__ */ new None(), + /* @__PURE__ */ new None(), + /* @__PURE__ */ new None(), + "", + /* @__PURE__ */ new None(), + /* @__PURE__ */ new None() +); +function parse2(uri_string) { + return parse_scheme_loop(uri_string, uri_string, empty3, 0); +} + +// build/dev/javascript/modem/modem.ffi.mjs +var defaults = { + handle_external_links: false, + handle_internal_links: true +}; +var initial_location = globalThis?.window?.location?.href; +var do_initial_uri = () => { + if (!initial_location) { + return new Error(void 0); + } else { + return new Ok(uri_from_url(new URL(initial_location))); + } +}; +var do_init = (dispatch2, options = defaults) => { + document.addEventListener("click", (event4) => { + const a2 = find_anchor(event4.target); + if (!a2) return; + try { + const url = new URL(a2.href); + const uri = uri_from_url(url); + const is_external = url.host !== window.location.host; + if (!options.handle_external_links && is_external) return; + if (!options.handle_internal_links && !is_external) return; + event4.preventDefault(); + if (!is_external) { + window.history.pushState({}, "", a2.href); + window.requestAnimationFrame(() => { + if (url.hash) { + document.getElementById(url.hash.slice(1))?.scrollIntoView(); + } + }); + } + return dispatch2(uri); + } catch { + return; + } + }); + window.addEventListener("popstate", (e) => { + e.preventDefault(); + const url = new URL(window.location.href); + const uri = uri_from_url(url); + window.requestAnimationFrame(() => { + if (url.hash) { + document.getElementById(url.hash.slice(1))?.scrollIntoView(); + } + }); + dispatch2(uri); + }); + window.addEventListener("modem-push", ({ detail }) => { + dispatch2(detail); + }); + window.addEventListener("modem-replace", ({ detail }) => { + dispatch2(detail); + }); +}; +var find_anchor = (el) => { + if (!el || el.tagName === "BODY") { + return null; + } else if (el.tagName === "A") { + return el; + } else { + return find_anchor(el.parentElement); + } +}; +var uri_from_url = (url) => { + return new Uri( + /* scheme */ + url.protocol ? new Some(url.protocol.slice(0, -1)) : new None(), + /* userinfo */ + new None(), + /* host */ + url.hostname ? new Some(url.hostname) : new None(), + /* port */ + url.port ? new Some(Number(url.port)) : new None(), + /* path */ + url.pathname, + /* query */ + url.search ? new Some(url.search.slice(1)) : new None(), + /* fragment */ + url.hash ? new Some(url.hash.slice(1)) : new None() + ); +}; + +// build/dev/javascript/modem/modem.mjs +function init(handler) { + return from( + (dispatch2) => { + return guard( + !is_browser(), + void 0, + () => { + return do_init( + (uri) => { + let _pipe = uri; + let _pipe$1 = handler(_pipe); + return dispatch2(_pipe$1); + } + ); + } + ); + } + ); +} + +// build/dev/javascript/gleam_regexp/gleam_regexp_ffi.mjs +function compile(pattern, options) { + try { + let flags = "gu"; + if (options.case_insensitive) flags += "i"; + if (options.multi_line) flags += "m"; + return new Ok(new RegExp(pattern, flags)); + } catch (error) { + const number = (error.columnNumber || 0) | 0; + return new Error(new CompileError(error.message, number)); + } +} +function replace3(regex, original_string, replacement) { + regex.lastIndex = 0; + return original_string.replaceAll(regex, replacement); +} + +// build/dev/javascript/gleam_regexp/gleam/regexp.mjs +var CompileError = class extends CustomType { + constructor(error, byte_index) { + super(); + this.error = error; + this.byte_index = byte_index; + } +}; +var Options = class extends CustomType { + constructor(case_insensitive, multi_line) { + super(); + this.case_insensitive = case_insensitive; + this.multi_line = multi_line; + } +}; +function compile2(pattern, options) { + return compile(pattern, options); +} +function from_string(pattern) { + return compile2(pattern, new Options(false, false)); +} + +// build/dev/javascript/lustre_fable/lustre/fable/route.mjs +var Index2 = class extends CustomType { +}; +var Chapter = class extends CustomType { + constructor(chapter2) { + super(); + this.chapter = chapter2; + } +}; +var Story = class extends CustomType { + constructor(chapter2, story3) { + super(); + this.chapter = chapter2; + this.story = story3; + } +}; +var NotFound = class extends CustomType { +}; +function parse3(uri) { + let $ = path_segments(uri.path); + if ($ instanceof Empty) { + return new Index2(); + } else { + let $1 = $.tail; + if ($1 instanceof Empty) { + return new NotFound(); + } else { + let $2 = $1.tail; + if ($2 instanceof Empty) { + let $3 = $.head; + if ($3 === "chapter") { + let chapter2 = $1.head; + return new Chapter(chapter2); + } else { + return new NotFound(); + } + } else { + let $3 = $2.tail; + if ($3 instanceof Empty) { + return new NotFound(); + } else { + let $4 = $3.tail; + if ($4 instanceof Empty) { + let $5 = $2.head; + if ($5 === "story") { + let $6 = $.head; + if ($6 === "chapter") { + let chapter2 = $1.head; + let story3 = $3.head; + return new Story(chapter2, story3); + } else { + return new NotFound(); + } + } else { + return new NotFound(); + } + } else { + return new NotFound(); + } + } + } + } + } +} +function href2(route) { + return href( + (() => { + if (route instanceof Index2) { + return "/"; + } else if (route instanceof Chapter) { + let chapter2 = route.chapter; + return "/chapter/" + chapter2; + } else if (route instanceof Story) { + let chapter2 = route.chapter; + let story3 = route.story; + return "/chapter/" + chapter2 + "/story/" + story3; + } else { + return "#"; + } + })() + ); +} + +// build/dev/javascript/gleam_http/gleam/http.mjs +var Get = class extends CustomType { +}; +var Post = class extends CustomType { +}; +var Head = class extends CustomType { +}; +var Put = class extends CustomType { +}; +var Delete = class extends CustomType { +}; +var Trace = class extends CustomType { +}; +var Connect = class extends CustomType { +}; +var Options2 = class extends CustomType { +}; +var Patch2 = class extends CustomType { +}; +var Http = class extends CustomType { +}; +var Https = class extends CustomType { +}; +function method_to_string(method) { + if (method instanceof Get) { + return "GET"; + } else if (method instanceof Post) { + return "POST"; + } else if (method instanceof Head) { + return "HEAD"; + } else if (method instanceof Put) { + return "PUT"; + } else if (method instanceof Delete) { + return "DELETE"; + } else if (method instanceof Trace) { + return "TRACE"; + } else if (method instanceof Connect) { + return "CONNECT"; + } else if (method instanceof Options2) { + return "OPTIONS"; + } else if (method instanceof Patch2) { + return "PATCH"; + } else { + let s = method[0]; + return s; + } +} +function scheme_to_string(scheme) { + if (scheme instanceof Http) { + return "http"; + } else { + return "https"; + } +} +function scheme_from_string(scheme) { + let $ = lowercase(scheme); + if ($ === "http") { + return new Ok(new Http()); + } else if ($ === "https") { + return new Ok(new Https()); + } else { + return new Error(void 0); + } +} + +// build/dev/javascript/gleam_http/gleam/http/request.mjs +var Request = class extends CustomType { + constructor(method, headers, body, scheme, host, port, path2, query) { + super(); + this.method = method; + this.headers = headers; + this.body = body; + this.scheme = scheme; + this.host = host; + this.port = port; + this.path = path2; + this.query = query; + } +}; +function to_uri(request) { + return new Uri( + new Some(scheme_to_string(request.scheme)), + new None(), + new Some(request.host), + request.port, + request.path, + request.query, + new None() + ); +} +function from_uri(uri) { + return then$2( + (() => { + let _pipe = uri.scheme; + let _pipe$1 = unwrap(_pipe, ""); + return scheme_from_string(_pipe$1); + })(), + (scheme) => { + return then$2( + (() => { + let _pipe = uri.host; + return to_result(_pipe, void 0); + })(), + (host) => { + let req = new Request( + new Get(), + toList([]), + "", + scheme, + host, + uri.port, + uri.path, + uri.query + ); + return new Ok(req); + } + ); + } + ); +} + +// build/dev/javascript/gleam_http/gleam/http/response.mjs +var Response = class extends CustomType { + constructor(status, headers, body) { + super(); + this.status = status; + this.headers = headers; + this.body = body; + } +}; +function get_header(response, key) { + return key_find(response.headers, lowercase(key)); +} + +// build/dev/javascript/gleam_javascript/gleam_javascript_ffi.mjs +var PromiseLayer = class _PromiseLayer { + constructor(promise) { + this.promise = promise; + } + static wrap(value3) { + return value3 instanceof Promise ? new _PromiseLayer(value3) : value3; + } + static unwrap(value3) { + return value3 instanceof _PromiseLayer ? value3.promise : value3; + } +}; +function resolve(value3) { + return Promise.resolve(PromiseLayer.wrap(value3)); +} +function then_await(promise, fn) { + return promise.then((value3) => fn(PromiseLayer.unwrap(value3))); +} +function map_promise(promise, fn) { + return promise.then( + (value3) => PromiseLayer.wrap(fn(PromiseLayer.unwrap(value3))) + ); +} + +// build/dev/javascript/gleam_javascript/gleam/javascript/promise.mjs +function tap(promise, callback) { + let _pipe = promise; + return map_promise( + _pipe, + (a2) => { + callback(a2); + return a2; + } + ); +} +function try_await(promise, callback) { + let _pipe = promise; + return then_await( + _pipe, + (result) => { + if (result instanceof Ok) { + let a2 = result[0]; + return callback(a2); + } else { + let e = result[0]; + return resolve(new Error(e)); + } + } + ); +} + +// build/dev/javascript/gleam_fetch/gleam_fetch_ffi.mjs +async function raw_send(request) { + try { + return new Ok(await fetch(request)); + } catch (error) { + return new Error(new NetworkError(error.toString())); + } +} +function from_fetch_response(response) { + return new Response( + response.status, + List.fromArray([...response.headers]), + response + ); +} +function request_common(request) { + let url = to_string6(to_uri(request)); + let method = method_to_string(request.method).toUpperCase(); + let options = { + headers: make_headers(request.headers), + method + }; + return [url, options]; +} +function to_fetch_request(request) { + let [url, options] = request_common(request); + if (options.method !== "GET" && options.method !== "HEAD") options.body = request.body; + return new globalThis.Request(url, options); +} +function make_headers(headersList) { + let headers = new globalThis.Headers(); + for (let [k, v] of headersList) headers.append(k.toLowerCase(), v); + return headers; +} +async function read_text_body(response) { + let body; + try { + body = await response.body.text(); + } catch (error) { + return new Error(new UnableToReadBody()); + } + return new Ok(response.withFields({ body })); +} + +// build/dev/javascript/gleam_fetch/gleam/fetch.mjs +var NetworkError = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var UnableToReadBody = class extends CustomType { +}; +function send2(request) { + let _pipe = request; + let _pipe$1 = to_fetch_request(_pipe); + let _pipe$2 = raw_send(_pipe$1); + return try_await( + _pipe$2, + (resp) => { + return resolve(new Ok(from_fetch_response(resp))); + } + ); +} + +// build/dev/javascript/rsvp/rsvp.ffi.mjs +var from_relative_url = (url_string) => { + if (!globalThis.location) return new Error(void 0); + const url = new URL(url_string, globalThis.location.href); + const uri = uri_from_url2(url); + return new Ok(uri); +}; +var uri_from_url2 = (url) => { + const optional2 = (value3) => value3 ? new Some(value3) : new None(); + return new Uri( + /* scheme */ + optional2(url.protocol?.slice(0, -1)), + /* userinfo */ + new None(), + /* host */ + optional2(url.hostname), + /* port */ + optional2(url.port && Number(url.port)), + /* path */ + url.pathname, + /* query */ + optional2(url.search?.slice(1)), + /* fragment */ + optional2(url.hash?.slice(1)) + ); +}; + +// build/dev/javascript/rsvp/rsvp.mjs +var BadBody = class extends CustomType { +}; +var BadUrl = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var HttpError = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var NetworkError2 = class extends CustomType { +}; +var UnhandledResponse = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var Handler2 = class extends CustomType { + constructor(run2) { + super(); + this.run = run2; + } +}; +function expect_ok_response(handler) { + return new Handler2( + (result) => { + return handler( + try$( + result, + (response) => { + let $ = response.status; + let code2 = $; + if (code2 >= 200 && code2 < 300) { + return new Ok(response); + } else { + let code$1 = $; + if (code$1 >= 400 && code$1 < 600) { + return new Error(new HttpError(response)); + } else { + return new Error(new UnhandledResponse(response)); + } + } + } + ) + ); + } + ); +} +function expect_text_response(handler) { + return expect_ok_response( + (result) => { + return handler( + try$( + result, + (response) => { + let $ = get_header(response, "content-type"); + if ($ instanceof Ok) { + let $1 = $[0]; + if ($1.startsWith("text/")) { + return new Ok(response); + } else { + return new Error(new UnhandledResponse(response)); + } + } else { + return new Error(new UnhandledResponse(response)); + } + } + ) + ); + } + ); +} +function expect_text(handler) { + return expect_text_response( + (result) => { + let _pipe = result; + let _pipe$1 = map3(_pipe, (response) => { + return response.body; + }); + return handler(_pipe$1); + } + ); +} +function do_send(request, handler) { + return from( + (dispatch2) => { + let _pipe = send2(request); + let _pipe$1 = try_await(_pipe, read_text_body); + let _pipe$2 = map_promise( + _pipe$1, + (_capture) => { + return map_error( + _capture, + (error) => { + if (error instanceof NetworkError) { + return new NetworkError2(); + } else if (error instanceof UnableToReadBody) { + return new BadBody(); + } else { + return new BadBody(); + } + } + ); + } + ); + let _pipe$3 = map_promise(_pipe$2, handler.run); + tap(_pipe$3, dispatch2); + return void 0; + } + ); +} +function send3(request, handler) { + return do_send(request, handler); +} +function reject(err, handler) { + return from( + (dispatch2) => { + let _pipe = new Error(err); + let _pipe$1 = handler.run(_pipe); + return dispatch2(_pipe$1); + } + ); +} +function to_uri2(uri_string) { + let _block; + if (uri_string.startsWith("./")) { + _block = from_relative_url(uri_string); + } else if (uri_string.startsWith("/")) { + _block = from_relative_url(uri_string); + } else { + _block = parse2(uri_string); + } + let _pipe = _block; + return replace_error(_pipe, new BadUrl(uri_string)); +} +function get2(url, handler) { + let $ = to_uri2(url); + if ($ instanceof Ok) { + let uri = $[0]; + let _pipe = from_uri(uri); + let _pipe$1 = map3( + _pipe, + (_capture) => { + return send3(_capture, handler); + } + ); + let _pipe$2 = map_error( + _pipe$1, + (_) => { + return reject(new BadUrl(url), handler); + } + ); + return unwrap_both(_pipe$2); + } else { + let err = $[0]; + return reject(err, handler); + } +} + +// build/dev/javascript/lustre/lustre/element/keyed.mjs +function do_extract_keyed_children(loop$key_children_pairs, loop$keyed_children, loop$children, loop$children_count) { + while (true) { + let key_children_pairs = loop$key_children_pairs; + let keyed_children = loop$keyed_children; + let children = loop$children; + let children_count = loop$children_count; + if (key_children_pairs instanceof Empty) { + return [keyed_children, reverse(children), children_count]; + } else { + let rest = key_children_pairs.tail; + let key = key_children_pairs.head[0]; + let element$1 = key_children_pairs.head[1]; + let keyed_element = to_keyed(key, element$1); + let _block; + if (key === "") { + _block = keyed_children; + } else { + _block = insert3(keyed_children, key, keyed_element); + } + let keyed_children$1 = _block; + let children$1 = prepend(keyed_element, children); + let children_count$1 = children_count + advance(keyed_element); + loop$key_children_pairs = rest; + loop$keyed_children = keyed_children$1; + loop$children = children$1; + loop$children_count = children_count$1; + } + } +} +function extract_keyed_children(children) { + return do_extract_keyed_children( + children, + empty2(), + empty_list, + 0 + ); +} +function element3(tag, attributes, children) { + let $ = extract_keyed_children(children); + let keyed_children = $[0]; + let children$1 = $[1]; + return element( + "", + identity2, + "", + tag, + attributes, + children$1, + keyed_children, + false, + false + ); +} +function fragment3(children) { + let $ = extract_keyed_children(children); + let keyed_children = $[0]; + let children$1 = $[1]; + let children_count = $[2]; + return fragment( + "", + identity2, + children$1, + keyed_children, + children_count + ); +} +function ul2(attributes, children) { + return element3("ul", attributes, children); +} +function div2(attributes, children) { + return element3("div", attributes, children); +} + +// build/dev/javascript/lustre/lustre/element/svg.mjs +var namespace = "http://www.w3.org/2000/svg"; +function circle(attrs) { + return namespaced(namespace, "circle", attrs, empty_list); +} +function rect(attrs) { + return namespaced(namespace, "rect", attrs, empty_list); +} +function path(attrs) { + return namespaced(namespace, "path", attrs, empty_list); +} + +// build/dev/javascript/lustre_fable/lustre/fable/ui/icon.mjs +function sidebar(attributes) { + return svg( + prepend( + attribute2("width", "24"), + prepend( + attribute2("height", "24"), + prepend( + attribute2("viewBox", "0 0 24 24"), + prepend( + attribute2("fill", "none"), + prepend( + attribute2("stroke", "currentColor"), + prepend( + attribute2("stroke-width", "2"), + prepend( + attribute2("stroke-linecap", "round"), + prepend(attribute2("stroke-linejoin", "round"), attributes) + ) + ) + ) + ) + ) + ) + ), + toList([ + rect( + toList([ + attribute2("width", "18"), + attribute2("height", "18"), + attribute2("x", "3"), + attribute2("y", "3"), + attribute2("rx", "2") + ]) + ), + path(toList([attribute2("d", "M15 3v18")])) + ]) + ); +} +function hamburger(attributes) { + return svg( + prepend( + attribute2("width", "24"), + prepend( + attribute2("height", "24"), + prepend( + attribute2("viewBox", "0 0 24 24"), + prepend( + attribute2("fill", "none"), + prepend( + attribute2("stroke", "currentColor"), + prepend( + attribute2("stroke-width", "2"), + prepend( + attribute2("stroke-linecap", "round"), + prepend(attribute2("stroke-linejoin", "round"), attributes) + ) + ) + ) + ) + ) + ) + ), + toList([ + path(toList([attribute2("d", "M4 12h16")])), + path(toList([attribute2("d", "M4 18h16")])), + path(toList([attribute2("d", "M4 6h16")])) + ]) + ); +} +function search(attributes) { + return svg( + prepend( + attribute2("width", "24"), + prepend( + attribute2("height", "24"), + prepend( + attribute2("viewBox", "0 0 24 24"), + prepend( + attribute2("fill", "none"), + prepend( + attribute2("stroke", "currentColor"), + prepend( + attribute2("stroke-width", "2"), + prepend( + attribute2("stroke-linecap", "round"), + prepend(attribute2("stroke-linejoin", "round"), attributes) + ) + ) + ) + ) + ) + ) + ), + toList([ + path(toList([attribute2("d", "m21 21-4.34-4.34")])), + circle( + toList([ + attribute2("cx", "11"), + attribute2("cy", "11"), + attribute2("r", "8") + ]) + ) + ]) + ); +} +function lock(attributes) { + return svg( + prepend( + attribute2("width", "24"), + prepend( + attribute2("height", "24"), + prepend( + attribute2("viewBox", "0 0 24 24"), + prepend( + attribute2("fill", "none"), + prepend( + attribute2("stroke", "currentColor"), + prepend( + attribute2("stroke-width", "2"), + prepend( + attribute2("stroke-linecap", "round"), + prepend(attribute2("stroke-linejoin", "round"), attributes) + ) + ) + ) + ) + ) + ) + ), + toList([ + rect( + toList([ + attribute2("width", "18"), + attribute2("height", "11"), + attribute2("x", "3"), + attribute2("y", "11"), + attribute2("rx", "2"), + attribute2("ry", "2") + ]) + ), + path(toList([attribute2("d", "M7 11V7a5 5 0 0 1 10 0v4")])) + ]) + ); +} + +// build/dev/javascript/lustre_fable/lustre/fable/ui/layout.mjs +var Layout = class extends CustomType { + constructor(is_mobile, is_sidebar_open) { + super(); + this.is_mobile = is_mobile; + this.is_sidebar_open = is_sidebar_open; + } +}; +function view_container(layout, children) { + let _block; + let $ = layout.is_sidebar_open; + if ($) { + _block = "30ch"; + } else { + _block = "0px"; + } + let sidebar_width = _block; + let _block$1; + let $1 = layout.is_mobile; + let $2 = layout.is_sidebar_open; + if ($1) { + if ($2) { + _block$1 = "calc(100vh - 3rem)"; + } else { + _block$1 = "0px"; + } + } else { + _block$1 = "100%"; + } + let sidebar_height = _block$1; + return div2( + toList([ + style("--sidebar-width", sidebar_width), + style("--sidebar-height", sidebar_height), + class$("h-screen lg:transition-[grid-template-columns]"), + class$("grid grid-rows-[3rem_1fr] grid-cols-[1fr]"), + class$("lg:grid-cols-[var(--sidebar-width)_1fr]") + ]), + children + ); +} +function view_header(content3) { + return header( + toList([ + class$("flex items-center px-4 border-b"), + class$("lg:col-start-2") + ]), + content3 + ); +} +function view_sidebar(content3) { + return aside( + toList([ + class$( + "\n overflow-hidden text-sm max-h-[var(--sidebar-height)] bg-white z-10\n col-start-1 row-start-2 transition-[height]\n\n lg:row-start-1 lg:row-span-2 lg:border-r\n " + ) + ]), + toList([ + div( + toList([class$("min-w-[30ch] *:px-4 space-y-4")]), + content3 + ) + ]) + ); +} +function view_content(content3) { + return main( + toList([ + class$("col-start-1 row-start-2 overflow-auto"), + class$("lg:col-start-2") + ]), + content3 + ); +} +function view_story_scene(scene2) { + return main( + toList([ + class$("p-4 grid grid-cols-1 grid-rows-1 place-items-center") + ]), + toList([scene2]) + ); +} +function view_story_controls(controls) { + return aside( + toList([ + class$("p-4 border-t flex flex-col gap-4 h-full"), + class$("@3xl:border-t-0 @3xl:border-l") + ]), + toList([controls]) + ); +} +function story(scene2, controls) { + let should_show_controls = !isEqual(controls, none2()); + return div( + toList([class$("@container h-full")]), + toList([ + div( + toList([ + classes( + toList([ + ["h-full grid grid-cols-1 @3xl:grid-rows-1", true], + ["grid-rows-[1fr_300px]", should_show_controls], + ["@3xl:grid-cols-[1fr_300px]", should_show_controls] + ]) + ) + ]), + toList([view_story_scene(scene2), view_story_controls(controls)]) + ) + ]) + ); +} +var css = '/*! tailwindcss v4.1.8 | MIT License | https://tailwindcss.com */\n@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-blue-50:oklch(97% .014 254.604);--color-blue-500:oklch(62.3% .214 259.815);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-100:oklch(97% .001 106.424);--color-stone-200:oklch(92.3% .003 48.717);--color-white:#fff;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--font-weight-semibold:600;--radius-sm:.25rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:after,:before,::backdrop{border-color:var(--color-stone-200,currentColor)}::file-selector-button{border-color:var(--color-stone-200,currentColor)}}@layer components;@layer utilities{.\\@container{container-type:inline-size}.absolute{position:absolute}.relative{position:relative}.-top-2{top:calc(var(--spacing)*-2)}.right-2{right:calc(var(--spacing)*2)}.z-10{z-index:10}.col-start-1{grid-column-start:1}.row-start-2{grid-row-start:2}.block{display:block}.flex{display:flex}.grid{display:grid}.inline-grid{display:inline-grid}.size-3{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-\\[var\\(--sidebar-height\\)\\]{max-height:var(--sidebar-height)}.min-h-12{min-height:calc(var(--spacing)*12)}.w-full{width:100%}.min-w-\\[30ch\\]{min-width:30ch}.min-w-max{min-width:max-content}.flex-1{flex:1}.border-collapse{border-collapse:collapse}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-\\[1fr\\]{grid-template-columns:1fr}.grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.grid-rows-\\[1fr_300px\\]{grid-template-rows:1fr 300px}.grid-rows-\\[3rem_1fr\\]{grid-template-rows:3rem 1fr}.flex-col{flex-direction:column}.place-items-center{place-items:center}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing)*2)}.gap-4{gap:calc(var(--spacing)*4)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-blue-500\\/0{border-color:#0000}@supports (color:color-mix(in lab, red, red)){.border-blue-500\\/0{border-color:color-mix(in oklab,var(--color-blue-500)0%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-stone-100{background-color:var(--color-stone-100)}.bg-stone-200{background-color:var(--color-stone-200)}.bg-white{background-color:var(--color-white)}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-1{padding-block:calc(var(--spacing)*1)}.py-2{padding-block:calc(var(--spacing)*2)}.py-4{padding-block:calc(var(--spacing)*4)}.pl-2{padding-left:calc(var(--spacing)*2)}.text-center{text-align:center}.text-justify{text-align:justify}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-blue-500{color:var(--color-blue-500)}.lowercase{text-transform:lowercase}.underline{text-decoration-line:underline}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\\[height\\]{transition-property:height;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}:is(.\\*\\:px-4>*){padding-inline:calc(var(--spacing)*4)}.focus-within\\:border-blue-500:focus-within{border-color:var(--color-blue-500)}@media (hover:hover){.hover\\:border-blue-500\\/100:hover{border-color:var(--color-blue-500)}.hover\\:bg-stone-50:hover{background-color:var(--color-stone-50)}.hover\\:underline:hover{text-decoration-line:underline}}.focus\\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\\:outline-none:focus{--tw-outline-style:none;outline-style:none}@media (min-width:64rem){.lg\\:col-start-2{grid-column-start:2}.lg\\:row-span-2{grid-row:span 2/span 2}.lg\\:row-start-1{grid-row-start:1}.lg\\:grid-cols-\\[var\\(--sidebar-width\\)_1fr\\]{grid-template-columns:var(--sidebar-width)1fr}.lg\\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.lg\\:transition-\\[grid-template-columns\\]{transition-property:grid-template-columns;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}}@container (min-width:48rem){.\\@3xl\\:grid-cols-\\[1fr_300px\\]{grid-template-columns:1fr 300px}.\\@3xl\\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.\\@3xl\\:border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.\\@3xl\\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}'; +function shell(layout, handle_sidebar_toggle, header_content, sidebar_content, main_content) { + let header_sidebar_toggle = button( + toList([ + on_click(handle_sidebar_toggle), + class$("p-1 border rounded") + ]), + toList([ + (() => { + let $2 = layout.is_mobile; + if ($2) { + return hamburger(toList([class$("size-4")])); + } else { + return sidebar(toList([class$("size-4")])); + } + })() + ]) + ); + let _block; + let $ = layout.is_mobile; + if ($) { + _block = "mobile-sidebar"; + } else { + _block = "desktop-sidebar"; + } + let sidebar_key = _block; + return fragment2( + toList([ + style2(toList([]), css), + view_container( + layout, + toList([ + [ + "header", + view_header(prepend(header_sidebar_toggle, header_content)) + ], + [sidebar_key, view_sidebar(sidebar_content)], + ["content", view_content(main_content)] + ]) + ) + ]) + ); +} + +// build/dev/javascript/lustre_fable/lustre/fable/story.mjs +var FILEPATH = "src/lustre/fable/story.gleam"; +var Story2 = class extends CustomType { + constructor(title3, route, component2, scene2) { + super(); + this.title = title3; + this.route = route; + this.component = component2; + this.scene = scene2; + } +}; +var StoryBuilder = class extends CustomType { + constructor(run2) { + super(); + this.run = run2; + } +}; +var StoryConfig = class extends CustomType { + constructor(title3, inputs, sequences2, options, view8) { + super(); + this.title = title3; + this.inputs = inputs; + this.sequences = sequences2; + this.options = options; + this.view = view8; + } +}; +var Controls = class extends CustomType { + constructor(lookup) { + super(); + this.lookup = lookup; + } +}; +var Model = class extends CustomType { + constructor(lookup, sequences2, sequence, controls_visible, tab) { + super(); + this.lookup = lookup; + this.sequences = sequences2; + this.sequence = sequence; + this.controls_visible = controls_visible; + this.tab = tab; + } +}; +var TabControls = class extends CustomType { +}; +var TabSequences = class extends CustomType { +}; +var ComponentUpdatedValue = class extends CustomType { + constructor(key, value3) { + super(); + this.key = key; + this.value = value3; + } +}; +var ExternalStylesheetLoaded = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var ParentSetControlsVisibility = class extends CustomType { + constructor(controls_visible) { + super(); + this.controls_visible = controls_visible; + } +}; +var UserChangedTab = class extends CustomType { + constructor(tab) { + super(); + this.tab = tab; + } +}; +var UserEditedValue = class extends CustomType { + constructor(key, value3) { + super(); + this.key = key; + this.value = value3; + } +}; +var SceneModel = class extends CustomType { + constructor(lookup, stylesheets, pending_stylesheets) { + super(); + this.lookup = lookup; + this.stylesheets = stylesheets; + this.pending_stylesheets = pending_stylesheets; + } +}; +function story_init(_, sequences2, controls_visible) { + let model = new Model( + new_map(), + sequences2, + toList([]), + controls_visible, + new TabControls() + ); + return [model, none()]; +} +function story_update(model, msg) { + if (msg instanceof ComponentUpdatedValue) { + let key = msg.key; + let value3 = msg.value; + return [ + (() => { + let _record = model; + return new Model( + insert(model.lookup, key, value3), + _record.sequences, + _record.sequence, + _record.controls_visible, + _record.tab + ); + })(), + none() + ]; + } else if (msg instanceof ExternalStylesheetLoaded) { + return [model, none()]; + } else if (msg instanceof ParentSetControlsVisibility) { + let controls_visible = msg.controls_visible; + return [ + (() => { + let _record = model; + return new Model( + _record.lookup, + _record.sequences, + _record.sequence, + controls_visible, + _record.tab + ); + })(), + none() + ]; + } else if (msg instanceof UserChangedTab) { + let tab = msg.tab; + return [ + (() => { + let _record = model; + return new Model( + _record.lookup, + _record.sequences, + _record.sequence, + _record.controls_visible, + tab + ); + })(), + none() + ]; + } else { + let key = msg.key; + let value3 = msg.value; + return [ + (() => { + let _record = model; + return new Model( + insert( + model.lookup, + key, + (() => { + let _pipe = value3; + let _pipe$1 = to_string3(_pipe); + let _pipe$2 = parse(_pipe$1, dynamic); + return unwrap2(_pipe$2, nil()); + })() + ), + _record.sequences, + _record.sequence, + _record.controls_visible, + _record.tab + ); + })(), + none() + ]; + } +} +function story_view_toggle(active) { + let classes2 = (tab) => { + return classes( + toList([ + ["flex-1 text-center px-3 py-1 rounded-sm cursor-pointer", true], + ["hover:bg-stone-50", true], + ["bg-white", isEqual(tab, active)] + ]) + ); + }; + return div( + toList([class$("flex gap-2 rounded bg-stone-100 p-2")]), + toList([ + button( + toList([ + on_click(new UserChangedTab(new TabControls())), + classes2(new TabControls()) + ]), + toList([text3("Controls")]) + ), + button( + toList([ + on_click(new UserChangedTab(new TabSequences())), + classes2(new TabSequences()) + ]), + toList([text3("Sequences")]) + ) + ]) + ); +} +function scene_init(_, stylesheets, external_stylesheets) { + let model = new SceneModel( + new_map(), + stylesheets, + length(external_stylesheets) + ); + let _block; + let _pipe = external_stylesheets; + let _pipe$1 = map( + _pipe, + (_capture) => { + return get2( + _capture, + expect_text( + (var0) => { + return new ExternalStylesheetLoaded(var0); + } + ) + ); + } + ); + _block = batch(_pipe$1); + let effect = _block; + return [model, effect]; +} +function scene_update(model, msg) { + if (msg instanceof ComponentUpdatedValue) { + let key = msg.key; + let value3 = msg.value; + return [ + (() => { + let _record = model; + return new SceneModel( + insert(model.lookup, key, value3), + _record.stylesheets, + _record.pending_stylesheets + ); + })(), + none() + ]; + } else if (msg instanceof ExternalStylesheetLoaded) { + let $ = msg[0]; + if ($ instanceof Ok) { + let css2 = $[0]; + let _block; + let _record = model; + _block = new SceneModel( + _record.lookup, + prepend(css2, model.stylesheets), + model.pending_stylesheets - 1 + ); + let model$1 = _block; + return [model$1, none()]; + } else { + let _block; + let _record = model; + _block = new SceneModel( + _record.lookup, + _record.stylesheets, + model.pending_stylesheets - 1 + ); + let model$1 = _block; + return [model$1, none()]; + } + } else if (msg instanceof ParentSetControlsVisibility) { + return [model, none()]; + } else if (msg instanceof UserChangedTab) { + return [model, none()]; + } else { + let key = msg.key; + let value3 = msg.value; + return [ + model, + emit( + "change", + object2(toList([["key", int3(key)], ["value", value3]])) + ) + ]; + } +} +function scene_view(model, view8) { + return fragment2( + flatten( + toList([ + map( + model.stylesheets, + (css2) => { + return style2( + toList([]), + replace(css2, ":root", ":host") + ); + } + ), + toList([ + (() => { + let $ = model.pending_stylesheets; + if ($ === 0) { + return view8(new Controls(model.lookup)); + } else { + return none2(); + } + })(), + style2( + toList([]), + "\n :host {\n border-collapse: revert;\n border-spacing: revert;\n caption-side: revert;\n color: revert;\n cursor: revert;\n direction: revert;\n empty-cells: revert;\n font-family: revert;\n font-size: revert;\n font-style: revert;\n font-variant: revert;\n font-weight: revert;\n font-size-adjust: revert;\n font-stretch: revert;\n font: revert;\n letter-spacing: revert;\n line-height: revert;\n list-style-image: revert;\n list-style-position: revert;\n list-style-type: revert;\n list-style: revert;\n orphans: revert;\n quotes: revert;\n tab-size: revert;\n text-align: revert;\n text-align-last: revert;\n text-decoration-color: revert;\n text-indent: revert;\n text-justify: revert;\n text-shadow: revert;\n text-transform: revert;\n visibility: revert;\n white-space: revert;\n widows: revert;\n word-break: revert;\n word-spacing: revert;\n word-wrap: revert;\n }\n " + ) + ]) + ]) + ) + ); +} +function do_base_tag(loop$base, loop$count) { + while (true) { + let base = loop$base; + let count = loop$count; + let $ = is_registered(base + "-" + to_string(count)); + if ($) { + loop$base = base; + loop$count = count + 1; + } else { + return base + "-" + to_string(count); + } + } +} +function base_tag(title3) { + let $ = from_string("[^a-zA-Z0-9]+"); + if (!($ instanceof Ok)) { + throw makeError( + "let_assert", + FILEPATH, + "lustre/fable/story", + 425, + "base_tag", + "Pattern match failed, no pattern matched the value.", + { + value: $, + start: 10565, + end: 10620, + pattern_start: 10576, + pattern_end: 10582 + } + ); + } + let re = $[0]; + let _block; + let _pipe = title3; + let _pipe$1 = lowercase(_pipe); + _block = ((_capture) => { + return replace3(re, _capture, "-"); + })( + _pipe$1 + ); + let safe_component_name = _block; + let $1 = is_registered(safe_component_name); + if ($1) { + return do_base_tag(safe_component_name, 1); + } else { + return safe_component_name; + } +} +function story_view(model, scene2, inputs) { + let handle_change = subfield( + toList(["detail", "key"]), + int2, + (key) => { + return subfield( + toList(["detail", "value"]), + dynamic, + (value3) => { + return success(new ComponentUpdatedValue(key, value3)); + } + ); + } + ); + let attributes = fold2( + model.lookup, + toList([]), + (attributes2, key, value3) => { + return prepend( + property2(to_string(key), identity2(value3)), + attributes2 + ); + } + ); + return story( + element2( + scene2, + prepend( + on("change", handle_change), + prepend( + class$( + "rounded p-4 border-dashed border border-blue-500/0" + ), + prepend( + class$("transition hover:border-blue-500/100"), + attributes + ) + ) + ), + toList([]) + ), + (() => { + let $ = is_empty(inputs) && is_empty2(model.sequences); + if ($) { + return none2(); + } else { + return fragment2( + toList([ + story_view_toggle(model.tab), + div( + toList([class$("flex flex-col overflow-y-auto")]), + (() => { + let $1 = model.tab; + if ($1 instanceof TabControls) { + return map( + inputs, + (input3) => { + return input3(new Controls(model.lookup)); + } + ); + } else { + return toList([ + p( + toList([ + class$( + "flex-1 flex justify-center items-center" + ) + ]), + toList([ + text3( + "Sequences are currently unsupported in this pre-release." + ) + ]) + ) + ]); + } + })() + ) + ]) + ); + } + })() + ); +} +function register(config, stylesheets, external_stylesheets) { + let base = base_tag(config.title); + let component2 = base + "-story"; + let scene2 = base + "-scene"; + return try$( + make_component( + component( + (_capture) => { + return story_init( + _capture, + fold( + config.sequences, + new_map(), + (sequences2, sequence) => { + return insert(sequences2, sequence.key, sequence.messages); + } + ), + !is_empty(config.inputs) + ); + }, + story_update, + (_capture) => { + return story_view(_capture, scene2, config.inputs); + }, + toList([ + on_property_change( + "controls", + (() => { + let _pipe = bool; + return map2( + _pipe, + (var0) => { + return new ParentSetControlsVisibility(var0); + } + ); + })() + ) + ]) + ), + component2 + ), + (_) => { + return try$( + make_component( + component( + (_capture) => { + return scene_init(_capture, stylesheets, external_stylesheets); + }, + scene_update, + (_capture) => { + return scene_view(_capture, config.view); + }, + config.options + ), + scene2 + ), + (_2) => { + return new Ok(new Story2(config.title, base, component2, scene2)); + } + ); + } + ); +} + +// build/dev/javascript/lustre_fable/lustre/fable/chapter.mjs +var FILEPATH2 = "src/lustre/fable/chapter.gleam"; +var Chapter2 = class extends CustomType { + constructor(title3, route, stories) { + super(); + this.title = title3; + this.route = route; + this.stories = stories; + } +}; +function init2(title3, stories, stylesheets, external_stylesheets) { + let $ = from_string("[^a-zA-Z0-9]+"); + if (!($ instanceof Ok)) { + throw makeError( + "let_assert", + FILEPATH2, + "lustre/fable/chapter", + 29, + "init", + "Pattern match failed, no pattern matched the value.", + { value: $, start: 714, end: 769, pattern_start: 725, pattern_end: 731 } + ); + } + let re = $[0]; + let _block; + let _pipe = title3; + let _pipe$1 = ((_capture) => { + return replace3(re, _capture, "-"); + })( + _pipe + ); + _block = lowercase(_pipe$1); + let route = _block; + let _pipe$2 = stories; + let _pipe$3 = try_map( + _pipe$2, + (_capture) => { + return register(_capture, stylesheets, external_stylesheets); + } + ); + return map3( + _pipe$3, + (_capture) => { + return new Chapter2(title3, route, _capture); + } + ); +} +function view2(chapter2) { + return div( + toList([ + class$("inline-grid w-full justify-center gap-4 px-2 py-4") + ]), + map( + chapter2.stories, + (story3) => { + let route = new Story(chapter2.route, story3.route); + return fragment2( + toList([ + section( + toList([class$("w-full min-w-max")]), + toList([ + h2( + toList([class$("text-lg font-semibold underline")]), + toList([ + a( + toList([href2(route)]), + toList([text3(story3.title)]) + ) + ]) + ), + div( + toList([class$("relative")]), + toList([ + lock( + toList([ + class$( + "absolute -top-2 right-2 size-3 bg-white" + ) + ]) + ), + element2( + story3.scene, + toList([ + class$("block"), + class$( + "rounded p-4 border-dashed border border-blue-500/0" + ), + class$( + "transition hover:border-blue-500/100" + ) + ]), + toList([]) + ) + ]) + ) + ]) + ), + hr(toList([class$("w-full h-px bg-stone-200")])) + ]) + ); + } + ) + ); +} + +// build/dev/javascript/lustre_fable/lustre/fable/document.ffi.mjs +function focus(selector) { + document.querySelector(selector)?.focus(); +} + +// build/dev/javascript/lustre_fable/lustre/fable/document.mjs +function focus2(selector) { + return after_paint((_, _1) => { + return focus(selector); + }); +} + +// build/dev/javascript/lustre_fable/lustre/fable/window.ffi.mjs +function match_media(query) { + return window.matchMedia(query).matches; +} +function watch_media(query, callback) { + const mediaQueryList = window.matchMedia(query); + const listener = (event4) => callback(event4.matches); + mediaQueryList.addEventListener("change", listener); +} +function add_event_listener(name6, callback) { + window.addEventListener(name6, callback); +} + +// build/dev/javascript/lustre_fable/lustre/fable/book.mjs +var Book = class extends CustomType { + constructor(title3, stylesheets, external_stylesheets, chapters) { + super(); + this.title = title3; + this.stylesheets = stylesheets; + this.external_stylesheets = external_stylesheets; + this.chapters = chapters; + } +}; +var Model2 = class extends CustomType { + constructor(layout, route, title3, filter4, chapters, filtered) { + super(); + this.layout = layout; + this.route = route; + this.title = title3; + this.filter = filter4; + this.chapters = chapters; + this.filtered = filtered; + } +}; +var Args = class extends CustomType { + constructor(is_mobile, title3, chapters) { + super(); + this.is_mobile = is_mobile; + this.title = title3; + this.chapters = chapters; + } +}; +var BrowserChangedDimensions = class extends CustomType { + constructor(is_mobile) { + super(); + this.is_mobile = is_mobile; + } +}; +var UserFocusedSearch = class extends CustomType { +}; +var UserNavigatedTo = class extends CustomType { + constructor(route) { + super(); + this.route = route; + } +}; +var UserToggledSidebar = class extends CustomType { +}; +var UserTypedSearch = class extends CustomType { + constructor(filter4) { + super(); + this.filter = filter4; + } +}; +function init3(args) { + let _block; + let _pipe = do_initial_uri(); + let _pipe$1 = map3(_pipe, parse3); + _block = unwrap2(_pipe$1, new Index2()); + let route = _block; + let model = new Model2( + new Layout(args.is_mobile, !args.is_mobile), + route, + args.title, + "", + args.chapters, + args.chapters + ); + let effect = init( + (uri) => { + let _pipe$2 = uri; + let _pipe$3 = parse3(_pipe$2); + return new UserNavigatedTo(_pipe$3); + } + ); + return [model, effect]; +} +function update2(model, msg) { + let $ = echo(msg, "src/lustre/fable/book.gleam", 139); + if ($ instanceof BrowserChangedDimensions) { + let is_mobile = $.is_mobile; + let is_sidebar_open = !is_mobile; + let layout = new Layout(is_mobile, is_sidebar_open); + let _block; + let _record = model; + _block = new Model2( + layout, + _record.route, + _record.title, + _record.filter, + _record.chapters, + model.chapters + ); + let model$1 = _block; + return [model$1, none()]; + } else if ($ instanceof UserFocusedSearch) { + let effect = focus2("input[type='search']"); + return [model, effect]; + } else if ($ instanceof UserNavigatedTo) { + let route = $.route; + let _block; + let _record = model; + _block = new Model2( + _record.layout, + route, + _record.title, + "", + _record.chapters, + model.chapters + ); + let model$1 = _block; + return [model$1, none()]; + } else if ($ instanceof UserToggledSidebar) { + let is_sidebar_open = !model.layout.is_sidebar_open; + let _block; + let _record = model.layout; + _block = new Layout(_record.is_mobile, is_sidebar_open); + let layout = _block; + let _block$1; + let _record$1 = model; + _block$1 = new Model2( + layout, + _record$1.route, + _record$1.title, + _record$1.filter, + _record$1.chapters, + _record$1.filtered + ); + let model$1 = _block$1; + return [model$1, none()]; + } else { + let $1 = $.filter; + if ($1 === "") { + let _block; + let _record = model; + _block = new Model2( + _record.layout, + _record.route, + _record.title, + "", + _record.chapters, + model.chapters + ); + let model$1 = _block; + return [model$1, none()]; + } else { + let filter4 = $1; + let filtered = filter_map( + model.chapters, + (chapter2) => { + let stories = filter( + chapter2.stories, + (story3) => { + return contains_string( + lowercase(story3.title), + lowercase(filter4) + ); + } + ); + if (stories instanceof Empty) { + return new Error(void 0); + } else { + return new Ok(new Chapter2(chapter2.title, chapter2.route, stories)); + } + } + ); + let _block; + let _record = model; + _block = new Model2( + _record.layout, + _record.route, + _record.title, + filter4, + _record.chapters, + filtered + ); + let model$1 = _block; + return [model$1, none()]; + } + } +} +function split_on_filter(title3, filter4) { + let $ = split_once( + lowercase(title3), + lowercase(filter4) + ); + if ($ instanceof Ok) { + let after = $[0][1]; + let before = drop_end( + title3, + string_length(filter4) + string_length(after) + ); + let filter$1 = slice( + title3, + string_length(before), + string_length(filter4) + ); + let after$1 = slice( + title3, + string_length(before) + string_length(filter$1), + string_length(title3) + ); + return [before, filter$1, after$1]; + } else { + return [title3, "", ""]; + } +} +function view_nav_story(chapter2, story3, filter4, current2) { + let route = new Story(chapter2.route, story3.route); + let classes2 = classes( + toList([ + ["hover:underline", true], + ["text-blue-500", isEqual(route, current2)] + ]) + ); + return a( + toList([href2(route), classes2]), + (() => { + let $ = split_on_filter(story3.title, filter4); + let $1 = $[2]; + if ($1 === "") { + let $2 = $[1]; + if ($2 === "") { + return toList([text3(story3.title)]); + } else { + let before = $[0]; + let filter$1 = $2; + let after = $1; + return toList([ + text3(before), + span( + toList([class$("underline text-blue-500")]), + toList([text3(filter$1)]) + ), + text3(after) + ]); + } + } else { + let before = $[0]; + let filter$1 = $[1]; + let after = $1; + return toList([ + text3(before), + span( + toList([class$("underline text-blue-500")]), + toList([text3(filter$1)]) + ), + text3(after) + ]); + } + })() + ); +} +function view_nav_chapter(chapter2, filter4, current2) { + let route = new Chapter(chapter2.route); + let classes2 = classes( + toList([ + ["font-semibold hover:underline", true], + ["text-blue-500", isEqual(route, current2)] + ]) + ); + return div( + toList([class$("space-y-2")]), + toList([ + a( + toList([href2(route), classes2]), + toList([text3(chapter2.title)]) + ), + ul( + toList([]), + map( + chapter2.stories, + (story3) => { + return li( + toList([class$("pl-2")]), + toList([view_nav_story(chapter2, story3, filter4, current2)]) + ); + } + ) + ) + ]) + ); +} +function view_chapters(chapters, filter4, current2) { + return nav( + toList([class$("space-y-4")]), + map( + chapters, + (chapter2) => { + return view_nav_chapter(chapter2, filter4, current2); + } + ) + ); +} +function view3(model) { + return shell( + model.layout, + new UserToggledSidebar(), + toList([]), + toList([ + h1( + toList([class$("flex items-center min-h-12 bg-blue-50")]), + toList([ + span( + toList([class$("text-lg text-blue-500 font-semibold")]), + toList([text3(model.title)]) + ) + ]) + ), + div( + toList([]), + toList([ + div( + toList([ + class$( + "flex items-center gap-2 border rounded px-3 py-2" + ), + class$("focus-within:border-blue-500") + ]), + toList([ + span( + toList([]), + toList([search(toList([class$("size-4")]))]) + ), + input( + toList([ + class$("flex-1 w-full focus:outline-none"), + value(model.filter), + type_("search"), + placeholder("Press / to search..."), + on_input( + (var0) => { + return new UserTypedSearch(var0); + } + ) + ]) + ) + ]) + ) + ]) + ), + view_chapters(model.filtered, model.filter, model.route) + ]), + (() => { + let $ = model.route; + if ($ instanceof Index2) { + return toList([]); + } else if ($ instanceof Chapter) { + let chapter2 = $.chapter; + let $1 = find2( + model.chapters, + (c) => { + return c.route === chapter2; + } + ); + if ($1 instanceof Ok) { + let chapter$1 = $1[0]; + return toList([view2(chapter$1)]); + } else { + return toList([]); + } + } else if ($ instanceof Story) { + let story3 = $.story; + return toList([ + element2(story3 + "-story", toList([]), toList([])) + ]); + } else { + return toList([]); + } + })() + ); +} +function start4(book2) { + let app = application(init3, update2, view3); + let chapters = filter_map( + book2.chapters, + (chapter2) => { + return init2( + chapter2[0], + chapter2[1], + book2.stylesheets, + book2.external_stylesheets + ); + } + ); + let args = new Args( + match_media("(width < 64rem)"), + book2.title, + chapters + ); + return try$( + start3(app, "#app", args), + (runtime) => { + watch_media( + "(width < 64rem)", + (is_mobile) => { + let _pipe = dispatch(new BrowserChangedDimensions(is_mobile)); + return send(runtime, _pipe); + } + ); + add_event_listener( + "keydown", + (event4) => { + let decoder2 = field( + "key", + string2, + (key) => { + if (key === "/") { + return success(new UserFocusedSearch()); + } else { + return failure(new UserFocusedSearch(), "/"); + } + } + ); + let _pipe = run(event4, decoder2); + let _pipe$1 = map3(_pipe, dispatch); + let _pipe$2 = map3( + _pipe$1, + (_capture) => { + return send(runtime, _capture); + } + ); + return unwrap2(_pipe$2, void 0); + } + ); + return new Ok(void 0); + } + ); +} +function echo(value3, file, line) { + const grey = "\x1B[90m"; + const reset_color = "\x1B[39m"; + const file_line = `${file}:${line}`; + const string_value = echo$inspect(value3); + if (globalThis.process?.stderr?.write) { + const string5 = `${grey}${file_line}${reset_color} +${string_value} +`; + process.stderr.write(string5); + } else if (globalThis.Deno) { + const string5 = `${grey}${file_line}${reset_color} +${string_value} +`; + globalThis.Deno.stderr.writeSync(new TextEncoder().encode(string5)); + } else { + const string5 = `${file_line} +${string_value}`; + globalThis.console.log(string5); + } + return value3; +} +function echo$inspectString(str) { + let new_str = '"'; + for (let i = 0; i < str.length; i++) { + let char = str[i]; + if (char == "\n") new_str += "\\n"; + else if (char == "\r") new_str += "\\r"; + else if (char == " ") new_str += "\\t"; + else if (char == "\f") new_str += "\\f"; + else if (char == "\\") new_str += "\\\\"; + else if (char == '"') new_str += '\\"'; + else if (char < " " || char > "~" && char < "\xA0") { + new_str += "\\u{" + char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0") + "}"; + } else { + new_str += char; + } + } + new_str += '"'; + return new_str; +} +function echo$inspectDict(map6) { + let body = "dict.from_list(["; + let first3 = true; + let key_value_pairs = []; + map6.forEach((value3, key) => { + key_value_pairs.push([key, value3]); + }); + key_value_pairs.sort(); + key_value_pairs.forEach(([key, value3]) => { + if (!first3) body = body + ", "; + body = body + "#(" + echo$inspect(key) + ", " + echo$inspect(value3) + ")"; + first3 = false; + }); + return body + "])"; +} +function echo$inspectCustomType(record) { + const props = globalThis.Object.keys(record).map((label2) => { + const value3 = echo$inspect(record[label2]); + return isNaN(parseInt(label2)) ? `${label2}: ${value3}` : value3; + }).join(", "); + return props ? `${record.constructor.name}(${props})` : record.constructor.name; +} +function echo$inspectObject(v) { + const name6 = Object.getPrototypeOf(v)?.constructor?.name || "Object"; + const props = []; + for (const k of Object.keys(v)) { + props.push(`${echo$inspect(k)}: ${echo$inspect(v[k])}`); + } + const body = props.length ? " " + props.join(", ") + " " : ""; + const head = name6 === "Object" ? "" : name6 + " "; + return `//js(${head}{${body}})`; +} +function echo$inspect(v) { + const t = typeof v; + if (v === true) return "True"; + if (v === false) return "False"; + if (v === null) return "//js(null)"; + if (v === void 0) return "Nil"; + if (t === "string") return echo$inspectString(v); + if (t === "bigint" || t === "number") return v.toString(); + if (globalThis.Array.isArray(v)) + return `#(${v.map(echo$inspect).join(", ")})`; + if (v instanceof List) + return `[${v.toArray().map(echo$inspect).join(", ")}]`; + if (v instanceof UtfCodepoint) + return `//utfcodepoint(${String.fromCodePoint(v.value)})`; + if (v instanceof BitArray) return echo$inspectBitArray(v); + if (v instanceof CustomType) return echo$inspectCustomType(v); + if (echo$isDict(v)) return echo$inspectDict(v); + if (v instanceof Set) + return `//js(Set(${[...v].map(echo$inspect).join(", ")}))`; + if (v instanceof RegExp) return `//js(${v})`; + if (v instanceof Date) return `//js(Date("${v.toISOString()}"))`; + if (v instanceof Function) { + const args = []; + for (const i of Array(v.length).keys()) + args.push(String.fromCharCode(i + 97)); + return `//fn(${args.join(", ")}) { ... }`; + } + return echo$inspectObject(v); +} +function echo$inspectBitArray(bitArray) { + let endOfAlignedBytes = bitArray.bitOffset + 8 * Math.trunc(bitArray.bitSize / 8); + let alignedBytes = bitArraySlice( + bitArray, + bitArray.bitOffset, + endOfAlignedBytes + ); + let remainingUnalignedBits = bitArray.bitSize % 8; + if (remainingUnalignedBits > 0) { + let remainingBits = bitArraySliceToInt( + bitArray, + endOfAlignedBytes, + bitArray.bitSize, + false, + false + ); + let alignedBytesArray = Array.from(alignedBytes.rawBuffer); + let suffix = `${remainingBits}:size(${remainingUnalignedBits})`; + if (alignedBytesArray.length === 0) { + return `<<${suffix}>>`; + } else { + return `<<${Array.from(alignedBytes.rawBuffer).join(", ")}, ${suffix}>>`; + } + } else { + return `<<${Array.from(alignedBytes.rawBuffer).join(", ")}>>`; + } +} +function echo$isDict(value3) { + try { + return value3 instanceof Dict; + } catch { + return false; + } +} + +// build/dev/javascript/lustre_fable/lustre/dev/fable.mjs +var BookOption = class extends CustomType { + constructor(configure) { + super(); + this.configure = configure; + } +}; +var Control = class extends CustomType { + constructor(get5, set3) { + super(); + this.get = get5; + this.set = set3; + } +}; +function book(title3, options) { + let init8 = new Book(title3, toList([]), toList([]), toList([])); + return fold( + options, + init8, + (book2, option3) => { + return option3.configure(book2); + } + ); +} +function chapter(title3, stories) { + return new BookOption( + (book2) => { + let _record = book2; + return new Book( + _record.title, + _record.stylesheets, + _record.external_stylesheets, + prepend([title3, stories], book2.chapters) + ); + } + ); +} +function external_stylesheet(href3) { + return new BookOption( + (book2) => { + let _record = book2; + return new Book( + _record.title, + _record.stylesheets, + prepend(href3, book2.external_stylesheets), + _record.chapters + ); + } + ); +} +function start5(book2) { + return start4(book2); +} +function story2(title3, builder) { + let _record = builder().run(0); + return new StoryConfig( + title3, + _record.inputs, + _record.sequences, + _record.options, + _record.view + ); +} +function scene(view8) { + return new StoryBuilder( + (_) => { + return new StoryConfig( + "", + toList([]), + toList([]), + toList([adopt_styles(false)]), + view8 + ); + } + ); +} +function get3(controls, control2) { + return control2.get(controls); +} +function set(control2, value3) { + return control2.set(value3); +} +function control(default$2, encode2, decoder2, view8, next2) { + return new StoryBuilder( + (key) => { + let state = (controls) => { + let _pipe = controls.lookup; + let _pipe$1 = map_get(_pipe, key); + let _pipe$2 = unwrap2(_pipe$1, nil()); + let _pipe$3 = run(_pipe$2, decoder2); + return unwrap2(_pipe$3, default$2); + }; + let set_state = (value3) => { + let _pipe = value3; + let _pipe$1 = encode2(_pipe); + return ((_capture) => { + return new UserEditedValue(key, _capture); + })(_pipe$1); + }; + let input$1 = (controls) => { + return view8(state(controls), set_state); + }; + let option3 = on_property_change( + to_string(key), + (() => { + let _pipe = dynamic; + return map2( + _pipe, + (_capture) => { + return new ComponentUpdatedValue(key, _capture); + } + ); + })() + ); + let $ = next2(new Control(state, set_state)).run(key + 1); + let title3 = $.title; + let inputs = $.inputs; + let sequences2 = $.sequences; + let options = $.options; + let view$1 = $.view; + return new StoryConfig( + title3, + prepend(input$1, inputs), + sequences2, + prepend(option3, options), + view$1 + ); + } + ); +} +function input2(label2, value3, next2) { + return ((_capture) => { + return control(value3, string3, string2, _capture, next2); + })( + (value4, set_value) => { + return label( + toList([]), + toList([ + p( + toList([class$("text-sm")]), + toList([text3(label2)]) + ), + input( + toList([ + class$("border rounded px-2 py-1"), + class$("focus:outline-none focus:border-blue-500"), + value(value4), + on_input(set_value) + ]) + ) + ]) + ); + } + ); +} + +// build/dev/javascript/lustre_ui/lustre/ffi/dom.ffi.mjs +var assigned_elements = (slot2) => { + if (!(slot2 instanceof HTMLSlotElement)) return new Error(void 0); + const elements = slot2.assignedElements(); + return new Ok(List.fromArray(elements)); +}; +var bounding_client_rect = (element15) => { + if (!(element15 instanceof HTMLElement)) return new Error(void 0); + const rect2 = element15.getBoundingClientRect(); + return new Ok( + new BoundingClientRect( + rect2.top, + rect2.right, + rect2.bottom, + rect2.left, + rect2.width, + rect2.height + ) + ); +}; +var attribute3 = (element15, name6) => { + if (!(element15 instanceof HTMLElement)) return new Error(void 0); + if (typeof name6 !== "string") return new Error(void 0); + const value3 = element15.getAttribute(name6); + if (value3 === null) { + return new Error(void 0); + } else { + return new Ok(value3); + } +}; +var prevent_default = (event4) => { + if (!(event4 instanceof Event)) return; + event4.preventDefault(); +}; +var find_element = (selector, root3 = document) => { + if (typeof selector !== "string") return new Error(void 0); + if (!(root3 instanceof Document || root3 instanceof Element)) + return new Error(void 0); + const element15 = root3.querySelector(selector); + if (element15 === null) { + return new Error(void 0); + } else { + return new Ok(element15); + } +}; +var focus3 = (element15) => { + if (!(element15 instanceof HTMLElement)) return; + element15.focus(); +}; + +// build/dev/javascript/lustre_ui/lustre/ffi/dom.mjs +var BoundingClientRect = class extends CustomType { + constructor(top, right, bottom, left, width, height) { + super(); + this.top = top; + this.right = right; + this.bottom = bottom; + this.left = left; + this.width = width; + this.height = height; + } +}; +function assigned_elements2(decoder2, lenient) { + let lenient_decoder = one_of( + map2(decoder2, (var0) => { + return new Ok(var0); + }), + toList([success(new Error(void 0))]) + ); + return new_primitive_decoder( + "HTMLSlotElement.assignedElements()", + (slot2) => { + let $ = assigned_elements(slot2); + if ($ instanceof Ok) { + if (lenient) { + let elements = $[0]; + let _pipe = elements; + let _pipe$1 = try_map( + _pipe, + (_capture) => { + return run(_capture, lenient_decoder); + } + ); + let _pipe$2 = map3( + _pipe$1, + (_capture) => { + return filter_map(_capture, identity2); + } + ); + return replace_error(_pipe$2, toList([])); + } else { + let elements = $[0]; + let _pipe = elements; + let _pipe$1 = try_map( + _pipe, + (_capture) => { + return run(_capture, decoder2); + } + ); + return replace_error(_pipe$1, toList([])); + } + } else { + return new Error(toList([])); + } + } + ); +} +function bounding_client_rect2() { + return new_primitive_decoder( + "Element.getBoundingClientRect()", + (element15) => { + let $ = bounding_client_rect(element15); + if ($ instanceof Ok) { + let rect2 = $[0]; + return new Ok(rect2); + } else { + return new Error(new BoundingClientRect(0, 0, 0, 0, 0, 0)); + } + } + ); +} +function attribute4(name6) { + return new_primitive_decoder( + "Element.getAttribute()", + (element15) => { + let $ = attribute3(element15, name6); + if ($ instanceof Ok) { + let value3 = $[0]; + return new Ok(value3); + } else { + return new Error(""); + } + } + ); +} +function prevent_default2(event4) { + return from((_) => { + return prevent_default(event4); + }); +} +function child(selector, zero, decoder2) { + return new_primitive_decoder( + "Element.querySelector()", + (element15) => { + let $ = find_element(selector, element15); + if ($ instanceof Ok) { + let child$1 = $[0]; + let _pipe = run(child$1, decoder2); + return replace_error(_pipe, zero); + } else { + return new Error(zero); + } + } + ); +} +function focus4(selector) { + return before_paint( + (_, root3) => { + let $ = find_element(selector, root3); + if ($ instanceof Ok) { + let element15 = $[0]; + return focus3(element15); + } else { + return void 0; + } + } + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/data/bidict.mjs +function new$8() { + return [new_map(), new_map()]; +} +function has(bidict, key) { + return has_key(bidict[0], key); +} +function get4(bidict, key) { + return map_get(bidict[0], key); +} +function get_inverse(bidict, key) { + return map_get(bidict[1], key); +} +function min_inverse(bidict, compare5) { + let _pipe = map_to_list(bidict[1]); + let _pipe$1 = sort(_pipe, (a2, b) => { + return compare5(a2[0], b[0]); + }); + let _pipe$2 = first(_pipe$1); + return map3(_pipe$2, second); +} +function max_inverse(bidict, compare5) { + let _pipe = map_to_list(bidict[1]); + let _pipe$1 = sort(_pipe, (a2, b) => { + return compare5(b[0], a2[0]); + }); + let _pipe$2 = first(_pipe$1); + return map3(_pipe$2, second); +} +function next(bidict, key, increment) { + let _pipe = get4(bidict, key); + let _pipe$1 = map3(_pipe, increment); + return then$2( + _pipe$1, + (_capture) => { + return get_inverse(bidict, _capture); + } + ); +} +function set2(bidict, key, value3) { + return [ + insert(bidict[0], key, value3), + insert(bidict[1], value3, key) + ]; +} +function from_list3(entries) { + return fold( + entries, + new$8(), + (bidict, entry) => { + return set2(bidict, entry[0], entry[1]); + } + ); +} +function indexed(values3) { + return index_fold( + values3, + new$8(), + (bidict, value3, index5) => { + return set2(bidict, value3, index5); + } + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/primitives/collapse.mjs +var Model3 = class extends CustomType { + constructor(height, expanded2) { + super(); + this.height = height; + this.expanded = expanded2; + } +}; +var ParentChangedContent = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var ParentSetExpanded = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var UserPressedTrigger = class extends CustomType { + constructor($0, event4) { + super(); + this[0] = $0; + this.event = event4; + } +}; +function expanded(is_expanded) { + return attribute2( + "aria-expanded", + (() => { + let _pipe = to_string2(is_expanded); + return lowercase(_pipe); + })() + ); +} +function on_change2(handler) { + return on( + "change", + subfield( + toList(["detail", "expanded"]), + bool, + (expanded2) => { + return success(handler(expanded2)); + } + ) + ); +} +function init4(_) { + let model = new Model3(0, false); + let effect = none(); + return [model, effect]; +} +function update3(model, msg) { + if (msg instanceof ParentChangedContent) { + let height = msg[0]; + return [ + (() => { + let _record = model; + return new Model3(height, _record.expanded); + })(), + none() + ]; + } else if (msg instanceof ParentSetExpanded) { + let expanded$1 = msg[0]; + return [ + (() => { + let _record = model; + return new Model3(_record.height, expanded$1); + })(), + none() + ]; + } else { + let height = msg[0]; + let event4 = msg.event; + let _block; + let _record = model; + _block = new Model3(height, _record.expanded); + let model$1 = _block; + let emit_change = emit( + "change", + object2(toList([["expanded", bool2(!model$1.expanded)]])) + ); + let _block$1; + let $ = model$1.expanded; + if ($) { + _block$1 = emit("collapse", null$()); + } else { + _block$1 = emit("expand", null$()); + } + let emit_expand_collapse = _block$1; + let effect = batch( + toList([emit_change, emit_expand_collapse, prevent_default2(event4)]) + ); + return [model$1, effect]; + } +} +function handle_click() { + return subfield( + toList(["currentTarget", "nextElementSibling", "firstElementChild"]), + assigned_elements2( + (() => { + let _pipe = bounding_client_rect2(); + return map2(_pipe, (rect2) => { + return rect2.height; + }); + })(), + false + ), + (heights) => { + let height = sum(heights); + return success(new UserPressedTrigger(height, nil())); + } + ); +} +function handle_keydown() { + return then$( + dynamic, + (event4) => { + return field( + "key", + string2, + (key) => { + if (key === "Enter") { + return subfield( + toList([ + "currentTarget", + "nextElementSibling", + "firstElementChild" + ]), + assigned_elements2( + (() => { + let _pipe = bounding_client_rect2(); + return map2(_pipe, (rect2) => { + return rect2.height; + }); + })(), + false + ), + (heights) => { + let height = sum(heights); + return success(new UserPressedTrigger(height, event4)); + } + ); + } else if (key === " ") { + return subfield( + toList([ + "currentTarget", + "nextElementSibling", + "firstElementChild" + ]), + assigned_elements2( + (() => { + let _pipe = bounding_client_rect2(); + return map2(_pipe, (rect2) => { + return rect2.height; + }); + })(), + false + ), + (heights) => { + let height = sum(heights); + return success(new UserPressedTrigger(height, event4)); + } + ); + } else { + return failure( + new UserPressedTrigger(0, nil()), + "" + ); + } + } + ); + } + ); +} +function view_trigger() { + return slot( + toList([ + attribute2("part", "collapse-trigger"), + name("trigger"), + on("click", handle_click()), + on("keydown", handle_keydown()) + ]), + toList([]) + ); +} +function handle_slot_change() { + return subfield( + toList(["currentTarget", "nextElementSibling", "firstElementChild"]), + assigned_elements2( + (() => { + let _pipe = bounding_client_rect2(); + return map2(_pipe, (rect2) => { + return rect2.height; + }); + })(), + false + ), + (heights) => { + let height = sum(heights); + return success(new ParentChangedContent(height)); + } + ); +} +function view_content2(height) { + return div( + toList([ + attribute2("part", "collapse-content"), + styles( + toList([["transition-duration", "inherit"], ["height", height]]) + ) + ]), + toList([ + slot( + toList([on("slotchange", handle_slot_change())]), + toList([]) + ) + ]) + ); +} +var name2 = "lustre-ui-collapse"; +function element4(attributes, trigger, content3) { + return element2( + name2, + attributes, + toList([ + div(toList([attribute2("slot", "trigger")]), toList([trigger])), + content3 + ]) + ); +} +function view4(model) { + let _block; + let $ = model.expanded; + if ($) { + _block = float_to_string(model.height) + "px"; + } else { + _block = "0px"; + } + let height = _block; + return fragment2(toList([view_trigger(), view_content2(height)])); +} +function register2() { + let app = component( + init4, + update3, + view4, + toList([ + adopt_styles(true), + on_attribute_change( + "aria-expanded", + (value3) => { + if (value3 === "true") { + return new Ok(new ParentSetExpanded(true)); + } else if (value3 === "false") { + return new Ok(new ParentSetExpanded(false)); + } else if (value3 === "") { + return new Ok(new ParentSetExpanded(false)); + } else { + return new Error(void 0); + } + } + ) + ]) + ); + return make_component(app, name2); +} + +// build/dev/javascript/lustre_ui/lustre/ui/primitives/icon.mjs +function icon(attrs, path2) { + return svg( + prepend( + attribute2("viewBox", "0 0 15 15"), + prepend( + attribute2("fill", "none"), + prepend(class$("lustre-ui-icon"), attrs) + ) + ), + toList([ + path( + toList([ + attribute2("d", path2), + attribute2("fill", "currentColor"), + attribute2("fill-rule", "evenodd"), + attribute2("clip-rule", "evenodd") + ]) + ) + ]) + ); +} +function chevron_down(attrs) { + return icon( + attrs, + "M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" + ); +} +function chevron_right(attrs) { + return icon( + attrs, + "M6.1584 3.13508C6.35985 2.94621 6.67627 2.95642 6.86514 3.15788L10.6151 7.15788C10.7954 7.3502 10.7954 7.64949 10.6151 7.84182L6.86514 11.8418C6.67627 12.0433 6.35985 12.0535 6.1584 11.8646C5.95694 11.6757 5.94673 11.3593 6.1356 11.1579L9.565 7.49985L6.1356 3.84182C5.94673 3.64036 5.95694 3.32394 6.1584 3.13508Z" + ); +} +function dots_horizontal(attrs) { + return icon( + attrs, + "M3.625 7.5C3.625 8.12132 3.12132 8.625 2.5 8.625C1.87868 8.625 1.375 8.12132 1.375 7.5C1.375 6.87868 1.87868 6.375 2.5 6.375C3.12132 6.375 3.625 6.87868 3.625 7.5ZM8.625 7.5C8.625 8.12132 8.12132 8.625 7.5 8.625C6.87868 8.625 6.375 8.12132 6.375 7.5C6.375 6.87868 6.87868 6.375 7.5 6.375C8.12132 6.375 8.625 6.87868 8.625 7.5ZM12.5 8.625C13.1213 8.625 13.625 8.12132 13.625 7.5C13.625 6.87868 13.1213 6.375 12.5 6.375C11.8787 6.375 11.375 6.87868 11.375 7.5C11.375 8.12132 11.8787 8.625 12.5 8.625Z" + ); +} +function check2(attrs) { + return icon( + attrs, + "M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z" + ); +} +function exclamation_triangle(attrs) { + return icon( + attrs, + "M8.4449 0.608765C8.0183 -0.107015 6.9817 -0.107015 6.55509 0.608766L0.161178 11.3368C-0.275824 12.07 0.252503 13 1.10608 13H13.8939C14.7475 13 15.2758 12.07 14.8388 11.3368L8.4449 0.608765ZM7.4141 1.12073C7.45288 1.05566 7.54712 1.05566 7.5859 1.12073L13.9798 11.8488C14.0196 11.9154 13.9715 12 13.8939 12H1.10608C1.02849 12 0.980454 11.9154 1.02018 11.8488L7.4141 1.12073ZM6.8269 4.48611C6.81221 4.10423 7.11783 3.78663 7.5 3.78663C7.88217 3.78663 8.18778 4.10423 8.1731 4.48612L8.01921 8.48701C8.00848 8.766 7.7792 8.98664 7.5 8.98664C7.2208 8.98664 6.99151 8.766 6.98078 8.48701L6.8269 4.48611ZM8.24989 10.476C8.24989 10.8902 7.9141 11.226 7.49989 11.226C7.08567 11.226 6.74989 10.8902 6.74989 10.476C6.74989 10.0618 7.08567 9.72599 7.49989 9.72599C7.9141 9.72599 8.24989 10.0618 8.24989 10.476Z" + ); +} +function slash(attrs) { + return icon(attrs, "M4.10876 14L9.46582 1H10.8178L5.46074 14H4.10876Z"); +} +function magnifying_glass(attrs) { + return icon( + attrs, + "M10 6.5C10 8.433 8.433 10 6.5 10C4.567 10 3 8.433 3 6.5C3 4.567 4.567 3 6.5 3C8.433 3 10 4.567 10 6.5ZM9.30884 10.0159C8.53901 10.6318 7.56251 11 6.5 11C4.01472 11 2 8.98528 2 6.5C2 4.01472 4.01472 2 6.5 2C8.98528 2 11 4.01472 11 6.5C11 7.56251 10.6318 8.53901 10.0159 9.30884L12.8536 12.1464C13.0488 12.3417 13.0488 12.6583 12.8536 12.8536C12.6583 13.0488 12.3417 13.0488 12.1464 12.8536L9.30884 10.0159Z" + ); +} +function bookmark(attrs) { + return icon( + attrs, + "M3 2.5C3 2.22386 3.22386 2 3.5 2H11.5C11.7761 2 12 2.22386 12 2.5V13.5C12 13.6818 11.9014 13.8492 11.7424 13.9373C11.5834 14.0254 11.3891 14.0203 11.235 13.924L7.5 11.5896L3.765 13.924C3.61087 14.0203 3.41659 14.0254 3.25762 13.9373C3.09864 13.8492 3 13.6818 3 13.5V2.5ZM4 3V12.5979L6.97 10.7416C7.29427 10.539 7.70573 10.539 8.03 10.7416L11 12.5979V3H4Z" + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/accordion.mjs +var Item = class extends CustomType { + constructor(value3, label2, content3) { + super(); + this.value = value3; + this.label = label2; + this.content = content3; + } +}; +var Model4 = class extends CustomType { + constructor(options, expanded2, mode) { + super(); + this.options = options; + this.expanded = expanded2; + this.mode = mode; + } +}; +var AtMostOne = class extends CustomType { +}; +var ExactlyOne = class extends CustomType { +}; +var Multi = class extends CustomType { +}; +var Options3 = class extends CustomType { + constructor(all10, lookup_label, lookup_index) { + super(); + this.all = all10; + this.lookup_label = lookup_label; + this.lookup_index = lookup_index; + } +}; +var ParentChangedChildren = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var ParentSetMode = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var UserPressedDown = class extends CustomType { + constructor($0, event4) { + super(); + this[0] = $0; + this.event = event4; + } +}; +var UserPressedEnd = class extends CustomType { +}; +var UserPressedHome = class extends CustomType { +}; +var UserPressedUp = class extends CustomType { + constructor($0, event4) { + super(); + this[0] = $0; + this.event = event4; + } +}; +var UserToggledItem = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +function item(value3, label2, content3) { + return new Item(value3, label2, content3); +} +function init5(_) { + let options = new Options3(toList([]), new$8(), new$8()); + let model = new Model4(options, new$(), new AtMostOne()); + let effect = none(); + return [model, effect]; +} +function focus_trigger(key) { + let selector = "[data-lustre-key=" + key + "] [part=accordion-trigger]"; + return focus4(selector); +} +function update4(model, msg) { + if (msg instanceof ParentChangedChildren) { + let all10 = msg[0]; + let lookup_label = from_list3(all10); + let lookup_index = indexed(map(all10, first2)); + let options = new Options3(all10, lookup_label, lookup_index); + let expanded2 = filter3( + model.expanded, + (_capture) => { + return has(lookup_label, _capture); + } + ); + let keys2 = map(all10, first2); + let _block; + let $ = model.mode; + let $1 = size(expanded2); + if ($ instanceof AtMostOne) { + if ($1 === 0) { + _block = expanded2; + } else if ($1 === 1) { + _block = expanded2; + } else { + let _pipe = find2( + keys2, + (_capture) => { + return contains(expanded2, _capture); + } + ); + let _pipe$1 = map3( + _pipe, + (key) => { + return from_list2(toList([key])); + } + ); + _block = unwrap2(_pipe$1, new$()); + } + } else if ($ instanceof ExactlyOne) { + if ($1 === 0) { + let _pipe = first(keys2); + let _pipe$1 = map3( + _pipe, + (key) => { + return from_list2(toList([key])); + } + ); + _block = unwrap2(_pipe$1, new$()); + } else if ($1 === 1) { + _block = expanded2; + } else { + let _pipe = find2( + keys2, + (_capture) => { + return contains(expanded2, _capture); + } + ); + let _pipe$1 = map3( + _pipe, + (key) => { + return from_list2(toList([key])); + } + ); + _block = unwrap2(_pipe$1, new$()); + } + } else { + _block = expanded2; + } + let expanded$1 = _block; + let _block$1; + let _record = model; + _block$1 = new Model4(options, expanded$1, _record.mode); + let model$1 = _block$1; + let effect = none(); + return [model$1, effect]; + } else if (msg instanceof ParentSetMode) { + let mode = msg[0]; + let keys2 = map(model.options.all, first2); + let _block; + let $ = size(model.expanded); + if (mode instanceof AtMostOne) { + if ($ === 0) { + _block = model.expanded; + } else if ($ === 1) { + _block = model.expanded; + } else { + let _pipe = find2( + keys2, + (_capture) => { + return contains(model.expanded, _capture); + } + ); + let _pipe$1 = map3( + _pipe, + (key) => { + return from_list2(toList([key])); + } + ); + _block = unwrap2(_pipe$1, new$()); + } + } else if (mode instanceof ExactlyOne) { + if ($ === 0) { + let _pipe = first(keys2); + let _pipe$1 = map3( + _pipe, + (key) => { + return from_list2(toList([key])); + } + ); + _block = unwrap2(_pipe$1, new$()); + } else if ($ === 1) { + _block = model.expanded; + } else { + let _pipe = find2( + keys2, + (_capture) => { + return contains(model.expanded, _capture); + } + ); + let _pipe$1 = map3( + _pipe, + (key) => { + return from_list2(toList([key])); + } + ); + _block = unwrap2(_pipe$1, new$()); + } + } else { + _block = model.expanded; + } + let expanded2 = _block; + let _block$1; + let _record = model; + _block$1 = new Model4(_record.options, expanded2, mode); + let model$1 = _block$1; + let effect = none(); + return [model$1, effect]; + } else if (msg instanceof UserPressedDown) { + let key = msg[0]; + let event4 = msg.event; + let effect = try$( + get4(model.options.lookup_index, key), + (index5) => { + return map3( + get_inverse(model.options.lookup_index, index5 + 1), + (next2) => { + return focus_trigger(next2); + } + ); + } + ); + return [ + model, + batch( + toList([ + (() => { + let _pipe = effect; + return unwrap2(_pipe, none()); + })(), + prevent_default2(event4) + ]) + ) + ]; + } else if (msg instanceof UserPressedEnd) { + let effect = map3( + max_inverse(model.options.lookup_index, compare), + (last) => { + return focus_trigger(last); + } + ); + return [ + model, + (() => { + let _pipe = effect; + return unwrap2(_pipe, none()); + })() + ]; + } else if (msg instanceof UserPressedHome) { + let effect = map3( + min_inverse(model.options.lookup_index, compare), + (first3) => { + return focus_trigger(first3); + } + ); + return [ + model, + (() => { + let _pipe = effect; + return unwrap2(_pipe, none()); + })() + ]; + } else if (msg instanceof UserPressedUp) { + let key = msg[0]; + let event4 = msg.event; + let effect = try$( + get4(model.options.lookup_index, key), + (index5) => { + return map3( + get_inverse(model.options.lookup_index, index5 - 1), + (prev) => { + return focus_trigger(prev); + } + ); + } + ); + return [ + model, + batch( + toList([ + (() => { + let _pipe = effect; + return unwrap2(_pipe, none()); + })(), + prevent_default2(event4) + ]) + ) + ]; + } else { + let value3 = msg[0]; + let _block; + let $ = contains(model.expanded, value3); + let $1 = model.mode; + if ($1 instanceof AtMostOne) { + if ($) { + _block = new$(); + } else { + _block = from_list2(toList([value3])); + } + } else if ($1 instanceof ExactlyOne) { + if ($) { + _block = model.expanded; + } else { + _block = from_list2(toList([value3])); + } + } else if ($) { + _block = delete$2(model.expanded, value3); + } else { + _block = insert2(model.expanded, value3); + } + let expanded2 = _block; + let _block$1; + let _record = model; + _block$1 = new Model4(_record.options, expanded2, _record.mode); + let model$1 = _block$1; + let _block$2; + let $2 = contains(expanded2, value3); + if ($2) { + _block$2 = emit("expand", string3(value3)); + } else { + _block$2 = emit("collapse", string3(value3)); + } + let effect = _block$2; + return [model$1, effect]; + } +} +function handle_slot_change2() { + return field( + "target", + assigned_elements2( + field( + "tagName", + string2, + (tag) => { + return then$( + attribute4("value"), + (value3) => { + return field( + "textContent", + string2, + (label2) => { + return success([tag, value3, label2]); + } + ); + } + ); + } + ), + true + ), + (options) => { + let _pipe = options; + let _pipe$1 = fold_right( + _pipe, + [toList([]), new$()], + (acc, option3) => { + let tag = option3[0]; + let value3 = option3[1]; + let label2 = option3[2]; + return guard( + tag !== "LUSTRE-UI-ACCORDION-ITEM", + acc, + () => { + return guard( + contains(acc[1], value3), + acc, + () => { + let seen = insert2(acc[1], value3); + let options$1 = prepend([value3, label2], acc[0]); + return [options$1, seen]; + } + ); + } + ); + } + ); + let _pipe$2 = first2(_pipe$1); + let _pipe$3 = new ParentChangedChildren(_pipe$2); + return success(_pipe$3); + } + ); +} +function handle_keydown2(id) { + return then$( + dynamic, + (event4) => { + return field( + "key", + string2, + (key) => { + if (key === "ArrowDown") { + return success(new UserPressedDown(id, event4)); + } else if (key === "ArrowUp") { + return success(new UserPressedUp(id, event4)); + } else if (key === "End") { + return success(new UserPressedEnd()); + } else if (key === "Home") { + return success(new UserPressedHome()); + } else { + return failure(new UserPressedHome(), ""); + } + } + ); + } + ); +} +var name3 = "lustre-ui-accordion"; +function element5(attributes, children) { + return element3( + name3, + attributes, + flat_map( + children, + (_use0) => { + let value3 = _use0.value; + let label2 = _use0.label; + let content3 = _use0.content; + return guard( + value3 === "", + toList([]), + () => { + let item$1 = element2( + "lustre-ui-accordion-item", + toList([value(value3)]), + toList([text3(label2)]) + ); + let content$1 = div( + toList([attribute2("slot", value3)]), + content3 + ); + return toList([[value3, item$1], [value3 + "-content", content$1]]); + } + ); + } + ) + ); +} +function view5(model) { + return fragment2( + toList([ + slot( + toList([ + style("display", "none"), + on("slotchange", handle_slot_change2()) + ]), + toList([]) + ), + fragment3( + map( + model.options.all, + (_use0) => { + let key = _use0[0]; + let label2 = _use0[1]; + let is_expanded = contains(model.expanded, key); + let item$1 = element4( + toList([ + expanded(is_expanded), + on_change2((_) => { + return new UserToggledItem(key); + }) + ]), + button( + toList([ + attribute2("part", "accordion-trigger"), + attribute2("tabindex", "0"), + on("keydown", handle_keydown2(key)) + ]), + toList([ + p( + toList([attribute2("part", "accordion-trigger-label")]), + toList([text3(label2)]) + ), + chevron_down( + toList([ + attribute2( + "part", + (() => { + if (is_expanded) { + return "accordion-trigger-icon expanded"; + } else { + return "accordion-trigger-icon"; + } + })() + ) + ]) + ) + ]) + ), + slot( + toList([ + attribute2("part", "accordion-content"), + name(key) + ]), + toList([]) + ) + ); + return [key, item$1]; + } + ) + ) + ]) + ); +} +function register3() { + let $ = register2(); + if ($ instanceof Ok) { + let app = component( + init5, + update4, + view5, + toList([ + on_attribute_change( + "mode", + (value3) => { + if (value3 === "at-most-one") { + return new Ok(new ParentSetMode(new AtMostOne())); + } else if (value3 === "exactly-one") { + return new Ok(new ParentSetMode(new ExactlyOne())); + } else if (value3 === "multi") { + return new Ok(new ParentSetMode(new Multi())); + } else { + return new Error(void 0); + } + } + ) + ]) + ); + return make_component(app, name3); + } else { + let $1 = $[0]; + if ($1 instanceof ComponentAlreadyRegistered) { + let app = component( + init5, + update4, + view5, + toList([ + on_attribute_change( + "mode", + (value3) => { + if (value3 === "at-most-one") { + return new Ok(new ParentSetMode(new AtMostOne())); + } else if (value3 === "exactly-one") { + return new Ok(new ParentSetMode(new ExactlyOne())); + } else if (value3 === "multi") { + return new Ok(new ParentSetMode(new Multi())); + } else { + return new Error(void 0); + } + } + ) + ]) + ); + return make_component(app, name3); + } else { + let error = $; + return error; + } + } +} + +// build/dev/javascript/gleam_community_colour/gleam_community/colour.mjs +var Rgba = class extends CustomType { + constructor(r, g, b, a2) { + super(); + this.r = r; + this.g = g; + this.b = b; + this.a = a2; + } +}; +function valid_colour_value(c) { + let $ = c > 1 || c < 0; + if ($) { + return new Error(void 0); + } else { + return new Ok(c); + } +} +function hue_to_rgb(hue, m1, m2) { + let _block; + if (hue < 0) { + _block = hue + 1; + } else { + if (hue > 1) { + _block = hue - 1; + } else { + _block = hue; + } + } + let h = _block; + let h_t_6 = h * 6; + let h_t_2 = h * 2; + let h_t_3 = h * 3; + if (h_t_6 < 1) { + return m1 + (m2 - m1) * h * 6; + } else { + if (h_t_2 < 1) { + return m2; + } else { + if (h_t_3 < 2) { + return m1 + (m2 - m1) * (divideFloat(2, 3) - h) * 6; + } else { + return m1; + } + } + } +} +function hsla_to_rgba(h, s, l, a2) { + let _block; + let $ = l <= 0.5; + if ($) { + _block = l * (s + 1); + } else { + _block = l + s - l * s; + } + let m2 = _block; + let m1 = l * 2 - m2; + let r = hue_to_rgb(h + divideFloat(1, 3), m1, m2); + let g = hue_to_rgb(h, m1, m2); + let b = hue_to_rgb(h - divideFloat(1, 3), m1, m2); + return [r, g, b, a2]; +} +function from_rgb255(red2, green2, blue2) { + return then$2( + (() => { + let _pipe = red2; + let _pipe$1 = identity(_pipe); + let _pipe$2 = divide(_pipe$1, 255); + return then$2(_pipe$2, valid_colour_value); + })(), + (r) => { + return then$2( + (() => { + let _pipe = green2; + let _pipe$1 = identity(_pipe); + let _pipe$2 = divide(_pipe$1, 255); + return then$2(_pipe$2, valid_colour_value); + })(), + (g) => { + return then$2( + (() => { + let _pipe = blue2; + let _pipe$1 = identity(_pipe); + let _pipe$2 = divide(_pipe$1, 255); + return then$2(_pipe$2, valid_colour_value); + })(), + (b) => { + return new Ok(new Rgba(r, g, b, 1)); + } + ); + } + ); + } + ); +} +function to_rgba(colour) { + if (colour instanceof Rgba) { + let r = colour.r; + let g = colour.g; + let b = colour.b; + let a2 = colour.a; + return [r, g, b, a2]; + } else { + let h = colour.h; + let s = colour.s; + let l = colour.l; + let a2 = colour.a; + return hsla_to_rgba(h, s, l, a2); + } +} + +// build/dev/javascript/lustre_ui/lustre/ui/colour.mjs +var FILEPATH3 = "src/lustre/ui/colour.gleam"; +var ColourPalette = class extends CustomType { + constructor(base, primary, secondary, success3, warning, danger3) { + super(); + this.base = base; + this.primary = primary; + this.secondary = secondary; + this.success = success3; + this.warning = warning; + this.danger = danger3; + } +}; +var ColourScale = class extends CustomType { + constructor(bg, bg_subtle, tint, tint_subtle, tint_strong, accent, accent_subtle, accent_strong, solid3, solid_subtle, solid_strong, solid_text, text4, text_subtle) { + super(); + this.bg = bg; + this.bg_subtle = bg_subtle; + this.tint = tint; + this.tint_subtle = tint_subtle; + this.tint_strong = tint_strong; + this.accent = accent; + this.accent_subtle = accent_subtle; + this.accent_strong = accent_strong; + this.solid = solid3; + this.solid_subtle = solid_subtle; + this.solid_strong = solid_strong; + this.solid_text = solid_text; + this.text = text4; + this.text_subtle = text_subtle; + } +}; +function rgb(r, g, b) { + let r$1 = min(255, max(0, r)); + let g$1 = min(255, max(0, g)); + let b$1 = min(255, max(0, b)); + let $ = from_rgb255(r$1, g$1, b$1); + if (!($ instanceof Ok)) { + throw makeError( + "let_assert", + FILEPATH3, + "lustre/ui/colour", + 63, + "rgb", + "Pattern match failed, no pattern matched the value.", + { + value: $, + start: 1255, + end: 1306, + pattern_start: 1266, + pattern_end: 1276 + } + ); + } + let colour = $[0]; + return colour; +} +function slate() { + return new ColourScale( + rgb(252, 252, 253), + rgb(249, 249, 251), + rgb(232, 232, 236), + rgb(240, 240, 243), + rgb(224, 225, 230), + rgb(205, 206, 214), + rgb(217, 217, 224), + rgb(185, 187, 198), + rgb(139, 141, 152), + rgb(150, 152, 162), + rgb(128, 131, 141), + rgb(255, 255, 255), + rgb(28, 32, 36), + rgb(96, 100, 108) + ); +} +function red() { + return new ColourScale( + rgb(255, 252, 252), + rgb(255, 247, 247), + rgb(255, 219, 220), + rgb(254, 235, 236), + rgb(255, 205, 206), + rgb(244, 169, 170), + rgb(253, 189, 190), + rgb(235, 142, 144), + rgb(229, 72, 77), + rgb(236, 83, 88), + rgb(220, 62, 66), + rgb(255, 255, 255), + rgb(100, 23, 35), + rgb(206, 44, 49) + ); +} +function plum() { + return new ColourScale( + rgb(254, 252, 255), + rgb(253, 247, 253), + rgb(247, 222, 248), + rgb(251, 235, 251), + rgb(242, 209, 243), + rgb(222, 173, 227), + rgb(233, 194, 236), + rgb(207, 145, 216), + rgb(171, 74, 186), + rgb(177, 85, 191), + rgb(161, 68, 175), + rgb(255, 255, 255), + rgb(83, 25, 93), + rgb(149, 62, 163) + ); +} +function blue() { + return new ColourScale( + rgb(251, 253, 255), + rgb(244, 250, 255), + rgb(213, 239, 255), + rgb(230, 244, 254), + rgb(194, 229, 255), + rgb(142, 200, 246), + rgb(172, 216, 252), + rgb(94, 177, 239), + rgb(0, 144, 255), + rgb(5, 148, 260), + rgb(5, 136, 240), + rgb(255, 255, 255), + rgb(17, 50, 100), + rgb(13, 116, 206) + ); +} +function green() { + return new ColourScale( + rgb(251, 254, 252), + rgb(244, 251, 246), + rgb(214, 241, 223), + rgb(230, 246, 235), + rgb(196, 232, 209), + rgb(142, 206, 170), + rgb(173, 221, 192), + rgb(91, 185, 139), + rgb(48, 164, 108), + rgb(53, 173, 115), + rgb(43, 154, 102), + rgb(255, 255, 255), + rgb(25, 59, 45), + rgb(33, 131, 88) + ); +} +function yellow() { + return new ColourScale( + rgb(253, 253, 249), + rgb(254, 252, 233), + rgb(255, 243, 148), + rgb(255, 250, 184), + rgb(255, 231, 112), + rgb(228, 199, 103), + rgb(243, 215, 104), + rgb(213, 174, 57), + rgb(255, 230, 41), + rgb(255, 234, 82), + rgb(255, 220, 0), + rgb(71, 59, 31), + rgb(71, 59, 31), + rgb(158, 108, 0) + ); +} +function default_light_palette() { + return new ColourPalette(slate(), blue(), plum(), green(), yellow(), red()); +} +function slate_dark() { + return new ColourScale( + rgb(24, 25, 27), + rgb(17, 17, 19), + rgb(39, 42, 45), + rgb(33, 34, 37), + rgb(46, 49, 53), + rgb(67, 72, 78), + rgb(54, 58, 63), + rgb(90, 97, 105), + rgb(105, 110, 119), + rgb(91, 96, 105), + rgb(119, 123, 132), + rgb(255, 255, 255), + rgb(237, 238, 240), + rgb(176, 180, 186) + ); +} +function red_dark() { + return new ColourScale( + rgb(32, 19, 20), + rgb(25, 17, 17), + rgb(80, 15, 28), + rgb(59, 18, 25), + rgb(97, 22, 35), + rgb(140, 51, 58), + rgb(114, 35, 45), + rgb(181, 69, 72), + rgb(229, 72, 77), + rgb(220, 52, 57), + rgb(236, 93, 94), + rgb(255, 255, 255), + rgb(255, 209, 217), + rgb(255, 149, 146) + ); +} +function plum_dark() { + return new ColourScale( + rgb(32, 19, 32), + rgb(24, 17, 24), + rgb(69, 29, 71), + rgb(53, 26, 53), + rgb(81, 36, 84), + rgb(115, 64, 121), + rgb(94, 48, 97), + rgb(146, 84, 156), + rgb(171, 74, 186), + rgb(154, 68, 167), + rgb(182, 88, 196), + rgb(255, 255, 255), + rgb(244, 212, 244), + rgb(231, 150, 243) + ); +} +function blue_dark() { + return new ColourScale( + rgb(17, 25, 39), + rgb(13, 21, 32), + rgb(0, 51, 98), + rgb(13, 40, 71), + rgb(0, 64, 116), + rgb(32, 93, 158), + rgb(16, 77, 135), + rgb(40, 112, 189), + rgb(0, 144, 255), + rgb(0, 110, 195), + rgb(59, 158, 255), + rgb(255, 255, 255), + rgb(194, 230, 255), + rgb(112, 184, 255) + ); +} +function green_dark() { + return new ColourScale( + rgb(18, 27, 23), + rgb(14, 21, 18), + rgb(17, 59, 41), + rgb(19, 45, 33), + rgb(23, 73, 51), + rgb(40, 104, 74), + rgb(32, 87, 62), + rgb(47, 124, 87), + rgb(48, 164, 108), + rgb(44, 152, 100), + rgb(51, 176, 116), + rgb(255, 255, 255), + rgb(177, 241, 203), + rgb(61, 214, 140) + ); +} +function yellow_dark() { + return new ColourScale( + rgb(27, 24, 15), + rgb(20, 18, 11), + rgb(54, 43, 0), + rgb(45, 35, 5), + rgb(67, 53, 0), + rgb(102, 84, 23), + rgb(82, 66, 2), + rgb(131, 106, 33), + rgb(255, 230, 41), + rgb(250, 220, 0), + rgb(255, 255, 87), + rgb(27, 24, 15), + rgb(246, 238, 180), + rgb(245, 225, 71) + ); +} +function default_dark_palette() { + return new ColourPalette( + slate_dark(), + blue_dark(), + plum_dark(), + green_dark(), + yellow_dark(), + red_dark() + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/theme.mjs +var Theme = class extends CustomType { + constructor(id, selector, font, radius2, space, light, dark) { + super(); + this.id = id; + this.selector = selector; + this.font = font; + this.radius = radius2; + this.space = space; + this.light = light; + this.dark = dark; + } +}; +var Fonts = class extends CustomType { + constructor(heading, body, code2) { + super(); + this.heading = heading; + this.body = body; + this.code = code2; + } +}; +var SizeScale = class extends CustomType { + constructor(xs, sm, md, lg, xl, xl_2, xl_3) { + super(); + this.xs = xs; + this.sm = sm; + this.md = md; + this.lg = lg; + this.xl = xl; + this.xl_2 = xl_2; + this.xl_3 = xl_3; + } +}; +var Global = class extends CustomType { +}; +var Host = class extends CustomType { +}; +var Class = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +function perfect_fifth(base) { + return new SizeScale( + divideFloat(divideFloat(divideFloat(base, 1.5), 1.5), 1.5), + divideFloat(divideFloat(base, 1.5), 1.5), + base, + base * 1.5, + base * 1.5 * 1.5, + base * 1.5 * 1.5 * 1.5, + base * 1.5 * 1.5 * 1.5 * 1.5 + ); +} +function golden_ratio(base) { + return new SizeScale( + divideFloat(divideFloat(divideFloat(base, 1.618), 1.618), 1.618), + divideFloat(divideFloat(base, 1.618), 1.618), + base, + base * 1.618, + base * 1.618 * 1.618, + base * 1.618 * 1.618 * 1.618, + base * 1.618 * 1.618 * 1.618 * 1.618 + ); +} +function with_scope(theme, selector) { + let _record = theme; + return new Theme( + _record.id, + selector, + _record.font, + _record.radius, + _record.space, + _record.light, + _record.dark + ); +} +function to_css_selector(selector) { + if (selector instanceof Global) { + return ""; + } else if (selector instanceof Host) { + return ":root, :host"; + } else if (selector instanceof Class) { + let class$2 = selector[0]; + return "." + class$2; + } else { + let $ = selector[1]; + if ($ === "") { + let name6 = selector[0]; + return "[data-" + name6 + "]"; + } else { + let name6 = selector[0]; + let value3 = $; + return "[data-" + name6 + "=" + value3 + "]"; + } + } +} +function to_css_rgb(colour) { + let $ = to_rgba(colour); + let r = $[0]; + let g = $[1]; + let b = $[2]; + let _block; + let _pipe = round2(r * 255); + _block = to_string(_pipe); + let r$1 = _block; + let _block$1; + let _pipe$1 = round2(g * 255); + _block$1 = to_string(_pipe$1); + let g$1 = _block$1; + let _block$2; + let _pipe$2 = round2(b * 255); + _block$2 = to_string(_pipe$2); + let b$1 = _block$2; + return r$1 + " " + g$1 + " " + b$1; +} +function var$(name6) { + return "--lustre-ui-" + name6; +} +function to_css_variable(name6, value3) { + return var$(name6) + ":" + value3 + ";"; +} +function to_colour_scale_variables(scale, name6) { + return concat2( + toList([ + to_css_variable(name6 + "-bg", to_css_rgb(scale.bg)), + to_css_variable(name6 + "-bg-subtle", to_css_rgb(scale.bg_subtle)), + to_css_variable(name6 + "-tint", to_css_rgb(scale.tint)), + to_css_variable(name6 + "-tint-subtle", to_css_rgb(scale.tint_subtle)), + to_css_variable(name6 + "-tint-strong", to_css_rgb(scale.tint_strong)), + to_css_variable(name6 + "-accent", to_css_rgb(scale.accent)), + to_css_variable(name6 + "-accent-subtle", to_css_rgb(scale.accent_subtle)), + to_css_variable(name6 + "-accent-strong", to_css_rgb(scale.accent_strong)), + to_css_variable(name6 + "-solid", to_css_rgb(scale.solid)), + to_css_variable(name6 + "-solid-subtle", to_css_rgb(scale.solid_subtle)), + to_css_variable(name6 + "-solid-strong", to_css_rgb(scale.solid_strong)), + to_css_variable(name6 + "-solid-text", to_css_rgb(scale.solid_text)), + to_css_variable(name6 + "-text", to_css_rgb(scale.text)), + to_css_variable(name6 + "-text-subtle", to_css_rgb(scale.text_subtle)), + "& ." + name6 + ', [data-scale="' + name6 + '"] {', + "--lustre-ui-bg: var(--lustre-ui-" + name6 + "-bg);", + "--lustre-ui-bg-subtle: var(--lustre-ui-" + name6 + "-bg-subtle);", + "--lustre-ui-tint: var(--lustre-ui-" + name6 + "-tint);", + "--lustre-ui-tint-subtle: var(--lustre-ui-" + name6 + "-tint-subtle);", + "--lustre-ui-tint-strong: var(--lustre-ui-" + name6 + "-tint-strong);", + "--lustre-ui-accent: var(--lustre-ui-" + name6 + "-accent);", + "--lustre-ui-accent-subtle: var(--lustre-ui-" + name6 + "-accent-subtle);", + "--lustre-ui-accent-strong: var(--lustre-ui-" + name6 + "-accent-strong);", + "--lustre-ui-solid: var(--lustre-ui-" + name6 + "-solid);", + "--lustre-ui-solid-subtle: var(--lustre-ui-" + name6 + "-solid-subtle);", + "--lustre-ui-solid-strong: var(--lustre-ui-" + name6 + "-solid-strong);", + "--lustre-ui-solid-text: var(--lustre-ui-" + name6 + "-solid-text);", + "--lustre-ui-text: var(--lustre-ui-" + name6 + "-text);", + "--lustre-ui-text-subtle: var(--lustre-ui-" + name6 + "-text-subtle);", + "}" + ]) + ); +} +function to_color_palette_variables(palette, scheme) { + return concat2( + toList([ + to_css_variable("color-scheme", scheme), + to_colour_scale_variables(palette.base, "base"), + to_colour_scale_variables(palette.primary, "primary"), + to_colour_scale_variables(palette.secondary, "secondary"), + to_colour_scale_variables(palette.success, "success"), + to_colour_scale_variables(palette.warning, "warning"), + to_colour_scale_variables(palette.danger, "danger"), + "--lustre-ui-bg: var(--lustre-ui-base-bg);", + "--lustre-ui-bg-subtle: var(--lustre-ui-base-bg-subtle);", + "--lustre-ui-tint: var(--lustre-ui-base-tint);", + "--lustre-ui-tint-subtle: var(--lustre-ui-base-tint-subtle);", + "--lustre-ui-tint-strong: var(--lustre-ui-base-tint-strong);", + "--lustre-ui-accent: var(--lustre-ui-base-accent);", + "--lustre-ui-accent-subtle: var(--lustre-ui-base-accent-subtle);", + "--lustre-ui-accent-strong: var(--lustre-ui-base-accent-strong);", + "--lustre-ui-solid: var(--lustre-ui-base-solid);", + "--lustre-ui-solid-subtle: var(--lustre-ui-base-solid-subtle);", + "--lustre-ui-solid-strong: var(--lustre-ui-base-solid-strong);", + "--lustre-ui-solid-text: var(--lustre-ui-base-solid-text);", + "--lustre-ui-text: var(--lustre-ui-base-text);", + "--lustre-ui-text-subtle: var(--lustre-ui-base-text-subtle);" + ]) + ); +} +function to_css_variables(theme) { + return concat2( + toList([ + to_css_variable("id", theme.id), + to_css_variable("font-heading", theme.font.heading), + to_css_variable("font-body", theme.font.body), + to_css_variable("font-code", theme.font.code), + to_css_variable("radius-xs", float_to_string(theme.radius.xs) + "rem"), + to_css_variable("radius-sm", float_to_string(theme.radius.sm) + "rem"), + to_css_variable("radius-md", float_to_string(theme.radius.md) + "rem"), + to_css_variable("radius-lg", float_to_string(theme.radius.lg) + "rem"), + to_css_variable("radius-xl", float_to_string(theme.radius.xl) + "rem"), + to_css_variable( + "radius-xl-2", + float_to_string(theme.radius.xl_2) + "rem" + ), + to_css_variable( + "radius-xl-3", + float_to_string(theme.radius.xl_3) + "rem" + ), + to_css_variable("spacing-xs", float_to_string(theme.space.xs) + "rem"), + to_css_variable("spacing-sm", float_to_string(theme.space.sm) + "rem"), + to_css_variable("spacing-md", float_to_string(theme.space.md) + "rem"), + to_css_variable("spacing-lg", float_to_string(theme.space.lg) + "rem"), + to_css_variable("spacing-xl", float_to_string(theme.space.xl) + "rem"), + to_css_variable( + "spacing-xl-2", + float_to_string(theme.space.xl_2) + "rem" + ), + to_css_variable( + "spacing-xl-3", + float_to_string(theme.space.xl_3) + "rem" + ), + to_color_palette_variables(theme.light, "light") + ]) + ); +} +var sans = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'; +var code = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'; +var stylesheet_global_light_no_dark = "\nbody {\n ${rules}\n\n background-color: rgb(var(--lustre-ui-bg));\n color: rgb(var(--lustre-ui-text));\n font-family: ${fonts.body}\n}\n\nh1, h2, h3, h4, h5, h6 {\n font-family: ${fonts.heading}\n}\n\npre, code, kbd, samp {\n font-family: ${fonts.code}\n}\n"; +var stylesheet_global_light_global_dark = "\nbody {\n ${rules}\n\n background-color: rgb(var(--lustre-ui-bg));\n color: rgb(var(--lustre-ui-text));\n font-family: ${fonts.body}\n}\n\nh1, h2, h3, h4, h5, h6 {\n font-family: ${fonts.heading}\n}\n\npre, code, kbd, samp {\n font-family: ${fonts.code}\n}\n\n@media (prefers-color-scheme: dark) {\n body {\n ${dark_rules}\n }\n}\n"; +var stylesheet_global_light_scoped_dark = "\nbody {\n ${rules}\n\n background-color: rgb(var(--lustre-ui-bg));\n color: rgb(var(--lustre-ui-text));\n font-family: ${fonts.body}\n}\n\nh1, h2, h3, h4, h5, h6 {\n font-family: ${fonts.heading}\n}\n\npre, code, kbd, samp {\n font-family: ${fonts.code}\n}\n\nbody${dark_selector}, body ${dark_selector} {\n ${dark_rules}\n}\n\n@media (prefers-color-scheme: dark) {\n body {\n ${dark_rules}\n }\n}\n"; +var stylesheet_scoped_light_no_dark = "\n${selector} {\n ${rules}\n\n background-color: rgb(var(--lustre-ui-bg));\n color: rgb(var(--lustre-ui-text));\n font-family: ${fonts.body}\n}\n\n${selector} :is(h1, h2, h3, h4, h5, h6) {\n font-family: ${fonts.heading}\n}\n\n${selector} :is(pre, code, kbd, samp) {\n font-family: ${fonts.code}\n}\n"; +var stylesheet_scoped_light_global_dark = "\n${selector} {\n ${rules}\n\n background-color: rgb(var(--lustre-ui-bg));\n color: rgb(var(--lustre-ui-text));\n font-family: ${fonts.body}\n}\n\n${selector} :is(h1, h2, h3, h4, h5, h6) {\n font-family: ${fonts.heading}\n}\n\n${selector} :is(pre, code, kbd, samp) {\n font-family: ${fonts.code}\n}\n\n@media (prefers-color-scheme: dark) {\n ${selector} {\n ${dark_rules}\n }\n}\n"; +var stylesheet_scoped_light_scoped_dark = "\n${selector} {\n ${rules}\n\n background-color: rgb(var(--lustre-ui-bg));\n color: rgb(var(--lustre-ui-text));\n font-family: ${fonts.body}\n}\n\n${selector} :is(h1, h2, h3, h4, h5, h6) {\n font-family: ${fonts.heading}\n}\n\n${selector} :is(pre, code, kbd, samp) {\n font-family: ${fonts.code}\n}\n\n${selector}${dark_selector}, ${selector} ${dark_selector} {\n ${dark_rules}\n}\n\n@media (prefers-color-scheme: dark) {\n ${selector} {\n ${dark_rules}\n }\n}\n"; +function to_style(theme) { + let data_attr = attribute2("data-lustre-ui-theme", theme.id); + let $ = theme.selector; + let $1 = theme.dark; + if ($1 instanceof Some) { + let $2 = $1[0][0]; + if ($2 instanceof Global) { + if ($ instanceof Global) { + let dark_palette = $1[0][1]; + let _pipe = stylesheet_global_light_global_dark; + let _pipe$1 = replace( + _pipe, + "${rules}", + to_css_variables(theme) + ); + let _pipe$2 = replace( + _pipe$1, + "${dark_rules}", + to_color_palette_variables(dark_palette, "dark") + ); + let _pipe$3 = replace( + _pipe$2, + "${fonts.heading}", + theme.font.heading + ); + let _pipe$4 = replace(_pipe$3, "${fonts.body}", theme.font.body); + let _pipe$5 = replace(_pipe$4, "${fonts.code}", theme.font.code); + return ((_capture) => { + return style2(toList([data_attr]), _capture); + })(_pipe$5); + } else { + let selector = $; + let dark_palette = $1[0][1]; + let _pipe = stylesheet_scoped_light_global_dark; + let _pipe$1 = replace( + _pipe, + "${selector}", + to_css_selector(selector) + ); + let _pipe$2 = replace( + _pipe$1, + "${rules}", + to_css_variables(theme) + ); + let _pipe$3 = replace( + _pipe$2, + "${dark_rules}", + to_color_palette_variables(dark_palette, "dark") + ); + let _pipe$4 = replace( + _pipe$3, + "${fonts.heading}", + theme.font.heading + ); + let _pipe$5 = replace(_pipe$4, "${fonts.body}", theme.font.body); + let _pipe$6 = replace(_pipe$5, "${fonts.code}", theme.font.code); + return ((_capture) => { + return style2(toList([data_attr]), _capture); + })(_pipe$6); + } + } else if ($ instanceof Global) { + let dark_selector = $2; + let dark_palette = $1[0][1]; + let _pipe = stylesheet_global_light_scoped_dark; + let _pipe$1 = replace(_pipe, "${rules}", to_css_variables(theme)); + let _pipe$2 = replace( + _pipe$1, + "${dark_selector}", + to_css_selector(dark_selector) + ); + let _pipe$3 = replace( + _pipe$2, + "${dark_rules}", + to_color_palette_variables(dark_palette, "dark") + ); + let _pipe$4 = replace( + _pipe$3, + "${fonts.heading}", + theme.font.heading + ); + let _pipe$5 = replace(_pipe$4, "${fonts.body}", theme.font.body); + let _pipe$6 = replace(_pipe$5, "${fonts.code}", theme.font.code); + return ((_capture) => { + return style2(toList([data_attr]), _capture); + })(_pipe$6); + } else { + let selector = $; + let dark_selector = $2; + let dark_palette = $1[0][1]; + let _pipe = stylesheet_scoped_light_scoped_dark; + let _pipe$1 = replace( + _pipe, + "${selector}", + to_css_selector(selector) + ); + let _pipe$2 = replace( + _pipe$1, + "${rules}", + to_css_variables(theme) + ); + let _pipe$3 = replace( + _pipe$2, + "${dark_selector}", + to_css_selector(dark_selector) + ); + let _pipe$4 = replace( + _pipe$3, + "${dark_rules}", + to_color_palette_variables(dark_palette, "dark") + ); + let _pipe$5 = replace( + _pipe$4, + "${fonts.heading}", + theme.font.heading + ); + let _pipe$6 = replace(_pipe$5, "${fonts.body}", theme.font.body); + let _pipe$7 = replace(_pipe$6, "${fonts.code}", theme.font.code); + return ((_capture) => { + return style2(toList([data_attr]), _capture); + })(_pipe$7); + } + } else if ($ instanceof Global) { + let _pipe = stylesheet_global_light_no_dark; + let _pipe$1 = replace(_pipe, "${rules}", to_css_variables(theme)); + let _pipe$2 = replace( + _pipe$1, + "${fonts.heading}", + theme.font.heading + ); + let _pipe$3 = replace(_pipe$2, "${fonts.body}", theme.font.body); + let _pipe$4 = replace(_pipe$3, "${fonts.code}", theme.font.code); + return ((_capture) => { + return style2(toList([data_attr]), _capture); + })( + _pipe$4 + ); + } else { + let selector = $; + let _pipe = stylesheet_scoped_light_no_dark; + let _pipe$1 = replace( + _pipe, + "${selector}", + to_css_selector(selector) + ); + let _pipe$2 = replace(_pipe$1, "${rules}", to_css_variables(theme)); + let _pipe$3 = replace( + _pipe$2, + "${fonts.heading}", + theme.font.heading + ); + let _pipe$4 = replace(_pipe$3, "${fonts.body}", theme.font.body); + let _pipe$5 = replace(_pipe$4, "${fonts.code}", theme.font.code); + return ((_capture) => { + return style2(toList([data_attr]), _capture); + })( + _pipe$5 + ); + } +} +function inject(theme, view8) { + return fragment2(toList([to_style(theme), view8()])); +} +function default$() { + let id = "lustre-ui-default"; + let font$1 = new Fonts(sans, sans, code); + let radius$1 = perfect_fifth(0.75); + let space = golden_ratio(1); + let light = default_light_palette(); + let dark = default_dark_palette(); + return new Theme( + id, + new Global(), + font$1, + radius$1, + space, + light, + new Some([new Class("dark"), dark]) + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/accordion_stories.mjs +function faq_story() { + return story2( + "FAQ", + () => { + return scene( + (_) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return element5( + toList([]), + toList([ + item( + "q1", + "What is an accordion?", + toList([ + text3( + "An interactive element for showing/hiding content" + ) + ]) + ), + item( + "q2", + "When should I use one?", + toList([ + text3( + "When you want to organize content into sections" + ) + ]) + ) + ]) + ); + } + ); + } + ); + } + ); +} +function all2() { + return chapter("Accordion", toList([faq_story()])); +} + +// build/dev/javascript/lustre_ui/lustre/ui/alert.mjs +function of(element15, attributes, children) { + return element15( + prepend( + role("alert"), + prepend(class$("lustre-ui-alert"), attributes) + ), + children + ); +} +function element6(attributes, children) { + return of(div, attributes, children); +} +function indicator(icon2) { + return span( + toList([ + class$("alert-indicator"), + role("presentation") + ]), + toList([icon2]) + ); +} +function title2(attributes, children) { + return header( + prepend(class$("alert-title"), attributes), + children + ); +} +function content(attributes, children) { + return section( + prepend(class$("alert-content"), attributes), + children + ); +} +function danger() { + return class$("danger"); +} +function success2() { + return class$("success"); +} + +// build/dev/javascript/lustre_ui/lustre/ui/alert_stories.mjs +var FILEPATH4 = "dev/lustre/ui/alert_stories.gleam"; +function title_only_success_alert_story() { + return story2( + "Title only, success", + () => { + return scene( + (_) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return element6( + toList([success2()]), + toList([ + title2( + toList([]), + toList([text3("New todo added to your list.")]) + ) + ]) + ); + } + ); + } + ); + } + ); +} +function title_indicator_content_error_alert_story() { + return story2( + "Title + indicator + content, error", + () => { + return scene( + (_) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return element6( + toList([danger()]), + toList([ + indicator(exclamation_triangle(toList([]))), + title2( + toList([]), + toList([text3("Could not delete todo")]) + ), + content( + toList([]), + toList([ + p( + toList([]), + toList([ + text3( + "Check your internet connection and try again." + ) + ]) + ) + ]) + ) + ]) + ); + } + ); + } + ); + } + ); +} +function all3() { + let $ = register3(); + if (!($ instanceof Ok)) { + throw makeError( + "let_assert", + FILEPATH4, + "lustre/ui/alert_stories", + 13, + "all", + "Pattern match failed, no pattern matched the value.", + { value: $, start: 339, end: 378, pattern_start: 350, pattern_end: 355 } + ); + } + return chapter( + "Alert", + toList([ + title_only_success_alert_story(), + title_indicator_content_error_alert_story() + ]) + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/badge.mjs +function of2(element15, attributes, children) { + return element15( + prepend(class$("lustre-ui-badge"), attributes), + children + ); +} +function element7(attributes, children) { + return of2(small, attributes, children); +} +function solid() { + return class$("badge-solid"); +} +function background(value3) { + return style("--background", value3); +} + +// build/dev/javascript/lustre_ui/lustre/ui/badge_stories.mjs +function online_avatar_story() { + return story2( + "Online Avatar Indicator", + () => { + return scene( + (_) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return div( + toList([class$("inline-block relative")]), + toList([ + img( + toList([ + class$("h-10 w-10 rounded-full"), + src("https://placehold.co/100"), + alt("Avatar") + ]) + ), + element7( + toList([ + background("green"), + solid(), + class$("absolute top-0 right-0"), + title("online") + ]), + toList([]) + ) + ]) + ); + } + ); + } + ); + } + ); +} +function all4() { + return chapter("Badge", toList([online_avatar_story()])); +} + +// build/dev/javascript/lustre_ui/lustre/ui/breadcrumb.mjs +function element8(attributes, children) { + return nav( + toList([attribute2("aria-label", "breadcrumb")]), + toList([ + ol( + prepend(class$("lustre-ui-breadcrumb"), attributes), + children + ) + ]) + ); +} +function item2(attributes, children) { + return li( + prepend(class$("breadcrumb-item"), attributes), + children + ); +} +function current(attributes, children) { + return span( + prepend( + class$("breadcrumb-item breadcrumb-current"), + prepend( + role("link"), + prepend( + attribute2("aria-disabled", "true"), + prepend(attribute2("aria-current", "page"), attributes) + ) + ) + ), + children + ); +} +function separator(attributes, content3) { + return li( + prepend( + class$("breadcrumb-separator"), + prepend( + role("presentation"), + prepend(attribute2("aria-hidden", "true"), attributes) + ) + ), + toList([content3]) + ); +} +function chevron(attributes) { + return separator(attributes, chevron_right(toList([]))); +} +function slash2(attributes) { + return separator(attributes, slash(toList([]))); +} +function ellipsis(attributes, label2) { + return span( + prepend( + class$("breadcrumb-ellipsis"), + prepend(role("presentation"), attributes) + ), + toList([ + dots_horizontal(toList([])), + span(toList([]), toList([text3(label2)])) + ]) + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/breadcrumb_stories.mjs +function basic_breadcrumb_story() { + return story2( + "Basic Breadcrumb Navigation", + () => { + return scene( + (_) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return element8( + toList([]), + toList([ + item2( + toList([]), + toList([ + a( + toList([href("/")]), + toList([text3("Home")]) + ) + ]) + ), + chevron(toList([])), + item2( + toList([]), + toList([ + a( + toList([href("/documents")]), + toList([text3("Documents")]) + ) + ]) + ), + chevron(toList([])), + current( + toList([]), + toList([text3("My Document")]) + ) + ]) + ); + } + ); + } + ); + } + ); +} +function breadcrumb_with_collapsed_items_story() { + return story2( + "Breadcrumb with Collapsed Items", + () => { + return scene( + (_) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return element8( + toList([]), + toList([ + item2( + toList([]), + toList([ + a( + toList([href("/")]), + toList([text3("Home")]) + ) + ]) + ), + slash2(toList([])), + ellipsis(toList([]), "Collapsed navigation items"), + slash2(toList([])), + current( + toList([]), + toList([text3("My Document")]) + ) + ]) + ); + } + ); + } + ); + } + ); +} +function all5() { + return chapter( + "Breadcrumb", + toList([basic_breadcrumb_story(), breadcrumb_with_collapsed_items_story()]) + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/button.mjs +function of3(element15, attributes, children) { + return element15( + prepend( + class$("lustre-ui-button"), + prepend(role("button"), attributes) + ), + children + ); +} +function element9(attributes, children) { + return of3( + button, + prepend(attribute2("tabindex", "0"), attributes), + children + ); +} +function shortcut_badge(attributes, chord) { + return span( + prepend( + class$("button-badge"), + prepend(title(join(chord, "+")), attributes) + ), + map( + chord, + (key) => { + return span( + toList([class$("key")]), + toList([text3(key)]) + ); + } + ) + ); +} +function danger2() { + return class$("danger"); +} +function clear() { + return class$("button-clear"); +} +function solid2() { + return class$("button-solid"); +} + +// build/dev/javascript/lustre_ui/lustre/ui/button_stories.mjs +function basic_button_story() { + return story2( + "Basic Button with Icon and Text", + () => { + return scene( + (_) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return element9( + toList([]), + toList([bookmark(toList([])), text3(" Save")]) + ); + } + ); + } + ); + } + ); +} +function command_palette_button_story() { + return story2( + "Command Palette Button with Keyboard Shortcut", + () => { + return scene( + (_) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return element9( + toList([solid2()]), + toList([ + text3("Open"), + shortcut_badge(toList([]), toList(["\u2318", "k"])) + ]) + ); + } + ); + } + ); + } + ); +} +function all6() { + return chapter( + "Button", + toList([basic_button_story(), command_palette_button_story()]) + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/card.mjs +function of4(element15, attributes, children) { + return element15( + prepend(class$("lustre-ui-card"), attributes), + children + ); +} +function element10(attributes, children) { + return of4(article, attributes, children); +} +function header2(attributes, children) { + return header( + prepend(class$("card-header"), attributes), + children + ); +} +function content2(attributes, children) { + return main( + prepend(class$("card-content"), attributes), + children + ); +} +function footer2(attributes, children) { + return footer( + prepend(class$("card-footer"), attributes), + children + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/card_stories.mjs +function basic_card_story() { + return story2( + "Basic Content Card with Header and Content", + () => { + return scene( + (_) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return element10( + toList([]), + toList([ + header2( + toList([]), + toList([ + h2( + toList([]), + toList([text3("Easy Chocolate Chip Cookies")]) + ) + ]) + ), + content2( + toList([]), + toList([ + p( + toList([]), + toList([ + text3( + "A simple recipe for delicious chocolate chip cookies that are crisp at the edges and chewy in the middle." + ) + ]) + ) + ]) + ) + ]) + ); + } + ); + } + ); + } + ); +} +function dialog_card_story() { + return story2( + "Dialog-Style Card with Footer Actions", + () => { + return scene( + (_) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return element10( + toList([]), + toList([ + header2( + toList([]), + toList([text3("Delete recipe?")]) + ), + content2( + toList([]), + toList([ + p( + toList([]), + toList([ + text3( + "Are you sure that you want to delete this recipe?" + ) + ]) + ) + ]) + ), + footer2( + toList([]), + toList([ + element9( + toList([clear()]), + toList([text3("Cancel")]) + ), + element9( + toList([solid2(), danger2()]), + toList([text3("Delete")]) + ) + ]) + ) + ]) + ); + } + ); + } + ); + } + ); +} +function all7() { + return chapter( + "Card", + toList([basic_card_story(), dialog_card_story()]) + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/input.mjs +function element11(attributes) { + return input( + prepend(class$("lustre-ui-input"), attributes) + ); +} +function container(attributes, children) { + return div( + prepend(class$("lustre-ui-input-container"), attributes), + children + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/primitives/popover.mjs +var TopLeft = class extends CustomType { +}; +var TopMiddle = class extends CustomType { +}; +var TopRight = class extends CustomType { +}; +var RightTop = class extends CustomType { +}; +var RightMiddle = class extends CustomType { +}; +var RightBottom = class extends CustomType { +}; +var BottomLeft = class extends CustomType { +}; +var BottomMiddle = class extends CustomType { +}; +var BottomRight = class extends CustomType { +}; +var LeftTop = class extends CustomType { +}; +var LeftMiddle = class extends CustomType { +}; +var WillExpand = class extends CustomType { +}; +var Expanded = class extends CustomType { +}; +var WillCollapse = class extends CustomType { +}; +var Collapsing = class extends CustomType { +}; +var Collapsed = class extends CustomType { +}; +var ParentSetOpen = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var SchedulerDidTick = class extends CustomType { +}; +var TransitionDidEnd = class extends CustomType { +}; +var UserPressedTrigger2 = class extends CustomType { + constructor(event4) { + super(); + this.event = event4; + } +}; +function open(is_open) { + return attribute2( + "aria-expanded", + (() => { + let _pipe = to_string2(is_open); + return lowercase(_pipe); + })() + ); +} +function anchor(direction) { + return attribute2( + "anchor", + (() => { + if (direction instanceof TopLeft) { + return "top-left"; + } else if (direction instanceof TopMiddle) { + return "top-middle"; + } else if (direction instanceof TopRight) { + return "top-right"; + } else if (direction instanceof RightTop) { + return "right-top"; + } else if (direction instanceof RightMiddle) { + return "right-middle"; + } else if (direction instanceof RightBottom) { + return "right-bottom"; + } else if (direction instanceof BottomLeft) { + return "bottom-left"; + } else if (direction instanceof BottomMiddle) { + return "bottom-middle"; + } else if (direction instanceof BottomRight) { + return "bottom-right"; + } else if (direction instanceof LeftTop) { + return "left-top"; + } else if (direction instanceof LeftMiddle) { + return "left-middle"; + } else { + return "left-bottom"; + } + })() + ); +} +function equal_width() { + return attribute2("equal-width", ""); +} +function gap(value3) { + return style("--gap", value3); +} +function on_open(handler) { + return on("open", success(handler)); +} +function on_close(handler) { + return on("close", success(handler)); +} +function init6(_) { + let model = new Collapsed(); + let effect = batch(toList([set_pseudo_state2("collapsed")])); + return [model, effect]; +} +function tick2() { + return after_paint( + (dispatch2, _) => { + return dispatch2(new SchedulerDidTick()); + } + ); +} +function update5(model, msg) { + let $ = echo2(msg, "src/lustre/ui/primitives/popover.gleam", 162); + if ($ instanceof ParentSetOpen) { + let $1 = $[0]; + if ($1) { + if (model instanceof WillCollapse) { + return [ + new WillExpand(), + batch( + toList([tick2(), set_pseudo_state2("will-expand")]) + ) + ]; + } else if (model instanceof Collapsed) { + return [ + new WillExpand(), + batch( + toList([tick2(), set_pseudo_state2("will-expand")]) + ) + ]; + } else { + return [model, none()]; + } + } else if (model instanceof WillExpand) { + return [ + new WillCollapse(), + batch( + toList([tick2(), set_pseudo_state2("will-collapse")]) + ) + ]; + } else if (model instanceof Expanded) { + return [ + new WillCollapse(), + batch( + toList([tick2(), set_pseudo_state2("will-collapse")]) + ) + ]; + } else { + return [model, none()]; + } + } else if ($ instanceof SchedulerDidTick) { + if (model instanceof WillExpand) { + return [new Expanded(), set_pseudo_state2("expanded")]; + } else if (model instanceof WillCollapse) { + return [new Collapsing(), set_pseudo_state2("collapsing")]; + } else { + return [model, none()]; + } + } else if ($ instanceof TransitionDidEnd) { + if (model instanceof Collapsing) { + return [new Collapsed(), set_pseudo_state2("collapsed")]; + } else { + return [model, none()]; + } + } else if (model instanceof WillExpand) { + let event4 = $.event; + return [ + model, + batch( + toList([ + emit("close", null$()), + emit( + "change", + object2(toList([["open", bool2(false)]])) + ), + prevent_default2(event4) + ]) + ) + ]; + } else if (model instanceof Expanded) { + let event4 = $.event; + return [ + model, + batch( + toList([ + emit("close", null$()), + emit( + "change", + object2(toList([["open", bool2(false)]])) + ), + prevent_default2(event4) + ]) + ) + ]; + } else if (model instanceof WillCollapse) { + let event4 = $.event; + return [ + model, + batch( + toList([ + emit("open", null$()), + emit( + "change", + object2(toList([["open", bool2(true)]])) + ), + prevent_default2(event4) + ]) + ) + ]; + } else if (model instanceof Collapsing) { + let event4 = $.event; + return [ + model, + batch( + toList([ + emit("open", null$()), + emit( + "change", + object2(toList([["open", bool2(true)]])) + ), + prevent_default2(event4) + ]) + ) + ]; + } else { + let event4 = $.event; + return [ + model, + batch( + toList([ + emit("open", null$()), + emit( + "change", + object2(toList([["open", bool2(true)]])) + ), + prevent_default2(event4) + ]) + ) + ]; + } +} +function handle_keydown3() { + return then$( + dynamic, + (event4) => { + return field( + "key", + string2, + (key) => { + if (key === "Enter") { + return success(new UserPressedTrigger2(event4)); + } else if (key === " ") { + return success(new UserPressedTrigger2(event4)); + } else { + return failure(new UserPressedTrigger2(event4), ""); + } + } + ); + } + ); +} +function view_trigger2() { + return slot( + toList([ + name("trigger"), + on( + "click", + map2( + dynamic, + (var0) => { + return new UserPressedTrigger2(var0); + } + ) + ), + on("keydown", handle_keydown3()) + ]), + toList([]) + ); +} +function view_popover(model) { + return guard( + isEqual(model, new Collapsed()), + text3(""), + () => { + return div( + toList([ + attribute2("part", "popover-content"), + on("transitionend", success(new TransitionDidEnd())) + ]), + toList([slot(toList([name("popover")]), toList([]))]) + ); + } + ); +} +function view6(model) { + return div( + toList([style("position", "relative")]), + toList([view_trigger2(), view_popover(model)]) + ); +} +var name4 = "lustre-ui-popover"; +function register4() { + let app = component( + init6, + update5, + view6, + toList([ + adopt_styles(true), + on_attribute_change( + "aria-expanded", + (value3) => { + if (value3 === "true") { + return new Ok(new ParentSetOpen(true)); + } else if (value3 === "") { + return new Ok(new ParentSetOpen(true)); + } else if (value3 === "false") { + return new Ok(new ParentSetOpen(false)); + } else { + return new Error(void 0); + } + } + ) + ]) + ); + return make_component(app, name4); +} +function element12(attributes, trigger, content3) { + return element2( + name4, + attributes, + toList([ + div(toList([attribute2("slot", "trigger")]), toList([trigger])), + div(toList([attribute2("slot", "popover")]), toList([content3])) + ]) + ); +} +function echo2(value3, file, line) { + const grey = "\x1B[90m"; + const reset_color = "\x1B[39m"; + const file_line = `${file}:${line}`; + const string_value = echo$inspect2(value3); + if (globalThis.process?.stderr?.write) { + const string5 = `${grey}${file_line}${reset_color} +${string_value} +`; + process.stderr.write(string5); + } else if (globalThis.Deno) { + const string5 = `${grey}${file_line}${reset_color} +${string_value} +`; + globalThis.Deno.stderr.writeSync(new TextEncoder().encode(string5)); + } else { + const string5 = `${file_line} +${string_value}`; + globalThis.console.log(string5); + } + return value3; +} +function echo$inspectString2(str) { + let new_str = '"'; + for (let i = 0; i < str.length; i++) { + let char = str[i]; + if (char == "\n") new_str += "\\n"; + else if (char == "\r") new_str += "\\r"; + else if (char == " ") new_str += "\\t"; + else if (char == "\f") new_str += "\\f"; + else if (char == "\\") new_str += "\\\\"; + else if (char == '"') new_str += '\\"'; + else if (char < " " || char > "~" && char < "\xA0") { + new_str += "\\u{" + char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0") + "}"; + } else { + new_str += char; + } + } + new_str += '"'; + return new_str; +} +function echo$inspectDict2(map6) { + let body = "dict.from_list(["; + let first3 = true; + let key_value_pairs = []; + map6.forEach((value3, key) => { + key_value_pairs.push([key, value3]); + }); + key_value_pairs.sort(); + key_value_pairs.forEach(([key, value3]) => { + if (!first3) body = body + ", "; + body = body + "#(" + echo$inspect2(key) + ", " + echo$inspect2(value3) + ")"; + first3 = false; + }); + return body + "])"; +} +function echo$inspectCustomType2(record) { + const props = globalThis.Object.keys(record).map((label2) => { + const value3 = echo$inspect2(record[label2]); + return isNaN(parseInt(label2)) ? `${label2}: ${value3}` : value3; + }).join(", "); + return props ? `${record.constructor.name}(${props})` : record.constructor.name; +} +function echo$inspectObject2(v) { + const name6 = Object.getPrototypeOf(v)?.constructor?.name || "Object"; + const props = []; + for (const k of Object.keys(v)) { + props.push(`${echo$inspect2(k)}: ${echo$inspect2(v[k])}`); + } + const body = props.length ? " " + props.join(", ") + " " : ""; + const head = name6 === "Object" ? "" : name6 + " "; + return `//js(${head}{${body}})`; +} +function echo$inspect2(v) { + const t = typeof v; + if (v === true) return "True"; + if (v === false) return "False"; + if (v === null) return "//js(null)"; + if (v === void 0) return "Nil"; + if (t === "string") return echo$inspectString2(v); + if (t === "bigint" || t === "number") return v.toString(); + if (globalThis.Array.isArray(v)) + return `#(${v.map(echo$inspect2).join(", ")})`; + if (v instanceof List) + return `[${v.toArray().map(echo$inspect2).join(", ")}]`; + if (v instanceof UtfCodepoint) + return `//utfcodepoint(${String.fromCodePoint(v.value)})`; + if (v instanceof BitArray) return echo$inspectBitArray2(v); + if (v instanceof CustomType) return echo$inspectCustomType2(v); + if (echo$isDict2(v)) return echo$inspectDict2(v); + if (v instanceof Set) + return `//js(Set(${[...v].map(echo$inspect2).join(", ")}))`; + if (v instanceof RegExp) return `//js(${v})`; + if (v instanceof Date) return `//js(Date("${v.toISOString()}"))`; + if (v instanceof Function) { + const args = []; + for (const i of Array(v.length).keys()) + args.push(String.fromCharCode(i + 97)); + return `//fn(${args.join(", ")}) { ... }`; + } + return echo$inspectObject2(v); +} +function echo$inspectBitArray2(bitArray) { + let endOfAlignedBytes = bitArray.bitOffset + 8 * Math.trunc(bitArray.bitSize / 8); + let alignedBytes = bitArraySlice( + bitArray, + bitArray.bitOffset, + endOfAlignedBytes + ); + let remainingUnalignedBits = bitArray.bitSize % 8; + if (remainingUnalignedBits > 0) { + let remainingBits = bitArraySliceToInt( + bitArray, + endOfAlignedBytes, + bitArray.bitSize, + false, + false + ); + let alignedBytesArray = Array.from(alignedBytes.rawBuffer); + let suffix = `${remainingBits}:size(${remainingUnalignedBits})`; + if (alignedBytesArray.length === 0) { + return `<<${suffix}>>`; + } else { + return `<<${Array.from(alignedBytes.rawBuffer).join(", ")}, ${suffix}>>`; + } + } else { + return `<<${Array.from(alignedBytes.rawBuffer).join(", ")}>>`; + } +} +function echo$isDict2(value3) { + try { + return value3 instanceof Dict; + } catch { + return false; + } +} + +// build/dev/javascript/lustre_ui/lustre/ui/combobox.mjs +var FILEPATH5 = "src/lustre/ui/combobox.gleam"; +var Item2 = class extends CustomType { + constructor(value3, label2, content3) { + super(); + this.value = value3; + this.label = label2; + this.content = content3; + } +}; +var Model5 = class extends CustomType { + constructor(expanded2, value3, placeholder2, query, intent, intent_strategy, options) { + super(); + this.expanded = expanded2; + this.value = value3; + this.placeholder = placeholder2; + this.query = query; + this.intent = intent; + this.intent_strategy = intent_strategy; + this.options = options; + } +}; +var ByIndex = class extends CustomType { +}; +var ByLength = class extends CustomType { +}; +var Options4 = class extends CustomType { + constructor(all10, filtered, lookup_label, lookup_index) { + super(); + this.all = all10; + this.filtered = filtered; + this.lookup_label = lookup_label; + this.lookup_index = lookup_index; + } +}; +var DomBlurredTrigger = class extends CustomType { +}; +var DomFocusedTrigger = class extends CustomType { +}; +var ParentChangedChildren2 = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var ParentSetPlaceholder = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var ParentSetStrategy = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var ParentSetValue = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var UserActivatedPopoverTrigger = class extends CustomType { + constructor(input3) { + super(); + this.input = input3; + } +}; +var UserChangedQuery = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var UserClosedMenu = class extends CustomType { +}; +var UserHoveredOption = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +var UserOpenedMenu = class extends CustomType { +}; +var UserPressedDown2 = class extends CustomType { + constructor(event4) { + super(); + this.event = event4; + } +}; +var UserPressedEnd2 = class extends CustomType { + constructor(event4) { + super(); + this.event = event4; + } +}; +var UserPressedEnter = class extends CustomType { + constructor(event4) { + super(); + this.event = event4; + } +}; +var UserPressedEscape = class extends CustomType { + constructor(event4, trigger) { + super(); + this.event = event4; + this.trigger = trigger; + } +}; +var UserPressedHome2 = class extends CustomType { + constructor(event4) { + super(); + this.event = event4; + } +}; +var UserPressedUp2 = class extends CustomType { + constructor(event4) { + super(); + this.event = event4; + } +}; +var UserSelectedOption = class extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +}; +function option2(value3, label2) { + return new Item2(value3, label2, toList([])); +} +function value2(value3) { + return value(value3); +} +function on_change3(handler) { + return on( + "change", + subfield( + toList(["detail", "value"]), + string2, + (value3) => { + return success(handler(value3)); + } + ) + ); +} +function init7(_) { + let model = new Model5( + false, + "", + "Select an option...", + "", + new None(), + new ByIndex(), + new Options4(toList([]), toList([]), new$8(), new$8()) + ); + let effect = batch(toList([set_pseudo_state2("empty")])); + return [model, effect]; +} +function contains_query(option3, query) { + let _pipe = option3.label; + let _pipe$1 = lowercase(_pipe); + return contains_string(_pipe$1, query); +} +function intent_from_query(query, strategy, options) { + return guard( + query === "", + new None(), + () => { + let query$1 = lowercase(query); + let compare_options = (a2, b) => { + let a_label = lowercase(a2.label); + let b_label = lowercase(b.label); + let a_starts = starts_with(a_label, query$1); + let b_starts = starts_with(b_label, query$1); + let $ = get4(options.lookup_index, a2.value); + if (!($ instanceof Ok)) { + throw makeError( + "let_assert", + FILEPATH5, + "lustre/ui/combobox", + 513, + "intent_from_query", + "Pattern match failed, no pattern matched the value.", + { + value: $, + start: 14126, + end: 14192, + pattern_start: 14137, + pattern_end: 14148 + } + ); + } + let a_index = $[0]; + let $1 = get4(options.lookup_index, b.value); + if (!($1 instanceof Ok)) { + throw makeError( + "let_assert", + FILEPATH5, + "lustre/ui/combobox", + 514, + "intent_from_query", + "Pattern match failed, no pattern matched the value.", + { + value: $1, + start: 14197, + end: 14263, + pattern_start: 14208, + pattern_end: 14219 + } + ); + } + let b_index = $1[0]; + if (b_starts) { + if (!a_starts) { + return new Gt(); + } else if (strategy instanceof ByIndex) { + return compare(a_index, b_index); + } else { + return compare( + string_length(a_label), + string_length(b_label) + ); + } + } else if (a_starts) { + return new Lt(); + } else if (strategy instanceof ByIndex) { + return compare(a_index, b_index); + } else { + return compare(string_length(a_label), string_length(b_label)); + } + }; + let _pipe = options.all; + let _pipe$1 = filter( + _pipe, + (_capture) => { + return contains_query(_capture, query$1); + } + ); + let _pipe$2 = sort(_pipe$1, compare_options); + let _pipe$3 = first(_pipe$2); + let _pipe$4 = map3(_pipe$3, (option3) => { + return option3.value; + }); + return from_result(_pipe$4); + } + ); +} +function update6(model, msg) { + let $ = echo3(msg, "src/lustre/ui/combobox.gleam", 316); + if ($ instanceof DomBlurredTrigger) { + return [model, remove_pseudo_state2("trigger-focus")]; + } else if ($ instanceof DomFocusedTrigger) { + return [model, set_pseudo_state2("trigger-focus")]; + } else if ($ instanceof ParentChangedChildren2) { + let all10 = $[0]; + let lookup_label = from_list3( + map(all10, (item3) => { + return [item3.value, item3.label]; + }) + ); + let lookup_index = indexed( + map(all10, (item3) => { + return item3.value; + }) + ); + let filtered = filter( + all10, + (option3) => { + let _pipe = lowercase(option3.label); + return contains_string(_pipe, lowercase(model.query)); + } + ); + let options = new Options4(all10, filtered, lookup_label, lookup_index); + let intent = new None(); + let _block; + let _record = model; + _block = new Model5( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + intent, + _record.intent_strategy, + options + ); + let model$1 = _block; + let effect = none(); + return [model$1, effect]; + } else if ($ instanceof ParentSetPlaceholder) { + let placeholder$1 = $[0]; + return [ + (() => { + let _record = model; + return new Model5( + _record.expanded, + _record.value, + placeholder$1, + _record.query, + _record.intent, + _record.intent_strategy, + _record.options + ); + })(), + none() + ]; + } else if ($ instanceof ParentSetStrategy) { + let strategy = $[0]; + return [ + (() => { + let _record = model; + return new Model5( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + _record.intent, + strategy, + _record.options + ); + })(), + none() + ]; + } else if ($ instanceof ParentSetValue) { + let value$1 = $[0]; + let _block; + let _record = model; + _block = new Model5( + _record.expanded, + value$1, + _record.placeholder, + _record.query, + new Some(value$1), + _record.intent_strategy, + _record.options + ); + let model$1 = _block; + let _block$1; + if (value$1 === "") { + _block$1 = set_pseudo_state2("empty"); + } else { + _block$1 = remove_pseudo_state2("empty"); + } + let effect = _block$1; + return [model$1, effect]; + } else if ($ instanceof UserActivatedPopoverTrigger) { + let input3 = $.input; + let effect = after_paint( + (_, _1) => { + return focus3(input3); + } + ); + return [model, effect]; + } else if ($ instanceof UserChangedQuery) { + let query = $[0]; + let filtered = filter( + model.options.all, + (option3) => { + let _pipe = lowercase(option3.label); + return contains_string(_pipe, lowercase(query)); + } + ); + let _block; + let _record = model.options; + _block = new Options4( + _record.all, + filtered, + _record.lookup_label, + _record.lookup_index + ); + let options = _block; + let intent = intent_from_query(query, model.intent_strategy, model.options); + let _block$1; + let _record$1 = model; + _block$1 = new Model5( + _record$1.expanded, + _record$1.value, + _record$1.placeholder, + query, + intent, + _record$1.intent_strategy, + options + ); + let model$1 = _block$1; + let effect = none(); + return [model$1, effect]; + } else if ($ instanceof UserClosedMenu) { + let _block; + let _record = model; + _block = new Model5( + false, + _record.value, + _record.placeholder, + _record.query, + new None(), + _record.intent_strategy, + _record.options + ); + let model$1 = _block; + let effect = remove_pseudo_state2("expanded"); + return [model$1, effect]; + } else if ($ instanceof UserHoveredOption) { + let intent = $[0]; + return [ + (() => { + let _record = model; + return new Model5( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + new Some(intent), + _record.intent_strategy, + _record.options + ); + })(), + none() + ]; + } else if ($ instanceof UserOpenedMenu) { + let _block; + let _record = model; + _block = new Model5( + true, + _record.value, + _record.placeholder, + _record.query, + _record.intent, + _record.intent_strategy, + _record.options + ); + let model$1 = _block; + let effect = set_pseudo_state2("expanded"); + return [model$1, effect]; + } else if ($ instanceof UserPressedDown2) { + let event4 = $.event; + let _block; + let $1 = model.intent; + if ($1 instanceof Some) { + let intent2 = $1[0]; + let _pipe = next( + model.options.lookup_index, + intent2, + (_capture) => { + return add(_capture, 1); + } + ); + let _pipe$1 = or(_pipe, new Ok(intent2)); + _block = from_result(_pipe$1); + } else { + let _pipe = min_inverse(model.options.lookup_index, compare); + _block = from_result(_pipe); + } + let intent = _block; + let _block$1; + let _record = model; + _block$1 = new Model5( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + intent, + _record.intent_strategy, + _record.options + ); + let model$1 = _block$1; + let effect = prevent_default2(event4); + return [model$1, effect]; + } else if ($ instanceof UserPressedEnd2) { + let event4 = $.event; + let _block; + let _pipe = model.options.lookup_index; + let _pipe$1 = max_inverse(_pipe, compare); + _block = from_result(_pipe$1); + let intent = _block; + let _block$1; + let _record = model; + _block$1 = new Model5( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + intent, + _record.intent_strategy, + _record.options + ); + let model$1 = _block$1; + let effect = prevent_default2(event4); + return [model$1, effect]; + } else if ($ instanceof UserPressedEnter) { + let event4 = $.event; + let _block; + let $1 = model.intent; + if ($1 instanceof Some) { + let value$1 = $1[0]; + _block = batch( + toList([ + emit( + "change", + object2(toList([["value", string3(value$1)]])) + ), + prevent_default2(event4) + ]) + ); + } else { + _block = prevent_default2(event4); + } + let effect = _block; + return [model, effect]; + } else if ($ instanceof UserPressedEscape) { + let event4 = $.event; + let trigger = $.trigger; + let _block; + let _record = model; + _block = new Model5( + false, + _record.value, + _record.placeholder, + _record.query, + new None(), + _record.intent_strategy, + _record.options + ); + let model$1 = _block; + let effect = batch( + toList([ + remove_pseudo_state2("expanded"), + prevent_default2(event4), + after_paint((_, _1) => { + return focus3(trigger); + }) + ]) + ); + return [model$1, effect]; + } else if ($ instanceof UserPressedHome2) { + let event4 = $.event; + let _block; + let _pipe = model.options.lookup_index; + let _pipe$1 = min_inverse(_pipe, compare); + _block = from_result(_pipe$1); + let intent = _block; + let _block$1; + let _record = model; + _block$1 = new Model5( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + intent, + _record.intent_strategy, + _record.options + ); + let model$1 = _block$1; + let effect = prevent_default2(event4); + return [model$1, effect]; + } else if ($ instanceof UserPressedUp2) { + let event4 = $.event; + let _block; + let $1 = model.intent; + if ($1 instanceof Some) { + let intent2 = $1[0]; + let _pipe = next( + model.options.lookup_index, + intent2, + (_capture) => { + return subtract(_capture, 1); + } + ); + let _pipe$1 = or(_pipe, new Ok(intent2)); + _block = from_result(_pipe$1); + } else { + let _pipe = max_inverse(model.options.lookup_index, compare); + _block = from_result(_pipe); + } + let intent = _block; + let _block$1; + let _record = model; + _block$1 = new Model5( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + intent, + _record.intent_strategy, + _record.options + ); + let model$1 = _block$1; + let effect = prevent_default2(event4); + return [model$1, effect]; + } else { + let value$1 = $[0]; + let _block; + let _pipe = value$1; + let _pipe$1 = ((_capture) => { + return get4(model.options.lookup_label, _capture); + })(_pipe); + _block = from_result(_pipe$1); + let intent = _block; + let _block$1; + let _record = model; + _block$1 = new Model5( + _record.expanded, + _record.value, + _record.placeholder, + _record.query, + intent, + _record.intent_strategy, + _record.options + ); + let model$1 = _block$1; + let effect = emit( + "change", + object2(toList([["value", string3(value$1)]])) + ); + return [model$1, effect]; + } +} +function handle_slot_change3() { + return field( + "target", + assigned_elements2( + field( + "tagName", + string2, + (tag) => { + return then$( + attribute4("value"), + (value3) => { + return field( + "textContent", + string2, + (label2) => { + return success([tag, value3, label2]); + } + ); + } + ); + } + ), + true + ), + (options) => { + let _pipe = options; + let _pipe$1 = fold_right( + _pipe, + [toList([]), new$()], + (acc, option3) => { + let tag = option3[0]; + let value$1 = option3[1]; + let label2 = option3[2]; + return guard( + tag !== "LUSTRE-UI-COMBOBOX-OPTION", + acc, + () => { + return guard( + contains(acc[1], value$1), + acc, + () => { + let seen = insert2(acc[1], value$1); + let options$1 = prepend( + new Item2(value$1, label2, toList([])), + acc[0] + ); + return [options$1, seen]; + } + ); + } + ); + } + ); + let _pipe$2 = first2(_pipe$1); + let _pipe$3 = new ParentChangedChildren2(_pipe$2); + return success(_pipe$3); + } + ); +} +function handle_popover_click(will_open) { + return field( + "currentTarget", + child("input", nil(), dynamic), + (input3) => { + if (will_open) { + return success(new UserActivatedPopoverTrigger(input3)); + } else { + return failure(new UserActivatedPopoverTrigger(input3), ""); + } + } + ); +} +function handle_popover_keydown(will_open) { + return field( + "key", + string2, + (key) => { + return field( + "currentTarget", + child("input", nil(), dynamic), + (input3) => { + if (key === "Enter") { + if (will_open) { + return success(new UserActivatedPopoverTrigger(input3)); + } else { + return failure(new UserActivatedPopoverTrigger(input3), ""); + } + } else if (key === " ") { + if (will_open) { + return success(new UserActivatedPopoverTrigger(input3)); + } else { + return failure(new UserActivatedPopoverTrigger(input3), ""); + } + } else { + return failure(new UserActivatedPopoverTrigger(input3), ""); + } + } + ); + } + ); +} +function view_trigger3(value3, placeholder2, options) { + let _block; + let _pipe = value3; + let _pipe$1 = ((_capture) => { + return get4(options.lookup_label, _capture); + })(_pipe); + _block = unwrap2(_pipe$1, placeholder2); + let label2 = _block; + return button( + toList([ + attribute2("part", "combobox-trigger"), + attribute2("tabindex", "0"), + on_focus(new DomFocusedTrigger()), + on_blur(new DomBlurredTrigger()) + ]), + toList([ + span( + toList([ + attribute2("part", "combobox-trigger-label"), + class$( + (() => { + if (label2 === "") { + return "empty"; + } else { + return ""; + } + })() + ) + ]), + toList([text3(label2)]) + ), + chevron_down(toList([attribute2("part", "combobox-trigger-icon")])) + ]) + ); +} +function handle_input_keydown() { + return then$( + dynamic, + (event4) => { + return field( + "key", + string2, + (key) => { + if (key === "ArrowDown") { + return success(new UserPressedDown2(event4)); + } else if (key === "ArrowEnd") { + return success(new UserPressedEnd2(event4)); + } else if (key === "Enter") { + return success(new UserPressedEnter(event4)); + } else if (key === "Escape") { + return field( + "target", + child("button", nil(), dynamic), + (trigger) => { + return success(new UserPressedEscape(event4, trigger)); + } + ); + } else if (key === "Home") { + return success(new UserPressedHome2(event4)); + } else if (key === "ArrowUp") { + return success(new UserPressedUp2(event4)); + } else if (key === "Tab") { + return success(new UserClosedMenu()); + } else { + return failure(new UserClosedMenu(), ""); + } + } + ); + } + ); +} +function view_input(query) { + return container( + toList([attribute2("part", "combobox-input")]), + toList([ + magnifying_glass(toList([])), + element11( + toList([ + styles( + toList([ + ["width", "100%"], + ["border-bottom-left-radius", "0px"], + ["border-bottom-right-radius", "0px"] + ]) + ), + autocomplete("off"), + on_input((var0) => { + return new UserChangedQuery(var0); + }), + on("keydown", handle_input_keydown()), + value(query) + ]) + ) + ]) + ); +} +var name5 = "lustre-ui-combobox"; +function element13(attributes, children) { + return element3( + name5, + attributes, + flat_map( + children, + (item3) => { + let option$1 = element2( + "lustre-ui-combobox-option", + toList([value(item3.value)]), + toList([text3(item3.label)]) + ); + return guard( + is_empty(item3.content), + toList([[item3.value, option$1]]), + () => { + return toList([ + [item3.value, option$1], + [ + "option-" + item3.value, + div( + toList([attribute2("slot", "option-" + item3.value)]), + item3.content + ) + ] + ]); + } + ); + } + ) + ); +} +function view_option(option3, value3, intent, last) { + let is_selected = option3.value === value3; + let is_intent = isEqual(new Some(option3.value), intent); + let _block; + if (is_selected) { + _block = check2; + } else { + _block = (_capture) => { + return span(_capture, toList([])); + }; + } + let icon2 = _block; + let parts = toList([ + "combobox-option", + (() => { + if (is_intent) { + return "intent"; + } else { + return ""; + } + })(), + (() => { + if (last) { + return "last"; + } else { + return ""; + } + })() + ]); + return li( + toList([ + attribute2("part", join(parts, " ")), + attribute2("value", option3.value), + on_mouse_over(new UserHoveredOption(option3.value)), + on_mouse_down(new UserSelectedOption(option3.value)) + ]), + toList([ + icon2( + toList([ + styles(toList([["height", "1rem"], ["width", "1rem"]])) + ]) + ), + span( + toList([style("flex", "1 1 0%")]), + toList([ + element2( + "slot", + toList([name("option-" + option3.value)]), + toList([text3(option3.label)]) + ) + ]) + ) + ]) + ); +} +function do_view_options(options, value3, intent) { + if (options instanceof Empty) { + return toList([]); + } else { + let $ = options.tail; + if ($ instanceof Empty) { + let option$1 = options.head; + return toList([ + [option$1.value, view_option(option$1, value3, intent, true)] + ]); + } else { + let option$1 = options.head; + let rest = $; + return prepend( + [option$1.label, view_option(option$1, value3, intent, false)], + do_view_options(rest, value3, intent) + ); + } + } +} +function view_options(options, value3, intent) { + return ul2(toList([]), do_view_options(options.filtered, value3, intent)); +} +function view7(model) { + return fragment2( + toList([ + slot( + toList([ + style("display", "none"), + on("slotchange", handle_slot_change3()) + ]), + toList([]) + ), + element12( + toList([ + anchor(new BottomMiddle()), + equal_width(), + gap("var(--padding-y)"), + on_close(new UserClosedMenu()), + on_open(new UserOpenedMenu()), + open(model.expanded), + on("click", handle_popover_click(!model.expanded)), + on("keydown", handle_popover_keydown(!model.expanded)) + ]), + view_trigger3(model.value, model.placeholder, model.options), + div( + toList([attribute2("part", "combobox-options")]), + toList([ + view_input(model.query), + view_options(model.options, model.value, model.intent) + ]) + ) + ) + ]) + ); +} +function register5() { + let $ = register4(); + if ($ instanceof Ok) { + let app = component( + init7, + update6, + view7, + toList([ + adopt_styles(false), + on_attribute_change( + "value", + (value3) => { + return new Ok(new ParentSetValue(value3)); + } + ), + on_attribute_change( + "placeholder", + (value3) => { + return new Ok(new ParentSetPlaceholder(value3)); + } + ), + on_attribute_change( + "strategy", + (value3) => { + if (value3 === "by-length") { + return new Ok(new ParentSetStrategy(new ByLength())); + } else if (value3 === "by-index") { + return new Ok(new ParentSetStrategy(new ByIndex())); + } else { + return new Error(void 0); + } + } + ) + ]) + ); + return make_component(app, name5); + } else { + let $1 = $[0]; + if ($1 instanceof ComponentAlreadyRegistered) { + let app = component( + init7, + update6, + view7, + toList([ + adopt_styles(false), + on_attribute_change( + "value", + (value3) => { + return new Ok(new ParentSetValue(value3)); + } + ), + on_attribute_change( + "placeholder", + (value3) => { + return new Ok(new ParentSetPlaceholder(value3)); + } + ), + on_attribute_change( + "strategy", + (value3) => { + if (value3 === "by-length") { + return new Ok(new ParentSetStrategy(new ByLength())); + } else if (value3 === "by-index") { + return new Ok(new ParentSetStrategy(new ByIndex())); + } else { + return new Error(void 0); + } + } + ) + ]) + ); + return make_component(app, name5); + } else { + let error = $; + return error; + } + } +} +function echo3(value3, file, line) { + const grey = "\x1B[90m"; + const reset_color = "\x1B[39m"; + const file_line = `${file}:${line}`; + const string_value = echo$inspect3(value3); + if (globalThis.process?.stderr?.write) { + const string5 = `${grey}${file_line}${reset_color} +${string_value} +`; + process.stderr.write(string5); + } else if (globalThis.Deno) { + const string5 = `${grey}${file_line}${reset_color} +${string_value} +`; + globalThis.Deno.stderr.writeSync(new TextEncoder().encode(string5)); + } else { + const string5 = `${file_line} +${string_value}`; + globalThis.console.log(string5); + } + return value3; +} +function echo$inspectString3(str) { + let new_str = '"'; + for (let i = 0; i < str.length; i++) { + let char = str[i]; + if (char == "\n") new_str += "\\n"; + else if (char == "\r") new_str += "\\r"; + else if (char == " ") new_str += "\\t"; + else if (char == "\f") new_str += "\\f"; + else if (char == "\\") new_str += "\\\\"; + else if (char == '"') new_str += '\\"'; + else if (char < " " || char > "~" && char < "\xA0") { + new_str += "\\u{" + char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0") + "}"; + } else { + new_str += char; + } + } + new_str += '"'; + return new_str; +} +function echo$inspectDict3(map6) { + let body = "dict.from_list(["; + let first3 = true; + let key_value_pairs = []; + map6.forEach((value3, key) => { + key_value_pairs.push([key, value3]); + }); + key_value_pairs.sort(); + key_value_pairs.forEach(([key, value3]) => { + if (!first3) body = body + ", "; + body = body + "#(" + echo$inspect3(key) + ", " + echo$inspect3(value3) + ")"; + first3 = false; + }); + return body + "])"; +} +function echo$inspectCustomType3(record) { + const props = globalThis.Object.keys(record).map((label2) => { + const value3 = echo$inspect3(record[label2]); + return isNaN(parseInt(label2)) ? `${label2}: ${value3}` : value3; + }).join(", "); + return props ? `${record.constructor.name}(${props})` : record.constructor.name; +} +function echo$inspectObject3(v) { + const name6 = Object.getPrototypeOf(v)?.constructor?.name || "Object"; + const props = []; + for (const k of Object.keys(v)) { + props.push(`${echo$inspect3(k)}: ${echo$inspect3(v[k])}`); + } + const body = props.length ? " " + props.join(", ") + " " : ""; + const head = name6 === "Object" ? "" : name6 + " "; + return `//js(${head}{${body}})`; +} +function echo$inspect3(v) { + const t = typeof v; + if (v === true) return "True"; + if (v === false) return "False"; + if (v === null) return "//js(null)"; + if (v === void 0) return "Nil"; + if (t === "string") return echo$inspectString3(v); + if (t === "bigint" || t === "number") return v.toString(); + if (globalThis.Array.isArray(v)) + return `#(${v.map(echo$inspect3).join(", ")})`; + if (v instanceof List) + return `[${v.toArray().map(echo$inspect3).join(", ")}]`; + if (v instanceof UtfCodepoint) + return `//utfcodepoint(${String.fromCodePoint(v.value)})`; + if (v instanceof BitArray) return echo$inspectBitArray3(v); + if (v instanceof CustomType) return echo$inspectCustomType3(v); + if (echo$isDict3(v)) return echo$inspectDict3(v); + if (v instanceof Set) + return `//js(Set(${[...v].map(echo$inspect3).join(", ")}))`; + if (v instanceof RegExp) return `//js(${v})`; + if (v instanceof Date) return `//js(Date("${v.toISOString()}"))`; + if (v instanceof Function) { + const args = []; + for (const i of Array(v.length).keys()) + args.push(String.fromCharCode(i + 97)); + return `//fn(${args.join(", ")}) { ... }`; + } + return echo$inspectObject3(v); +} +function echo$inspectBitArray3(bitArray) { + let endOfAlignedBytes = bitArray.bitOffset + 8 * Math.trunc(bitArray.bitSize / 8); + let alignedBytes = bitArraySlice( + bitArray, + bitArray.bitOffset, + endOfAlignedBytes + ); + let remainingUnalignedBits = bitArray.bitSize % 8; + if (remainingUnalignedBits > 0) { + let remainingBits = bitArraySliceToInt( + bitArray, + endOfAlignedBytes, + bitArray.bitSize, + false, + false + ); + let alignedBytesArray = Array.from(alignedBytes.rawBuffer); + let suffix = `${remainingBits}:size(${remainingUnalignedBits})`; + if (alignedBytesArray.length === 0) { + return `<<${suffix}>>`; + } else { + return `<<${Array.from(alignedBytes.rawBuffer).join(", ")}, ${suffix}>>`; + } + } else { + return `<<${Array.from(alignedBytes.rawBuffer).join(", ")}>>`; + } +} +function echo$isDict3(value3) { + try { + return value3 instanceof Dict; + } catch { + return false; + } +} + +// build/dev/javascript/lustre_ui/lustre/ui/combobox_stories.mjs +var FILEPATH6 = "dev/lustre/ui/combobox_stories.gleam"; +function basic_story() { + return story2( + "Typeahead filter", + () => { + return input2( + "Value", + "gleam", + (value3) => { + return scene( + (controls) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return element13( + toList([ + value2(get3(controls, value3)), + on_change3( + (_capture) => { + return set(value3, _capture); + } + ) + ]), + toList([ + option2("gleam", "Gleam"), + option2("go", "Go"), + option2("javascript", "JavaScript"), + option2("kotlin", "Kotlin"), + option2("rust", "Rust"), + option2("typescript", "TypeScript") + ]) + ); + } + ); + } + ); + } + ); + } + ); +} +function fruit_picker_story() { + return story2( + "Fruit Picker", + () => { + return input2( + "Value", + "apple", + (value3) => { + return scene( + (controls) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return element13( + toList([ + value2(get3(controls, value3)), + on_change3( + (_capture) => { + return set(value3, _capture); + } + ) + ]), + toList([ + option2("apple", "Apple"), + option2("banana", "Banana"), + option2("orange", "Orange") + ]) + ); + } + ); + } + ); + } + ); + } + ); +} +function all8() { + let $ = register5(); + if (!($ instanceof Ok)) { + throw makeError( + "let_assert", + FILEPATH6, + "lustre/ui/combobox_stories", + 10, + "all", + "Pattern match failed, no pattern matched the value.", + { value: $, start: 255, end: 293, pattern_start: 266, pattern_end: 271 } + ); + } + return chapter( + "Combobox", + toList([basic_story(), fruit_picker_story()]) + ); +} + +// build/dev/javascript/lustre_ui/lustre/ui/divider.mjs +function element14(attributes, children) { + if (children instanceof Empty) { + return hr( + prepend(class$("lustre-ui-divider"), attributes) + ); + } else { + return div( + prepend(class$("lustre-ui-divider"), attributes), + children + ); + } +} + +// build/dev/javascript/lustre_ui/lustre/ui/divider_stories.mjs +function basic_divider_story() { + return story2( + "Basic Divider with No Content", + () => { + return scene( + (_) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return div( + toList([]), + toList([ + p( + toList([]), + toList([ + text3( + "This is some content before the divider. The divider below has no content and serves as a simple horizontal rule." + ) + ]) + ), + element14(toList([]), toList([])), + p( + toList([]), + toList([ + text3( + "This is some content after the divider. Notice how the divider creates a clear separation between content sections." + ) + ]) + ) + ]) + ); + } + ); + } + ); + } + ); +} +function text_label_divider_story() { + return story2( + "Divider with Text Label", + () => { + return scene( + (_) => { + return inject( + (() => { + let _pipe = default$(); + return with_scope(_pipe, new Host()); + })(), + () => { + return div( + toList([]), + toList([ + p( + toList([]), + toList([text3("Sign in with your email and password")]) + ), + div( + toList([]), + toList([ + p( + toList([]), + toList([text3("(Form fields placeholder)")]) + ) + ]) + ), + element14(toList([]), toList([text3("OR")])), + p( + toList([]), + toList([text3("Continue with social login")]) + ), + div( + toList([]), + toList([ + p( + toList([]), + toList([ + text3("(Social login buttons placeholder)") + ]) + ) + ]) + ) + ]) + ); + } + ); + } + ); + } + ); +} +function all9() { + return chapter( + "Divider", + toList([basic_divider_story(), text_label_divider_story()]) + ); +} + +// build/dev/javascript/lustre_ui/lustre_ui_storybook.mjs +function main2() { + let book2 = book( + "Lustre UI", + toList([ + all2(), + all3(), + all4(), + all5(), + all6(), + all7(), + all8(), + all9(), + external_stylesheet("/priv/static/lustre_ui.css") + ]) + ); + return start5(book2); +} + +// build/.lustre/entry.mjs +main2(); diff --git a/src/dom.ffi.mjs b/src/dom.ffi.mjs deleted file mode 100644 index 098d074..0000000 --- a/src/dom.ffi.mjs +++ /dev/null @@ -1,175 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import { List, Ok, Error } from "../prelude.mjs"; -import { - ComponentAlreadyRegistered, - NotABrowser, - is_browser, -} from "../lustre/lustre.mjs"; - -// TYPES ----------------------------------------------------------------------- - -/** - * @template A - * @typedef {import("../build/dev/javascript/prelude.mjs").List} List - */ - -/** - * @template A, E - * @typedef {import("../build/dev/javascript/prelude.mjs").Result} Result - */ - -// QUERIES --------------------------------------------------------------------- - -/** - * Access the assigned elements of a Web Component's slot. - * - * @param {HTMLSlotElement} slot - * - * @returns {Result, List>} A decoded list of elements. If - * the slot is not an instance of `HTMLSlotElement`, an empty list of decode - * errors is returned. - */ -export const assigned_elements = (slot) => { - if (slot instanceof HTMLSlotElement) { - return new Ok(List.fromArray(slot.assignedElements())); - } - - return new Error(List.fromArray([])); -}; - -/** - * - */ -export const animation_time = () => { - return window.document.timeline.currentTime; -}; - -/** - * @param {string} name - * - * @returns {(element: HTMLElement) => Result>} - */ -export const get_attribute = (name) => (element) => { - if (!(element instanceof HTMLElement)) { - return new Error(List.fromArray([])); - } - - if (element.hasAttribute(name)) { - return new Ok(element.getAttribute(name)); - } else { - return new Error(List.fromArray([])); - } -}; - -/** - * @param {string} name - * - * @returns {(element: HTMLElement) => Result>} - */ -export const get_element = (selector) => (element) => { - if (!("querySelector" in element)) { - return new Error(List.fromArray([])); - } - - const result = element.querySelector(selector); - - if (result) { - return new Ok(result); - } else { - return new Error(List.fromArray([])); - } -}; - -/** - * @param {HTMLElement} element - * - * @returns {Result>} - */ -export const get_root = (element) => { - if (!(element instanceof HTMLElement)) { - return new Error(List.fromArray([])); - } - - return new Ok(element.getRootNode()); -}; - -// EFFECTS --------------------------------------------------------------------- - -/** - * @param {HTMLElement} element - */ -export const focus = (element) => { - element.focus(); -}; - -export const set_state = (value, shadow_root) => { - if (!(shadow_root instanceof ShadowRoot)) return; - if (!(shadow_root.host.internals instanceof ElementInternals)) return; - - shadow_root.host.internals.states.add(value); -}; - -export const remove_state = (value, shadow_root) => { - if (!(shadow_root instanceof ShadowRoot)) return; - if (!(shadow_root.host.internals instanceof ElementInternals)) return; - - shadow_root.host.internals.states.delete(value); -}; - -// COMPONENTS ------------------------------------------------------------------ - -export const register_intersection_observer = () => { - if (!is_browser()) return new Error(new NotABrowser()); - if (window.customElements.get("lustre-ui-intersection-observer")) { - return new Error( - new ComponentAlreadyRegistered("lustre-ui-intersection-observer"), - ); - } - - customElements.define( - "lustre-ui-intersection-observer", - class LustreUiIntersectionObserver extends HTMLElement { - constructor() { - super(); - this.observer = null; - } - - connectedCallback() { - const options = { - root: null, - rootMargin: "0px", - threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1], - }; - - this.style.display = "contents"; - this.observer = new IntersectionObserver((entries) => { - for (const entry of entries) { - const intersectionEvent = new CustomEvent("intersection", { - detail: { - target: entry.target, - isIntersecting: entry.isIntersecting, - intersectionRatio: entry.intersectionRatio, - }, - bubbles: true, - composed: true, - }); - - this.dispatchEvent(intersectionEvent); - } - }, options); - - // Observe self - this.observer.observe(this); - } - - disconnectedCallback() { - if (this.observer) { - this.observer.disconnect(); - } - } - }, - ); - - return new Ok(undefined); -}; diff --git a/src/lustre/ffi/dom.ffi.mjs b/src/lustre/ffi/dom.ffi.mjs new file mode 100644 index 0000000..461e92b --- /dev/null +++ b/src/lustre/ffi/dom.ffi.mjs @@ -0,0 +1,72 @@ +// IMPORTS --------------------------------------------------------------------- + +import { List, Ok, Error } from "../../../prelude.mjs"; +import { BoundingClientRect } from "./dom.mjs"; + +// DECODERS -------------------------------------------------------------------- + +export const assigned_elements = (slot) => { + if (!(slot instanceof HTMLSlotElement)) return new Error(undefined); + + const elements = slot.assignedElements(); + + return new Ok(List.fromArray(elements)); +}; + +export const bounding_client_rect = (element) => { + if (!(element instanceof HTMLElement)) return new Error(undefined); + + const rect = element.getBoundingClientRect(); + + return new Ok( + new BoundingClientRect( + rect.top, + rect.right, + rect.bottom, + rect.left, + rect.width, + rect.height, + ), + ); +}; + +export const attribute = (element, name) => { + if (!(element instanceof HTMLElement)) return new Error(undefined); + if (typeof name !== "string") return new Error(undefined); + + const value = element.getAttribute(name); + + if (value === null) { + return new Error(undefined); + } else { + return new Ok(value); + } +}; + +// EFFECTS --------------------------------------------------------------------- + +export const prevent_default = (event) => { + if (!(event instanceof Event)) return; + + event.preventDefault(); +}; + +export const find_element = (selector, root = document) => { + if (typeof selector !== "string") return new Error(undefined); + if (!(root instanceof Document || root instanceof Element)) + return new Error(undefined); + + const element = root.querySelector(selector); + + if (element === null) { + return new Error(undefined); + } else { + return new Ok(element); + } +}; + +export const focus = (element) => { + if (!(element instanceof HTMLElement)) return; + + element.focus(); +}; diff --git a/src/lustre/ffi/dom.gleam b/src/lustre/ffi/dom.gleam new file mode 100644 index 0000000..ecb60ba --- /dev/null +++ b/src/lustre/ffi/dom.gleam @@ -0,0 +1,138 @@ +// IMPORTS --------------------------------------------------------------------- + +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode.{type Decoder} +import gleam/function +import gleam/list +import gleam/result +import lustre/effect.{type Effect} + +// TYPES ----------------------------------------------------------------------- + +pub type BoundingClientRect { + BoundingClientRect( + top: Float, + right: Float, + bottom: Float, + left: Float, + width: Float, + height: Float, + ) +} + +// DECODERS -------------------------------------------------------------------- + +/// Decode the assigned elements of a `HTMLSlotElement` using the provided +/// decoder. The `lenient` flag determines whether this should decoder should fail +/// if one of the assigned elements fails to decode. When `lenient` is `True` +/// it will continue decoding the remaining elements and return a list of only +/// those that successfully decoded. When `lenient` is `False` this decoder will +/// fail if any of the assigned elements fail to decode. +/// +pub fn assigned_elements( + decoder: Decoder(a), + lenient lenient: Bool, +) -> Decoder(List(a)) { + let lenient_decoder = + decode.one_of(decode.map(decoder, Ok), [decode.success(Error(Nil))]) + + use slot <- decode.new_primitive_decoder("HTMLSlotElement.assignedElements()") + + case do_assigned_elements(slot) { + Ok(elements) if lenient -> + elements + |> list.try_map(decode.run(_, lenient_decoder)) + |> result.map(list.filter_map(_, function.identity)) + |> result.replace_error([]) + + Ok(elements) -> + elements + |> list.try_map(decode.run(_, decoder)) + |> result.replace_error([]) + + Error(_) -> Error([]) + } +} + +@external(javascript, "./dom.ffi.mjs", "assigned_elements") +fn do_assigned_elements(element: Dynamic) -> Result(List(Dynamic), Nil) + +/// Decode the `BoundingClientRect` of an element. This gives us its dimensions +/// and position relative to the viewport. +/// +pub fn bounding_client_rect() -> Decoder(BoundingClientRect) { + use element <- decode.new_primitive_decoder("Element.getBoundingClientRect()") + + case do_bounding_client_rect(element) { + Ok(rect) -> Ok(rect) + Error(_) -> Error(BoundingClientRect(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)) + } +} + +@external(javascript, "./dom.ffi.mjs", "bounding_client_rect") +fn do_bounding_client_rect(element: Dynamic) -> Result(BoundingClientRect, Nil) + +/// Decode an attribute of an element by its name. +/// +/// ```gleam +/// import lustre/event +/// import lustre/ui/ffi/dom +/// +/// fn handle_click() { +/// event.on("click", { +/// use id <- decode.field("target", dom.attribute("id")) +/// ... +/// }) +/// } +/// ``` +/// +pub fn attribute(name: String) -> Decoder(String) { + use element <- decode.new_primitive_decoder("Element.getAttribute()") + + case do_attribute(element, name) { + Ok(value) -> Ok(value) + Error(_) -> Error("") + } +} + +@external(javascript, "./dom.ffi.mjs", "attribute") +fn do_attribute(element: Dynamic, name: String) -> Result(String, Nil) + +/// Decode the first descendant of an element that matches the given CSS query +/// selector. If no matching element is found, this decoder will fail and the +/// provided `zero` value will be passed +/// +pub fn child(selector: String, zero: a, decoder: Decoder(a)) -> Decoder(a) { + use element <- decode.new_primitive_decoder("Element.querySelector()") + + case do_find_element(selector, element) { + Ok(child) -> decode.run(child, decoder) |> result.replace_error(zero) + Error(_) -> Error(zero) + } +} + +// EFFECTS --------------------------------------------------------------------- + +pub fn prevent_default(event: Dynamic) -> Effect(msg) { + use _ <- effect.from + + do_prevent_default(event) +} + +@external(javascript, "./dom.ffi.mjs", "prevent_default") +pub fn do_prevent_default(event: Dynamic) -> Nil + +@external(javascript, "./dom.ffi.mjs", "find_element") +pub fn do_find_element(selector: String, root: Dynamic) -> Result(Dynamic, Nil) + +pub fn focus(selector: String) -> Effect(msg) { + use _, root <- effect.before_paint + + case do_find_element(selector, root) { + Ok(element) -> do_focus(element) + Error(_) -> Nil + } +} + +@external(javascript, "./dom.ffi.mjs", "focus") +pub fn do_focus(element: Dynamic) -> Nil diff --git a/src/lustre/ui/accordion.gleam b/src/lustre/ui/accordion.gleam index fa7c621..3a73880 100644 --- a/src/lustre/ui/accordion.gleam +++ b/src/lustre/ui/accordion.gleam @@ -87,17 +87,29 @@ //// //// - [`--border`](#border) //// - [`--border-focus`](#border_focus) +//// - [`--border-width`](#border_focus) //// - [`--padding-x`](#padding_x) //// - [`--padding-y`](#padding_y) //// - [`--radius`](#radius) //// - [`--text`](#text) //// +//// ## Accessibility +//// +//// Accordions created with this element have a robust set of keyboard commands: +//// +//// - ArrowDown will focus the next trigger in the accordion +//// - ArrowUp will focus the previous trigger in the accordion +//// - End will focus the last trigger in the accordion +//// - Enter or Space will toggle the expanded state +//// - Home will focus the first trigger in the accordion +//// - Tab will move focus to the next focusable element +//// // IMPORTS --------------------------------------------------------------------- import gleam/bool -import gleam/dict.{type Dict} -import gleam/dynamic.{type DecodeError, type Decoder, type Dynamic, dynamic} +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode.{type Decoder} import gleam/int import gleam/json import gleam/list @@ -106,10 +118,13 @@ import gleam/result import gleam/set.{type Set} import lustre import lustre/attribute.{type Attribute, attribute} +import lustre/component import lustre/effect.{type Effect} import lustre/element.{type Element} import lustre/element/html +import lustre/element/keyed import lustre/event +import lustre/ffi/dom import lustre/ui/data/bidict.{type Bidict} import lustre/ui/primitives/collapse import lustre/ui/primitives/icon @@ -168,9 +183,21 @@ pub const name: String = "lustre-ui-accordion" pub fn register() -> Result(Nil, lustre.Error) { case collapse.register() { Ok(Nil) | Error(lustre.ComponentAlreadyRegistered(_)) -> { - let app = lustre.component(init, update, view, on_attribute_change()) + let app = + lustre.component(init, update, view, [ + component.on_attribute_change("mode", fn(value) { + case value { + "at-most-one" -> Ok(ParentSetMode(AtMostOne)) + "exactly-one" -> Ok(ParentSetMode(ExactlyOne)) + "multi" -> Ok(ParentSetMode(Multi)) + _ -> Error(Nil) + } + }), + ]) + lustre.register(app, name) } + error -> error } } @@ -206,7 +233,7 @@ pub fn element( attributes: List(Attribute(msg)), children: List(Item(msg)), ) -> Element(msg) { - element.keyed(element.element(name, attributes, _), { + keyed.element(name, attributes, { use Item(value, label, content) <- list.flat_map(children) use <- bool.guard(value == "", []) @@ -274,49 +301,49 @@ pub fn multi() -> Attribute(msg) { /// /// pub fn padding(x x: String, y y: String) -> Attribute(msg) { - attribute.style([#("--padding-x", x), #("--padding-y", y)]) + attribute.styles([#("--padding-x", x), #("--padding-y", y)]) } /// /// /// pub fn padding_x(value: String) -> Attribute(msg) { - attribute.style([#("--padding-x", value)]) + attribute.style("--padding-x", value) } /// /// /// pub fn padding_y(value: String) -> Attribute(msg) { - attribute.style([#("--padding-y", value)]) + attribute.style("--padding-y", value) } /// /// /// pub fn border(value: String) -> Attribute(msg) { - attribute.style([#("--border", value)]) + attribute.style("--border", value) } /// /// /// pub fn border_focus(value: String) -> Attribute(msg) { - attribute.style([#("--border-focus", value)]) + attribute.style("--border-focus", value) } /// /// /// pub fn border_width(value: String) -> Attribute(msg) { - attribute.style([#("--border-width", value)]) + attribute.style("--border-width", value) } /// /// /// pub fn text(value: String) -> Attribute(msg) { - attribute.style([#("--text", value)]) + attribute.style("--text", value) } // MODEL ----------------------------------------------------------------------- @@ -353,10 +380,10 @@ fn init(_) -> #(Model, Effect(Msg)) { type Msg { ParentChangedChildren(List(#(String, String))) ParentSetMode(Mode) - UserPressedDown(String) + UserPressedDown(String, event: Dynamic) UserPressedEnd UserPressedHome - UserPressedUp(String) + UserPressedUp(String, event: Dynamic) UserToggledItem(String) } @@ -416,7 +443,7 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { #(model, effect) } - UserPressedDown(key) -> { + UserPressedDown(key, event:) -> { let effect = { use index <- result.try(bidict.get(model.options.lookup_index, key)) use next <- result.map(bidict.get_inverse( @@ -427,7 +454,13 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { focus_trigger(next) } - #(model, effect |> result.unwrap(effect.none())) + #( + model, + effect.batch([ + effect |> result.unwrap(effect.none()), + dom.prevent_default(event), + ]), + ) } UserPressedEnd -> { @@ -456,7 +489,7 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { #(model, effect |> result.unwrap(effect.none())) } - UserPressedUp(key) -> { + UserPressedUp(key, event:) -> { let effect = { use index <- result.try(bidict.get(model.options.lookup_index, key)) use prev <- result.map(bidict.get_inverse( @@ -467,7 +500,13 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { focus_trigger(prev) } - #(model, effect |> result.unwrap(effect.none())) + #( + model, + effect.batch([ + effect |> result.unwrap(effect.none()), + dom.prevent_default(event), + ]), + ) } UserToggledItem(value) -> { @@ -491,52 +530,26 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { } } -fn on_attribute_change() -> Dict(String, Decoder(Msg)) { - dict.from_list([ - #("mode", fn(value) { - dynamic.string(value) - |> result.then(fn(mode) { - case mode { - "at-most-one" -> Ok(AtMostOne) - "exactly-one" -> Ok(ExactlyOne) - "multi" -> Ok(Multi) - _ -> Error([]) - } - }) - |> result.unwrap(AtMostOne) - |> ParentSetMode - |> Ok - }), - ]) -} - // EFFECTS --------------------------------------------------------------------- fn focus_trigger(key: String) -> Effect(msg) { - use _, root <- element.get_root() - let selector = "[data-lustre-key=" <> key <> "] button[part=trigger]" + let selector = "[data-lustre-key=" <> key <> "] [part=accordion-trigger]" - case get_element(selector)(root) { - Ok(trigger) -> focus(trigger) - Error(_) -> Nil - } + dom.focus(selector) } -@external(javascript, "../../dom.ffi.mjs", "get_element") -fn get_element(selector: String) -> Decoder(Dynamic) - -@external(javascript, "../../dom.ffi.mjs", "focus") -fn focus(element: Dynamic) -> Nil - // VIEW ------------------------------------------------------------------------ fn view(model: Model) -> Element(Msg) { element.fragment([ - html.slot([ - attribute.style([#("display", "none")]), - event.on("slotchange", handle_slot_change), - ]), - element.keyed(element.fragment, { + html.slot( + [ + attribute.style("display", "none"), + event.on("slotchange", handle_slot_change()), + ], + [], + ), + keyed.fragment({ use #(key, label) <- list.map(model.options.all) let is_expanded = set.contains(model.expanded, key) let item = @@ -549,7 +562,7 @@ fn view(model: Model) -> Element(Msg) { [ attribute("part", "accordion-trigger"), attribute("tabindex", "0"), - event.on("keydown", handle_keydown(key, _)), + event.on("keydown", handle_keydown(key)), ], [ html.p([attribute("part", "accordion-trigger-label")], [ @@ -563,10 +576,10 @@ fn view(model: Model) -> Element(Msg) { ]), ], ), - content: html.slot([ - attribute("part", "accordion-content"), - attribute.name(key), - ]), + content: html.slot( + [attribute("part", "accordion-content"), attribute.name(key)], + [], + ), ) #(key, item) @@ -574,18 +587,19 @@ fn view(model: Model) -> Element(Msg) { ]) } -fn handle_slot_change(event: Dynamic) -> Result(Msg, List(DecodeError)) { - use children <- result.try(dynamic.field("target", assigned_elements)(event)) - use options <- result.try( - dynamic.list(fn(el) { - dynamic.decode3( - fn(name, value, label) { #(name, value, label) }, - dynamic.field("tagName", dynamic.string), - get_attribute("value"), - dynamic.field("textContent", dynamic.string), - )(el) - })(children), - ) +fn handle_slot_change() -> Decoder(Msg) { + use options <- decode.field("target", { + dom.assigned_elements( + { + use tag <- decode.field("tagName", decode.string) + use value <- decode.then(dom.attribute("value")) + use label <- decode.field("textContent", decode.string) + + decode.success(#(tag, value, label)) + }, + lenient: True, + ) + }) options |> list.fold_right(#([], set.new()), fn(acc, option) { @@ -600,32 +614,18 @@ fn handle_slot_change(event: Dynamic) -> Result(Msg, List(DecodeError)) { }) |> pair.first |> ParentChangedChildren - |> Ok + |> decode.success } -@external(javascript, "../../dom.ffi.mjs", "assigned_elements") -fn assigned_elements(_slot: Dynamic) -> Result(Dynamic, List(DecodeError)) - -@external(javascript, "../../dom.ffi.mjs", "get_attribute") -fn get_attribute(name: String) -> Decoder(String) - -fn handle_keydown( - value: String, - event: Dynamic, -) -> Result(Msg, List(DecodeError)) { - use key <- result.try(dynamic.field("key", dynamic.string)(event)) +fn handle_keydown(id: String) -> Decoder(Msg) { + use event <- decode.then(decode.dynamic) + use key <- decode.field("key", decode.string) case key { - "ArrowDown" -> { - event.prevent_default(event) - Ok(UserPressedDown(value)) - } - "ArrowUp" -> { - event.prevent_default(event) - Ok(UserPressedUp(value)) - } - "End" -> Ok(UserPressedEnd) - "Home" -> Ok(UserPressedHome) - _ -> Error([]) + "ArrowDown" -> decode.success(UserPressedDown(id, event:)) + "ArrowUp" -> decode.success(UserPressedUp(id, event:)) + "End" -> decode.success(UserPressedEnd) + "Home" -> decode.success(UserPressedHome) + _ -> decode.failure(UserPressedHome, "") } } diff --git a/src/lustre/ui/alert.gleam b/src/lustre/ui/alert.gleam index 3152f88..15d615d 100644 --- a/src/lustre/ui/alert.gleam +++ b/src/lustre/ui/alert.gleam @@ -52,7 +52,7 @@ //// //// pub fn delete_todo_failed() { //// alert.element([alert.danger()], [ -//// alert.indicator([], [icon.exclamation_triangle([])]), +//// alert.indicator(icon.exclamation_triangle([])), //// alert.title([], [html.text("Could not delete todo")]), //// alert.content([], [ //// html.p([], [html.text("Check your internet connection and try again.")]) @@ -288,7 +288,7 @@ pub fn pill() -> Attribute(msg) { /// /// pub fn background(value: String) -> Attribute(msg) { - attribute.style([#("--background", value)]) + attribute.style("--background", value) } /// By default, the `alert` element uses an appropriate border colour based on @@ -305,7 +305,7 @@ pub fn background(value: String) -> Attribute(msg) { /// /// pub fn border(value: String) -> Attribute(msg) { - attribute.style([#("--border", value)]) + attribute.style("--border", value) } /// By default, the `alert` element sets the size of the [`indicator`](#indicator) @@ -323,7 +323,7 @@ pub fn border(value: String) -> Attribute(msg) { /// /// pub fn indicator_size(value: String) -> Attribute(msg) { - attribute.style([#("--indicator-size", value)]) + attribute.style("--indicator-size", value) } /// By default, the `alert` element sets appropriate horizontal and vertical @@ -343,41 +343,41 @@ pub fn indicator_size(value: String) -> Attribute(msg) { /// /// pub fn padding(x x: String, y y: String) -> Attribute(msg) { - attribute.style([#("--padding-x", x), #("--padding-y", y)]) + attribute.styles([#("--padding-x", x), #("--padding-y", y)]) } /// /// pub fn padding_x(value: String) -> Attribute(msg) { - attribute.style([#("--padding-x", value)]) + attribute.style("--padding-x", value) } /// /// pub fn padding_y(value: String) -> Attribute(msg) { - attribute.style([#("--padding-y", value)]) + attribute.style("--padding-y", value) } /// /// pub fn radius(value: String) -> Attribute(msg) { - attribute.style([#("--radius", value)]) + attribute.style("--radius", value) } /// /// pub fn text(value: String) -> Attribute(msg) { - attribute.style([#("--text", value)]) + attribute.style("--text", value) } /// /// pub fn title_margin(value: String) -> Attribute(msg) { - attribute.style([#("--title-margin", value)]) + attribute.style("--title-margin", value) } /// /// pub fn title_weight(value: String) -> Attribute(msg) { - attribute.style([#("--title-weight", value)]) + attribute.style("--title-weight", value) } diff --git a/src/lustre/ui/badge.gleam b/src/lustre/ui/badge.gleam index aab5102..28bb7b1 100644 --- a/src/lustre/ui/badge.gleam +++ b/src/lustre/ui/badge.gleam @@ -215,7 +215,7 @@ pub fn success() -> Attribute(msg) { /// /// pub fn background(value: String) -> Attribute(msg) { - attribute.style([#("--background", value)]) + attribute.style("--background", value) } /// @@ -223,7 +223,7 @@ pub fn background(value: String) -> Attribute(msg) { /// /// pub fn background_hover(value: String) -> Attribute(msg) { - attribute.style([#("--background-hover", value)]) + attribute.style("--background-hover", value) } /// @@ -231,7 +231,7 @@ pub fn background_hover(value: String) -> Attribute(msg) { /// /// pub fn border(value: String) -> Attribute(msg) { - attribute.style([#("--border", value)]) + attribute.style("--border", value) } /// @@ -239,7 +239,7 @@ pub fn border(value: String) -> Attribute(msg) { /// /// pub fn border_hover(value: String) -> Attribute(msg) { - attribute.style([#("--border-hover", value)]) + attribute.style("--border-hover", value) } /// @@ -247,7 +247,7 @@ pub fn border_hover(value: String) -> Attribute(msg) { /// /// pub fn border_width(value: String) -> Attribute(msg) { - attribute.style([#("--border-width", value)]) + attribute.style("--border-width", value) } /// @@ -255,7 +255,7 @@ pub fn border_width(value: String) -> Attribute(msg) { /// /// pub fn padding(x: String, y: String) -> Attribute(msg) { - attribute.style([#("--padding-x", x), #("--padding-y", y)]) + attribute.styles([#("--padding-x", x), #("--padding-y", y)]) } /// @@ -263,7 +263,7 @@ pub fn padding(x: String, y: String) -> Attribute(msg) { /// /// pub fn padding_x(value: String) -> Attribute(msg) { - attribute.style([#("--padding-x", value)]) + attribute.style("--padding-x", value) } /// @@ -271,7 +271,7 @@ pub fn padding_x(value: String) -> Attribute(msg) { /// /// pub fn padding_y(value: String) -> Attribute(msg) { - attribute.style([#("--padding-y", value)]) + attribute.style("--padding-y", value) } /// @@ -279,7 +279,7 @@ pub fn padding_y(value: String) -> Attribute(msg) { /// /// pub fn radius(value: String) -> Attribute(msg) { - attribute.style([#("--radius", value)]) + attribute.style("--radius", value) } /// @@ -287,5 +287,5 @@ pub fn radius(value: String) -> Attribute(msg) { /// /// pub fn text(value: String) -> Attribute(msg) { - attribute.style([#("--text", value)]) + attribute.style("--text", value) } diff --git a/src/lustre/ui/button.gleam b/src/lustre/ui/button.gleam index 65059df..b699566 100644 --- a/src/lustre/ui/button.gleam +++ b/src/lustre/ui/button.gleam @@ -187,7 +187,7 @@ pub fn badge( /// /// pub fn cmd_k_badge() { /// let chord = ["⌘", "k"] -/// let styles = attribute.style([#("--separator", "'+'")]) +/// let styles = attribute.style("--separator", "'+'") /// /// button.shortcut_badge([styles], chord) /// } @@ -219,7 +219,7 @@ pub fn count_badge(attributes: List(Attribute(msg)), count: Int) -> Element(msg) html.span( [ attribute.class("button-badge"), - attribute.style([#("font-variant-numeric", "tabular-nums")]), + attribute.style("font-variant-numeric", "tabular-nums"), ..attributes ], [ @@ -233,6 +233,47 @@ pub fn count_badge(attributes: List(Attribute(msg)), count: Int) -> Element(msg) // ATTRIBUTES ------------------------------------------------------------------ +/// Adjust the alert's colour to match your theme's primary colour palette. The +/// primary palette is typically used for alerts that provide additional information +/// or context to an action. +/// +/// +/// +pub fn primary() -> Attribute(msg) { + attribute.class("primary") +} + +/// Adjust the alert's colour to match your theme's secondary colour palette. The +/// secondary palette is typically used for alerts that provide additional information +/// or context to an action. +/// +/// +/// +pub fn secondary() -> Attribute(msg) { + attribute.class("secondary") +} + +/// Adjust the alert's colour to match your theme's warning colour palette. The +/// warning palette is typically used to draw the user's attention to additional +/// information before they perform an action or to alert the user of a non-fatal +/// error. +/// +/// +/// +pub fn warning() -> Attribute(msg) { + attribute.class("warning") +} + +/// Adjust the alert's colour to match your theme's danger colour palette. The +/// danger palette is typically used to highlight critical information before a +/// user performs an action or to alert the user of an important error. +/// +/// +/// +pub fn danger() -> Attribute(msg) { + attribute.class("danger") +} + /// /// pub fn clear() -> Attribute(msg) { @@ -304,59 +345,59 @@ pub fn large() -> Attribute(msg) { /// /// pub fn background(value: String) -> Attribute(msg) { - attribute.style([#("--background", value)]) + attribute.style("--background", value) } /// /// pub fn background_hover(value: String) -> Attribute(msg) { - attribute.style([#("--background-hover", value)]) + attribute.style("--background-hover", value) } /// /// pub fn border(value: String) -> Attribute(msg) { - attribute.style([#("--border", value)]) + attribute.style("--border", value) } /// /// pub fn border_hover(value: String) -> Attribute(msg) { - attribute.style([#("--border-hover", value)]) + attribute.style("--border-hover", value) } /// /// pub fn border_width(value: String) -> Attribute(msg) { - attribute.style([#("--border-width", value)]) + attribute.style("--border-width", value) } /// /// pub fn height(value: String) -> Attribute(msg) { - attribute.style([#("--height", value)]) + attribute.style("--height", value) } /// /// pub fn min_height(value: String) -> Attribute(msg) { - attribute.style([#("--min-height", value)]) + attribute.style("--min-height", value) } /// /// pub fn padding_x(value: String) -> Attribute(msg) { - attribute.style([#("--padding-x", value)]) + attribute.style("--padding-x", value) } /// /// pub fn radius(value: String) -> Attribute(msg) { - attribute.style([#("--radius", value)]) + attribute.style("--radius", value) } /// /// pub fn text(value: String) -> Attribute(msg) { - attribute.style([#("--text", value)]) + attribute.style("--text", value) } diff --git a/src/lustre/ui/card.gleam b/src/lustre/ui/card.gleam index 6af78cb..746ba94 100644 --- a/src/lustre/ui/card.gleam +++ b/src/lustre/ui/card.gleam @@ -1,3 +1,102 @@ +//// The [`card`](#element) element is used to create a small, self-contained +//// content container that groups related elements and information. +//// +//// Common uses for the `card` element include: +//// +//// - Displaying snippets of content or information that stand apart from surrounding +//// content but with a clear link between them, such as blog posts on a blog's +//// archive page or event details in a calendar view. +//// +//// - Previews or summary information that can be clicked to navigate to more +//// detailed views. +//// +//// - Containers for interactive elements that work as a cohesive unit like forms, +//// profile editors, or contact dialogs. +//// +//// ## Anatomy +//// +//// +//// +//// A card is made up of different parts: +//// +//// - The main [`element`](#element) container used to control the card's styles +//// and layout. (**required**) +//// +//// - A [`header`](#header) element that may contain a title or other information +//// about the card's purpose or content. (_optional_) +//// +//// - One or more pieces of [`content`](#content) displayed in the card's main body. +//// (_optional_) +//// +//// - A [`footer`](#footer) that can contain supplementary content, metadata, or +//// actions related to the card's content. (_optional_) +//// +//// ## Recipes +//// +//// Below are some recipes that show common uses of the `card` element. +//// +//// ### A basic content card with header and content: +//// +//// ```gleam +//// import lustre/element/html +//// import lustre/ui/card +//// +//// pub fn recipe_preview(title: String, preview: String) { +//// card.element([], [ +//// card.header([], [html.h2([], [html.text(title)])]), +//// card.content([], [html.p([], [html.text(preview)])]) +//// ]) +//// } +//// ``` +//// +//// ### A dialog-style card with footer actions: +//// +//// ```gleam +//// import lustre/element/html +//// import lustre/event +//// import lustre/ui/button +//// import lustre/ui/card +//// +//// pub fn delete_dialog() { +//// card.element([], [ +//// card.header([], [html.text("Delete recipe?")]), +//// card.content([], [ +//// html.text("Are you sure that you want to delete this recipe?") +//// ]), +//// card.footer([], [ +//// button.element([button.clear(), event.on_click(Cancel)], [html.text("Cancel")]), +//// button.element([button.solid(), button.danger(), event.on_click(Delete)], [ +//// html.text("Delete"), +//// ]) +//// ]) +//// ]) +//// } +//// ``` +//// +//// ## Customisation +//// +//// The border radius of a card can be controlled while still using your theme's +//// configuration by using one of the following attributes: +//// +//// - [`square`](#square) +//// - [`round`](#round) +//// +//// It is possible to control some aspects of a card's styling through CSS +//// variables. You may want to do this in cases where you are integrating lustre/ui +//// into an existing design system and you want the `card` element to match +//// elements outside of this package. +//// +//// The following CSS variables can set in your own stylesheets or by using the +//// corresponding attribute functions in this module: +//// +//// - [`--background`](#background) +//// - [`--border`](#border) +//// - [`--border-width`](#border_width) +//// - [`--padding-x`](#padding_x) +//// - [`--padding-y`](#padding_y) +//// - [`--radius`](#radius) +//// + // IMPORTS --------------------------------------------------------------------- import lustre/attribute.{type Attribute} @@ -62,41 +161,41 @@ pub fn round() -> Attribute(msg) { /// /// pub fn background(value: String) -> Attribute(msg) { - attribute.style([#("--background", value)]) + attribute.style("--background", value) } /// /// pub fn border(value: String) -> Attribute(msg) { - attribute.style([#("--border", value)]) + attribute.style("--border", value) } /// /// pub fn border_width(value: String) -> Attribute(msg) { - attribute.style([#("--border-width", value)]) + attribute.style("--border-width", value) } /// /// pub fn padding(x: String, y: String) -> Attribute(msg) { - attribute.style([#("--padding-x", x), #("--padding-y", y)]) + attribute.styles([#("--padding-x", x), #("--padding-y", y)]) } /// /// pub fn padding_x(value: String) -> Attribute(msg) { - attribute.style([#("--padding-x", value)]) + attribute.style("--padding-x", value) } /// /// pub fn padding_y(value: String) -> Attribute(msg) { - attribute.style([#("--padding-y", value)]) + attribute.style("--padding-y", value) } /// /// pub fn radius(value: String) -> Attribute(msg) { - attribute.style([#("--radius", value)]) + attribute.style("--radius", value) } diff --git a/src/lustre/ui/checkbox.gleam b/src/lustre/ui/checkbox.gleam index 6fa39c4..78bfdf0 100644 --- a/src/lustre/ui/checkbox.gleam +++ b/src/lustre/ui/checkbox.gleam @@ -1,3 +1,34 @@ +//// The [`checkbox`](#element) element is an interactive control that lets users +//// select a value that has two possible states - checked and unchecked. +//// +//// Common uses for checkboxes include: +//// +//// - Toggling a boolean value in a form or setting. +//// +//// - Selecting multiple items from a list. +//// +//// - Toggling visibility or state of other elements. +//// +//// ## Customisation +//// +//// It is possible to control some aspects of a checkbox's styling through CSS +//// variables. You may want to do this in cases where you are integrating lustre/ui +//// into an existing design system and you want the `checkbox` element to match +//// elements outside of this package. +//// +//// The following CSS variables can set in your own stylesheets or by using the +//// corresponding attribute functions in this module: +//// +//// - [`--background`](#background) +//// - [`--background-hover`](#background_hover) +//// - [`--border`](#border) +//// - [`--border-hover`](#border_hover) +//// - [`--border-width`](#border_width) +//// - [`--check-color`](#check_color) +//// - [`--padding`](#padding) +//// - [`--size`](#size) +//// + // IMPORTS --------------------------------------------------------------------- import lustre/attribute.{type Attribute} @@ -17,33 +48,33 @@ pub fn element(attributes: List(Attribute(msg))) -> Element(msg) { // CSS VARIABLES --------------------------------------------------------------- pub fn background(value: String) -> Attribute(msg) { - attribute.style([#("--background", value)]) + attribute.style("--background", value) } pub fn background_hover(value: String) -> Attribute(msg) { - attribute.style([#("--background-hover", value)]) + attribute.style("--background-hover", value) } pub fn border(value: String) -> Attribute(msg) { - attribute.style([#("--border", value)]) + attribute.style("--border", value) } pub fn border_hover(value: String) -> Attribute(msg) { - attribute.style([#("--border-hover", value)]) + attribute.style("--border-hover", value) } pub fn border_width(value: String) -> Attribute(msg) { - attribute.style([#("--border-width", value)]) + attribute.style("--border-width", value) } pub fn size(value: String) -> Attribute(msg) { - attribute.style([#("--size", value)]) + attribute.style("--size", value) } pub fn padding(value: String) -> Attribute(msg) { - attribute.style([#("--padding", value)]) + attribute.style("--padding", value) } pub fn check_color(value: String) -> Attribute(msg) { - attribute.style([#("--check-color", value)]) + attribute.style("--check-color", value) } diff --git a/src/lustre/ui/colour.gleam b/src/lustre/ui/colour.gleam index 65c79fc..bfcbf13 100644 --- a/src/lustre/ui/colour.gleam +++ b/src/lustre/ui/colour.gleam @@ -1,9 +1,9 @@ // IMPORTS --------------------------------------------------------------------- -import gleam/dynamic.{type DecodeError, type Dynamic} + +import gleam/dynamic/decode.{type Decoder} import gleam/float import gleam/int import gleam/json.{type Json} -import gleam/result.{try} import gleam_community/colour import gleam_community/colour/accessibility @@ -183,23 +183,23 @@ pub fn encode_palette(palette: ColourPalette) -> Json { ]) } -pub fn decoder(json: Dynamic) -> Result(ColourScale, List(DecodeError)) { - use bg <- try(dynamic.field("bg", colour.decoder)(json)) - use bg_subtle <- try(dynamic.field("bg_subtle", colour.decoder)(json)) - use tint <- try(dynamic.field("tint", colour.decoder)(json)) - use tint_subtle <- try(dynamic.field("tint_subtle", colour.decoder)(json)) - use tint_strong <- try(dynamic.field("tint_strong", colour.decoder)(json)) - use accent <- try(dynamic.field("accent", colour.decoder)(json)) - use accent_subtle <- try(dynamic.field("accent_subtle", colour.decoder)(json)) - use accent_strong <- try(dynamic.field("accent_strong", colour.decoder)(json)) - use solid <- try(dynamic.field("solid", colour.decoder)(json)) - use solid_subtle <- try(dynamic.field("solid_subtle", colour.decoder)(json)) - use solid_strong <- try(dynamic.field("solid_strong", colour.decoder)(json)) - use solid_text <- try(dynamic.field("solid_text", colour.decoder)(json)) - use text <- try(dynamic.field("text", colour.decoder)(json)) - use text_subtle <- try(dynamic.field("text_subtle", colour.decoder)(json)) - - Ok(ColourScale( +pub fn decoder() -> Decoder(ColourScale) { + use bg <- decode.field("bg", colour.decoder()) + use bg_subtle <- decode.field("bg_subtle", colour.decoder()) + use tint <- decode.field("tint", colour.decoder()) + use tint_subtle <- decode.field("tint_subtle", colour.decoder()) + use tint_strong <- decode.field("tint_strong", colour.decoder()) + use accent <- decode.field("accent", colour.decoder()) + use accent_subtle <- decode.field("accent_subtle", colour.decoder()) + use accent_strong <- decode.field("accent_strong", colour.decoder()) + use solid <- decode.field("solid", colour.decoder()) + use solid_subtle <- decode.field("solid_subtle", colour.decoder()) + use solid_strong <- decode.field("solid_strong", colour.decoder()) + use solid_text <- decode.field("solid_text", colour.decoder()) + use text <- decode.field("text", colour.decoder()) + use text_subtle <- decode.field("text_subtle", colour.decoder()) + + decode.success(ColourScale( bg:, bg_subtle:, tint:, @@ -217,18 +217,22 @@ pub fn decoder(json: Dynamic) -> Result(ColourScale, List(DecodeError)) { )) } -pub fn palette_decoder( - json: Dynamic, -) -> Result(ColourPalette, List(DecodeError)) { - dynamic.decode6( - ColourPalette, - dynamic.field("base", decoder), - dynamic.field("primary", decoder), - dynamic.field("secondary", decoder), - dynamic.field("success", decoder), - dynamic.field("warning", decoder), - dynamic.field("danger", decoder), - )(json) +pub fn palette_decoder() -> Decoder(ColourPalette) { + use base <- decode.field("base", decoder()) + use primary <- decode.field("primary", decoder()) + use secondary <- decode.field("secondary", decoder()) + use success <- decode.field("success", decoder()) + use warning <- decode.field("warning", decoder()) + use danger <- decode.field("danger", decoder()) + + decode.success(ColourPalette( + base:, + primary:, + secondary:, + success:, + warning:, + danger:, + )) } // RADIX UI -------------------------------------------------------------------- diff --git a/src/lustre/ui/combobox.gleam b/src/lustre/ui/combobox.gleam index 08220a5..ba33264 100644 --- a/src/lustre/ui/combobox.gleam +++ b/src/lustre/ui/combobox.gleam @@ -1,8 +1,91 @@ +//// The [`combobox`](#element) element combines a textbox with a drop-down list. +//// This allows users to select a value from a predefined list of options, or +//// optionally enter a custom value by typing. +//// +//// Common uses for comboboxes include: +//// +//// - Selecting from a filtered list of items like countries or currencies +//// +//// - Entering common text values with typeahead like calendar input fields +//// +//// ## Anatomy +//// +//// +//// +//// A combobox is made up of different parts: +//// +//// - The main [`element`](#element) container used to control the combobox's +//// styles and layout. (**required**) +//// +//// - The [`trigger`](#element) button used to open and close the combobox menu. +//// (**required**) +//// +//// - An input control used to filter the available options. (**required**) +//// +//// - A list of [`option`](#option) elements representing the available choices. +//// (**required**) +//// +//// ## Recipes +//// +//// Below are some recipes that show common uses of the `combobox` element. +//// +//// ### A basic combobox with text options: +//// +//// ```gleam +//// import lustre/ui/combobox +//// +//// pub fn fruit_picker() { +//// combobox.element([], [ +//// combobox.option(value: "apple", label: "Apple"), +//// combobox.option(value: "banana", label: "Banana"), +//// combobox.option(value: "orange", label: "Orange"), +//// ]) +//// } +//// ``` +//// +//// ### A combobox with typeahead filtering: +//// +//// ```gleam +//// import lustre/ui/combobox +//// +//// pub fn language_picker() { +//// combobox.element([], [ +//// combobox.option(value: "gleam", label: "Gleam"), +//// combobox.option(value: "go", label: "Go"), +//// combobox.option(value: "javascript", label: "JavaScript"), +//// combobox.option(value: "kotlin", label: "Kotlin"), +//// combobox.option(value: "rust", label: "Rust"), +//// combobox.option(value: "typescript", label: "TypeScript"), +//// ]) +//// } +//// ``` +//// +//// ## Customisation +//// +//// It is possible to control some aspects of a combobox's styling through CSS +//// variables. You may want to do this in cases where you are integrating lustre/ui +//// into an existing design system and you want the `combobox` element to match +//// elements outside of this package. +//// +//// ## Accessibility +//// +//// Comboboxes created with this element have a robust set of keyboard commands: +//// +//// - ArrowDown will focus the next option in the menu +//// - ArrowUp will focus the previous option in the menu +//// - End will focus the last option in the menu +//// - Enter will open/close the menu and select the focused option +//// - Escape will close the menu +//// - Home will focus the first option in the menu +//// - Space will open/close the menu +//// - Tab will close the menu and focus the next focusable element +//// + // IMPORTS --------------------------------------------------------------------- import gleam/bool -import gleam/dict.{type Dict} -import gleam/dynamic.{type DecodeError, type Decoder, type Dynamic, dynamic} +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode.{type Decoder} import gleam/int import gleam/json import gleam/list @@ -14,10 +97,13 @@ import gleam/set import gleam/string import lustre import lustre/attribute.{type Attribute, attribute} +import lustre/component import lustre/effect.{type Effect} import lustre/element.{type Element} import lustre/element/html +import lustre/element/keyed import lustre/event +import lustre/ffi/dom import lustre/ui/data/bidict.{type Bidict} import lustre/ui/input import lustre/ui/primitives/icon @@ -25,8 +111,8 @@ import lustre/ui/primitives/popover // TYPES ----------------------------------------------------------------------- -pub opaque type Item { - Item(value: String, label: String) +pub opaque type Item(msg) { + Item(value: String, label: String, content: List(Element(msg))) } // ELEMENTS -------------------------------------------------------------------- @@ -36,7 +122,24 @@ pub const name: String = "lustre-ui-combobox" pub fn register() -> Result(Nil, lustre.Error) { case popover.register() { Ok(Nil) | Error(lustre.ComponentAlreadyRegistered(_)) -> { - let app = lustre.component(init, update, view, on_attribute_change()) + let app = + lustre.component(init, update, view, [ + component.adopt_styles(False), + component.on_attribute_change("value", fn(value) { + Ok(ParentSetValue(value)) + }), + component.on_attribute_change("placeholder", fn(value) { + Ok(ParentSetPlaceholder(value)) + }), + component.on_attribute_change("strategy", fn(value) { + case value { + "by-length" -> Ok(ParentSetStrategy(ByLength)) + "by-index" -> Ok(ParentSetStrategy(ByIndex)) + _ -> Error(Nil) + } + }), + ]) + lustre.register(app, name) } error -> error @@ -45,23 +148,39 @@ pub fn register() -> Result(Nil, lustre.Error) { pub fn element( attributes: List(Attribute(msg)), - children: List(Item), + children: List(Item(msg)), ) -> Element(msg) { - element.keyed(element.element(name, attributes, _), { - use item <- list.map(children) - let el = + keyed.element(name, attributes, { + use item <- list.flat_map(children) + let option = element.element( "lustre-ui-combobox-option", [attribute.value(item.value)], [html.text(item.label)], ) - #(item.value, el) + use <- bool.guard(list.is_empty(item.content), [#(item.value, option)]) + + [ + #(item.value, option), + #( + "option-" <> item.value, + html.div([attribute("slot", "option-" <> item.value)], item.content), + ), + ] }) } -pub fn option(value value: String, label label: String) -> Item { - Item(value:, label:) +pub fn option(value value: String, label label: String) -> Item(msg) { + Item(value:, label:, content: []) +} + +pub fn custom( + value value: String, + label label: String, + content content: List(Element(msg)), +) { + Item(value:, label:, content:) } // ATTRIBUTES ------------------------------------------------------------------ @@ -74,14 +193,51 @@ pub fn placeholder(value: String) -> Attribute(msg) { attribute("placeholder", value) } +// VARIABLES ------------------------------------------------------------------- + +/// +/// +/// +pub fn padding(x x: String, y y: String) -> Attribute(msg) { + attribute.styles([#("--padding-x", x), #("--padding-y", y)]) +} + +/// +/// +/// +pub fn padding_x(value: String) -> Attribute(msg) { + attribute.style("--padding-x", value) +} + +/// +/// +/// +pub fn padding_y(value: String) -> Attribute(msg) { + attribute.style("--padding-y", value) +} + +/// +/// +/// +pub fn border_width(value: String) -> Attribute(msg) { + attribute.style("--border-width", value) +} + +/// +/// +/// +pub fn radius(value: String) -> Attribute(msg) { + attribute.style("--radius", value) +} + // EVENTS ---------------------------------------------------------------------- pub fn on_change(handler: fn(String) -> msg) -> Attribute(msg) { - use event <- event.on("change") - use detail <- result.try(dynamic.field("detail", dynamic)(event)) - use value <- result.try(dynamic.field("value", dynamic.string)(detail)) + event.on("change", { + use value <- decode.subfield(["detail", "value"], decode.string) - Ok(handler(value)) + decode.success(handler(value)) + }) } // MODEL ----------------------------------------------------------------------- @@ -105,8 +261,8 @@ type Strategy { type Options { Options( - all: List(Item), - filtered: List(Item), + all: List(Item(Nil)), + filtered: List(Item(Nil)), lookup_label: Bidict(String, String), lookup_index: Bidict(String, Int), ) @@ -128,7 +284,7 @@ fn init(_) -> #(Model, Effect(Msg)) { lookup_index: bidict.new(), ), ) - let effect = effect.batch([set_state("empty")]) + let effect = effect.batch([component.set_pseudo_state("empty")]) #(model, effect) } @@ -138,28 +294,31 @@ fn init(_) -> #(Model, Effect(Msg)) { type Msg { DomBlurredTrigger DomFocusedTrigger - ParentChangedChildren(List(Item)) + ParentChangedChildren(List(Item(Nil))) ParentSetPlaceholder(String) ParentSetStrategy(Strategy) ParentSetValue(String) + UserActivatedPopoverTrigger(input: Dynamic) UserChangedQuery(String) UserClosedMenu UserHoveredOption(String) UserOpenedMenu - UserPressedDown - UserPressedEnd - UserPressedEnter - UserPressedEscape - UserPressedHome - UserPressedUp + UserPressedDown(event: Dynamic) + UserPressedEnd(event: Dynamic) + UserPressedEnter(event: Dynamic) + UserPressedEscape(event: Dynamic, trigger: Dynamic) + UserPressedHome(event: Dynamic) + UserPressedUp(event: Dynamic) UserSelectedOption(String) } fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { - case msg { - DomBlurredTrigger -> #(model, remove_state("trigger-focus")) - - DomFocusedTrigger -> #(model, set_state("trigger-focus")) + case echo msg { + DomBlurredTrigger -> #( + model, + component.remove_pseudo_state("trigger-focus"), + ) + DomFocusedTrigger -> #(model, component.set_pseudo_state("trigger-focus")) ParentChangedChildren(all) -> { let lookup_label = @@ -191,8 +350,17 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { ParentSetValue(value) -> { let model = Model(..model, value:, intent: option.Some(value)) let effect = case value { - "" -> set_state("empty") - _ -> remove_state("empty") + "" -> component.set_pseudo_state("empty") + _ -> component.remove_pseudo_state("empty") + } + + #(model, effect) + } + + UserActivatedPopoverTrigger(input:) -> { + let effect = { + use _, _ <- effect.after_paint + dom.do_focus(input) } #(model, effect) @@ -215,7 +383,7 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { UserClosedMenu -> { let model = Model(..model, expanded: False, intent: option.None) - let effect = remove_state("expanded") + let effect = component.remove_pseudo_state("expanded") #(model, effect) } @@ -227,12 +395,12 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { UserOpenedMenu -> { let model = Model(..model, expanded: True) - let effect = set_state("expanded") + let effect = component.set_pseudo_state("expanded") #(model, effect) } - UserPressedDown -> { + UserPressedDown(event:) -> { let intent = case model.intent { option.Some(intent) -> bidict.next(model.options.lookup_index, intent, int.add(_, 1)) @@ -244,51 +412,59 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { } let model = Model(..model, intent:) - let effect = effect.none() + let effect = dom.prevent_default(event) #(model, effect) } - UserPressedEnd -> { + UserPressedEnd(event:) -> { let intent = model.options.lookup_index |> bidict.max_inverse(int.compare) |> option.from_result let model = Model(..model, intent:) - let effect = effect.none() + let effect = dom.prevent_default(event) #(model, effect) } - UserPressedEnter -> { + UserPressedEnter(event:) -> { let effect = case model.intent { option.Some(value) -> - event.emit("change", json.object([#("value", json.string(value))])) - option.None -> effect.none() + effect.batch([ + event.emit("change", json.object([#("value", json.string(value))])), + dom.prevent_default(event), + ]) + option.None -> dom.prevent_default(event) } #(model, effect) } - UserPressedEscape -> { + UserPressedEscape(event:, trigger:) -> { let model = Model(..model, expanded: False, intent: option.None) - let effect = remove_state("expanded") + let effect = + effect.batch([ + component.remove_pseudo_state("expanded"), + dom.prevent_default(event), + effect.after_paint(fn(_, _) { dom.do_focus(trigger) }), + ]) #(model, effect) } - UserPressedHome -> { + UserPressedHome(event:) -> { let intent = model.options.lookup_index |> bidict.min_inverse(int.compare) |> option.from_result let model = Model(..model, intent:) - let effect = effect.none() + let effect = dom.prevent_default(event) #(model, effect) } - UserPressedUp -> { + UserPressedUp(event:) -> { let intent = case model.intent { option.Some(intent) -> bidict.next(model.options.lookup_index, intent, int.subtract(_, 1)) @@ -300,7 +476,7 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { } let model = Model(..model, intent:) - let effect = effect.none() + let effect = dom.prevent_default(event) #(model, effect) } @@ -325,90 +501,52 @@ fn intent_from_query( options: Options, ) -> option.Option(String) { use <- bool.guard(query == "", option.None) + let query = string.lowercase(query) - let matches = - list.filter(options.all, fn(option) { - option.label |> string.lowercase |> string.contains(query) - }) - - let sorted = - list.sort(matches, fn(a, b) { - let a_label = string.lowercase(a.label) - let b_label = string.lowercase(b.label) - - let a_starts_with_query = string.starts_with(a_label, query) - let b_starts_with_query = string.starts_with(b_label, query) - - let assert Ok(a_index) = bidict.get(options.lookup_index, a.value) - let assert Ok(b_index) = bidict.get(options.lookup_index, b.value) - - let a_length = string.length(a_label) - let b_length = string.length(b_label) - - case a_starts_with_query, b_starts_with_query, strategy { - True, False, _ -> order.Lt - False, True, _ -> order.Gt - _, _, ByIndex -> int.compare(a_index, b_index) - _, _, ByLength -> int.compare(a_length, b_length) - } - }) - sorted + let compare_options = fn(a: Item(Nil), b: Item(Nil)) -> order.Order { + let a_label = string.lowercase(a.label) + let b_label = string.lowercase(b.label) + let a_starts = string.starts_with(a_label, query) + let b_starts = string.starts_with(b_label, query) + + let assert Ok(a_index) = bidict.get(options.lookup_index, a.value) + let assert Ok(b_index) = bidict.get(options.lookup_index, b.value) + + case a_starts, b_starts, strategy { + True, False, _ -> order.Lt + False, True, _ -> order.Gt + _, _, ByIndex -> int.compare(a_index, b_index) + _, _, ByLength -> + int.compare(string.length(a_label), string.length(b_label)) + } + } + + options.all + |> list.filter(contains_query(_, query)) + |> list.sort(compare_options) |> list.first |> result.map(fn(option) { option.value }) |> option.from_result } -fn on_attribute_change() -> Dict(String, Decoder(Msg)) { - dict.from_list([ - #("value", fn(value) { - value - |> dynamic.string - |> result.map(ParentSetValue) - }), - #("placeholder", fn(value) { - value - |> dynamic.string - |> result.map(ParentSetPlaceholder) - }), - #("strategy", fn(value) { - case dynamic.string(value) { - Ok("by-index") -> Ok(ParentSetStrategy(ByIndex)) - Ok("by-length") -> Ok(ParentSetStrategy(ByLength)) - _ -> Error([]) - } - }), - ]) +fn contains_query(option: Item(a), query: String) -> Bool { + option.label + |> string.lowercase + |> string.contains(query) } -// EFFECTS --------------------------------------------------------------------- - -fn set_state(value: String) -> Effect(msg) { - use _, root <- element.get_root - - do_set_state(value, root) -} - -@external(javascript, "../../dom.ffi.mjs", "set_state") -fn do_set_state(value: String, root: Dynamic) -> Nil - -fn remove_state(value: String) -> Effect(msg) { - use _, root <- element.get_root - - do_remove_state(value, root) -} - -@external(javascript, "../../dom.ffi.mjs", "remove_state") -fn do_remove_state(value: String, root: Dynamic) -> Nil - // VIEW ------------------------------------------------------------------------ fn view(model: Model) -> Element(Msg) { element.fragment([ - html.slot([ - attribute.style([#("display", "none")]), - event.on("slotchange", handle_slot_change), - ]), + html.slot( + [ + attribute.style("display", "none"), + event.on("slotchange", handle_slot_change()), + ], + [], + ), popover.element( [ popover.anchor(popover.BottomMiddle), @@ -424,8 +562,8 @@ fn view(model: Model) -> Element(Msg) { // To handle those, we instead listen for explicit interaction events and // trigger the focus from them. These events will never produce a message. // - event.on("click", handle_popover_click(_, !model.expanded)), - event.on("keydown", handle_popover_keydown(_, !model.expanded)), + event.on("click", handle_popover_click(!model.expanded)), + event.on("keydown", handle_popover_keydown(!model.expanded)), ], trigger: view_trigger(model.value, model.placeholder, model.options), content: html.div([attribute("part", "combobox-options")], [ @@ -436,82 +574,61 @@ fn view(model: Model) -> Element(Msg) { ]) } -fn handle_slot_change(event: Dynamic) -> Result(Msg, List(DecodeError)) { - use children <- result.try(dynamic.field("target", assigned_elements)(event)) - use options <- result.try( - dynamic.list(fn(el) { - dynamic.decode3( - fn(tag, value, label) { #(tag, value, label) }, - dynamic.field("tagName", dynamic.string), - get_attribute("value"), - dynamic.field("textContent", dynamic.string), - )(el) - })(children), - ) +fn handle_slot_change() -> Decoder(Msg) { + use options <- decode.field("target", { + dom.assigned_elements( + { + use tag <- decode.field("tagName", decode.string) + use value <- decode.then(dom.attribute("value")) + use label <- decode.field("textContent", decode.string) + + decode.success(#(tag, value, label)) + }, + lenient: True, + ) + }) options |> list.fold_right(#([], set.new()), fn(acc, option) { let #(tag, value, label) = option - use <- bool.guard(tag != "LUSTRE-UI-COMBOBOX-OPTION", acc) use <- bool.guard(set.contains(acc.1, value), acc) let seen = set.insert(acc.1, value) - let options = [Item(value:, label:), ..acc.0] + let options = [Item(value:, label:, content: []), ..acc.0] #(options, seen) }) |> pair.first |> ParentChangedChildren - |> Ok + |> decode.success } -@external(javascript, "../../dom.ffi.mjs", "assigned_elements") -fn assigned_elements(_slot: Dynamic) -> Result(Dynamic, List(DecodeError)) - -@external(javascript, "../../dom.ffi.mjs", "get_attribute") -fn get_attribute(name: String) -> Decoder(String) - -fn handle_popover_click( - event: Dynamic, - will_open: Bool, -) -> Result(Msg, List(DecodeError)) { - use target <- result.try(dynamic.field("currentTarget", dynamic)(event)) - use input <- result.try(get_element("input")(target)) +fn handle_popover_click(will_open: Bool) -> Decoder(Msg) { + use input <- decode.field( + "currentTarget", + dom.child("input", dynamic.nil(), decode.dynamic), + ) case will_open { - True -> after_paint(fn() { focus(input) }) - False -> Nil + True -> decode.success(UserActivatedPopoverTrigger(input:)) + False -> decode.failure(UserActivatedPopoverTrigger(input:), "") } - - Error([]) } -fn handle_popover_keydown( - event: Dynamic, - will_open: Bool, -) -> Result(Msg, List(DecodeError)) { - use key <- result.try(dynamic.field("key", dynamic.string)(event)) - use target <- result.try(dynamic.field("currentTarget", dynamic)(event)) - use input <- result.try(get_element("input")(target)) +fn handle_popover_keydown(will_open: Bool) -> Decoder(Msg) { + use key <- decode.field("key", decode.string) + use input <- decode.field( + "currentTarget", + dom.child("input", dynamic.nil(), decode.dynamic), + ) case key { - "Enter" | " " if will_open -> { - after_paint(fn() { focus(input) }) - Error([]) - } - _ -> Error([]) + "Enter" | " " if will_open -> + decode.success(UserActivatedPopoverTrigger(input:)) + _ -> decode.failure(UserActivatedPopoverTrigger(input:), "") } } -@external(javascript, "../../dom.ffi.mjs", "get_element") -fn get_element(selector: String) -> Decoder(Dynamic) - -@external(javascript, "../../dom.ffi.mjs", "focus") -fn focus(element: Dynamic) -> Nil - -@external(javascript, "../../scheduler.ffi.mjs", "after_paint") -fn after_paint(k: fn() -> Nil) -> Nil - // VIEW TRIGGER ---------------------------------------------------------------- fn view_trigger( @@ -553,69 +670,42 @@ fn view_input(query: String) -> Element(Msg) { input.container([attribute("part", "combobox-input")], [ icon.magnifying_glass([]), input.element([ - attribute.style([ + attribute.styles([ #("width", "100%"), #("border-bottom-left-radius", "0px"), #("border-bottom-right-radius", "0px"), ]), attribute.autocomplete("off"), event.on_input(UserChangedQuery), - event.on("keydown", handle_input_keydown), + event.on("keydown", handle_input_keydown()), attribute.value(query), ]), ]) } -fn handle_input_keydown(event: Dynamic) -> Result(Msg, List(DecodeError)) { - use key <- result.try(dynamic.field("key", dynamic.string)(event)) +fn handle_input_keydown() -> Decoder(Msg) { + use event <- decode.then(decode.dynamic) + use key <- decode.field("key", decode.string) case key { - "ArrowDown" -> { - event.prevent_default(event) - Ok(UserPressedDown) - } - - "ArrowEnd" -> { - event.prevent_default(event) - Ok(UserPressedEnd) - } - - "Enter" -> { - event.prevent_default(event) - Ok(UserPressedEnter) - } - + "ArrowDown" -> decode.success(UserPressedDown(event:)) + "ArrowEnd" -> decode.success(UserPressedEnd(event:)) + "Enter" -> decode.success(UserPressedEnter(event:)) "Escape" -> { - use root <- result.try(dynamic.field("target", get_root)(event)) - use trigger <- result.try(get_element("button")(root)) - - after_paint(fn() { focus(trigger) }) - event.prevent_default(event) - - Ok(UserPressedEscape) - } - - "Home" -> { - event.prevent_default(event) - Ok(UserPressedHome) - } - - "ArrowUp" -> { - event.prevent_default(event) - Ok(UserPressedUp) - } + use trigger <- decode.field( + "target", + dom.child("button", dynamic.nil(), decode.dynamic), + ) - "Tab" -> { - Ok(UserClosedMenu) + decode.success(UserPressedEscape(event:, trigger:)) } - - _ -> Error([]) + "Home" -> decode.success(UserPressedHome(event:)) + "ArrowUp" -> decode.success(UserPressedUp(event:)) + "Tab" -> decode.success(UserClosedMenu) + _ -> decode.failure(UserClosedMenu, "") } } -@external(javascript, "../../dom.ffi.mjs", "get_root") -fn get_root(element: Dynamic) -> Result(Dynamic, List(DecodeError)) - // VIEW OPTIONS ---------------------------------------------------------------- fn view_options( @@ -623,14 +713,11 @@ fn view_options( value: String, intent: option.Option(String), ) -> Element(Msg) { - element.keyed( - html.ul([], _), - do_view_options(options.filtered, value, intent), - ) + keyed.ul([], do_view_options(options.filtered, value, intent)) } fn do_view_options( - options: List(Item), + options: List(Item(msg)), value: String, intent: option.Option(String), ) -> List(#(String, Element(Msg))) { @@ -645,7 +732,7 @@ fn do_view_options( } fn view_option( - option: Item, + option: Item(msg), value: String, intent: option.Option(String), last: Bool, @@ -654,7 +741,7 @@ fn view_option( let is_intent = option.Some(option.value) == intent let icon = case is_selected { - True -> icon.check(_) + True -> icon.check False -> html.span(_, []) } @@ -678,8 +765,8 @@ fn view_option( event.on_mouse_down(UserSelectedOption(option.value)), ], [ - icon([attribute.style([#("height", "1rem"), #("width", "1rem")])]), - html.span([attribute.style([#("flex", "1 1 0%")])], [ + icon([attribute.styles([#("height", "1rem"), #("width", "1rem")])]), + html.span([attribute.style("flex", "1 1 0%")], [ element.element("slot", [attribute.name("option-" <> option.value)], [ html.text(option.label), ]), diff --git a/src/lustre/ui/divider.gleam b/src/lustre/ui/divider.gleam index 32fe529..ddf30b4 100644 --- a/src/lustre/ui/divider.gleam +++ b/src/lustre/ui/divider.gleam @@ -1,6 +1,76 @@ +//// The [`divider`](#element) element is a visual separator between sections of +//// content that can be presented in one of two ways: +//// +//// 1. As a horizontal rule with no content (similar to the HTML `
` element). +//// +//// 2. As a horizontal rule that can contain content positioned in its center. +//// +//// A common alternative term for this element is a separator. +//// +//// Common uses for dividers include: +//// +//// - Separating blocks of related content to make them easier to scan. +//// +//// - Adding labels to group related links or menu items. +//// +//// ## Anatomy +//// +//// +//// +//// A divider has a very simple anatomy: +//// +//// - The main [`element`](#element) container used to control the divider's +//// styles and layout. If no content is provided, this is rendered as an `
`, +//// otherwise it is rendered as a `
`. (**required**) +//// +//// - An optional container for content that serves as a label or title for the +//// content that follows, for example "OR" or "Section 3". (_optional_) +//// +//// ## Recipes +//// +//// Below are some recipes that show common uses of the `divider` element. +//// +//// ### A basic divider with no content: +//// +//// ```gleam +//// import lustre/ui/divider +//// +//// pub fn hr() { +//// divider.element([], []) +//// } +//// ``` +//// +//// ### A divider with a text label: +//// +//// ```gleam +//// import lustre/element/html +//// import lustre/ui/divider +//// +//// pub fn or() { +//// divider.element([], [html.text("OR")]) +//// } +//// ``` +//// +//// ## Customisation +//// +//// It is possible to control some aspects of a divider's styling through CSS +//// variables. You may want to do this in cases where you are integrating lustre/ui +//// into an existing design system and you want the `divider` element to match +//// elements outside of this package. +//// +//// The following CSS variables can set in your own stylesheets or by using the +//// corresponding attribute functions in this module: +//// +//// - [`--colour`](#colour) +//// - [`--gap`](#gap) +//// - [`--margin`](#margin) +//// - [`--size-x`](#size_x) +//// - [`--size-y`](#size_y) +//// + // IMPORTS --------------------------------------------------------------------- -import lustre/attribute.{type Attribute, attribute} +import lustre/attribute.{type Attribute} import lustre/element.{type Element} import lustre/element/html @@ -32,21 +102,21 @@ pub fn of( // VARIABLES ------------------------------------------------------------------- pub fn colour(value: String) -> Attribute(msg) { - attribute.style([#("--colour", value)]) + attribute.style("--colour", value) } pub fn gap(value: String) -> Attribute(msg) { - attribute.style([#("--gap", value)]) + attribute.style("--gap", value) } pub fn margin(value: String) -> Attribute(msg) { - attribute.style([#("--margin", value)]) + attribute.style("--margin", value) } pub fn size_x(value: String) -> Attribute(msg) { - attribute.style([#("--size-x", value)]) + attribute.style("--size-x", value) } pub fn size_y(value: String) -> Attribute(msg) { - attribute.style([#("--size-y", value)]) + attribute.style("--size-y", value) } diff --git a/src/lustre/ui/primitives/collapse.gleam b/src/lustre/ui/primitives/collapse.gleam index 54ed359..fa9ff3b 100644 --- a/src/lustre/ui/primitives/collapse.gleam +++ b/src/lustre/ui/primitives/collapse.gleam @@ -1,20 +1,20 @@ // IMPORTS --------------------------------------------------------------------- -import decipher import gleam/bool -import gleam/dict.{type Dict} -import gleam/dynamic.{type DecodeError, type Decoder, type Dynamic, dynamic} +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode.{type Decoder} import gleam/float import gleam/int import gleam/json -import gleam/result import gleam/string import lustre import lustre/attribute.{type Attribute, attribute} +import lustre/component import lustre/effect.{type Effect} import lustre/element.{type Element} import lustre/element/html import lustre/event +import lustre/ffi/dom // ELEMENTS -------------------------------------------------------------------- @@ -26,7 +26,17 @@ pub const name: String = "lustre-ui-collapse" // must do this before the component will properly render. // pub fn register() -> Result(Nil, lustre.Error) { - let app = lustre.component(init, update, view, on_attribute_change()) + let app = + lustre.component(init, update, view, [ + component.adopt_styles(True), + component.on_attribute_change("aria-expanded", fn(value) { + case value { + "true" -> Ok(ParentSetExpanded(True)) + "false" | "" -> Ok(ParentSetExpanded(False)) + _ -> Error(Nil) + } + }), + ]) lustre.register(app, name) } @@ -62,31 +72,25 @@ pub fn expanded(is_expanded: Bool) -> Attribute(msg) { // By default, // pub fn duration(ms: Int) -> Attribute(msg) { - attribute.style([#("transition-duration", int.to_string(ms) <> "ms")]) + attribute.style("transition-duration", int.to_string(ms) <> "ms") } // EVENTS ---------------------------------------------------------------------- pub fn on_change(handler: fn(Bool) -> msg) -> Attribute(msg) { - use event <- event.on("change") - use is_expanded <- result.try(decipher.at( - ["detail", "expanded"], - dynamic.bool, - )(event)) + event.on("change", { + use expanded <- decode.subfield(["detail", "expanded"], decode.bool) - Ok(handler(is_expanded)) + decode.success(handler(expanded)) + }) } pub fn on_expand(handler: msg) -> Attribute(msg) { - use _ <- event.on("expand") - - Ok(handler) + event.on("expand", decode.success(handler)) } pub fn on_collapse(handler: msg) -> Attribute(msg) { - use _ <- event.on("collapse") - - Ok(handler) + event.on("collapse", decode.success(handler)) } // MODEL ----------------------------------------------------------------------- @@ -107,14 +111,14 @@ fn init(_) -> #(Model, Effect(Msg)) { type Msg { ParentChangedContent(Float) ParentSetExpanded(Bool) - UserPressedTrigger(Float) + UserPressedTrigger(Float, event: Dynamic) } fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { case msg { ParentChangedContent(height) -> #(Model(..model, height:), effect.none()) ParentSetExpanded(expanded) -> #(Model(..model, expanded:), effect.none()) - UserPressedTrigger(height) -> { + UserPressedTrigger(height, event) -> { let model = Model(..model, height:) let emit_change = @@ -128,26 +132,18 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { False -> event.emit("expand", json.null()) } - let effect = effect.batch([emit_change, emit_expand_collapse]) + let effect = + effect.batch([ + emit_change, + emit_expand_collapse, + dom.prevent_default(event), + ]) #(model, effect) } } } -fn on_attribute_change() -> Dict(String, Decoder(Msg)) { - dict.from_list([ - // - #("aria-expanded", fn(value) { - value - |> decipher.bool_string - |> result.unwrap(False) - |> ParentSetExpanded - |> Ok - }), - ]) -} - // VIEW ------------------------------------------------------------------------ fn view(model: Model) -> Element(Msg) { @@ -160,66 +156,77 @@ fn view(model: Model) -> Element(Msg) { } fn view_trigger() -> Element(Msg) { - html.slot([ - attribute("part", "collapse-trigger"), - attribute.name("trigger"), - event.on("click", handle_click), - event.on("keydown", handle_keydown), - ]) + html.slot( + [ + attribute("part", "collapse-trigger"), + attribute.name("trigger"), + event.on("click", handle_click()), + event.on("keydown", handle_keydown()), + ], + [], + ) } fn view_content(height: String) -> Element(Msg) { html.div( [ attribute("part", "collapse-content"), - attribute.style([#("transition-duration", "inherit"), #("height", height)]), + attribute.styles([ + #("transition-duration", "inherit"), + #("height", height), + ]), ], - [html.slot([event.on("slotchange", handle_slot_change)])], + [html.slot([event.on("slotchange", handle_slot_change())], [])], ) } // EVENT HANDLERS -------------------------------------------------------------- -fn handle_click(event: Dynamic) -> Result(Msg, List(DecodeError)) { - let path = ["currentTarget", "nextElementSibling", "firstElementChild"] - use slot <- result.try(decipher.at(path, dynamic)(event)) - use height <- result.try(calculate_slot_height(slot)) +fn handle_click() -> Decoder(Msg) { + use heights <- decode.subfield( + ["currentTarget", "nextElementSibling", "firstElementChild"], + dom.assigned_elements( + dom.bounding_client_rect() |> decode.map(fn(rect) { rect.height }), + lenient: False, + ), + ) + let height = float.sum(heights) - Ok(UserPressedTrigger(height)) + decode.success(UserPressedTrigger(height, event: dynamic.nil())) } -fn handle_keydown(event: Dynamic) -> Result(Msg, List(DecodeError)) { - use key <- result.try(dynamic.field("key", dynamic.string)(event)) +fn handle_keydown() -> Decoder(Msg) { + use event <- decode.then(decode.dynamic) + use key <- decode.field("key", decode.string) case key { "Enter" | " " -> { - let path = ["currentTarget", "nextElementSibling", "firstElementChild"] - use slot <- result.try(decipher.at(path, dynamic)(event)) - use height <- result.try(calculate_slot_height(slot)) - event.prevent_default(event) - - Ok(UserPressedTrigger(height)) + use heights <- decode.subfield( + ["currentTarget", "nextElementSibling", "firstElementChild"], + dom.assigned_elements( + dom.bounding_client_rect() + |> decode.map(fn(rect) { rect.height }), + lenient: False, + ), + ) + let height = float.sum(heights) + + decode.success(UserPressedTrigger(height, event:)) } - _ -> Error([]) + _ -> decode.failure(UserPressedTrigger(0.0, event: dynamic.nil()), "") } } -fn handle_slot_change(event: Dynamic) -> Result(Msg, List(DecodeError)) { - use slot <- result.try(dynamic.field("target", dynamic)(event)) - use height <- result.try(calculate_slot_height(slot)) - - Ok(ParentChangedContent(height)) -} - -fn calculate_slot_height(slot: Dynamic) -> Result(Float, List(DecodeError)) { - use content <- result.try(assigned_elements(slot)) - use heights <- result.try( - dynamic.list(dynamic.field("clientHeight", dynamic.float))(content), +fn handle_slot_change() -> Decoder(Msg) { + use heights <- decode.subfield( + ["currentTarget", "nextElementSibling", "firstElementChild"], + dom.assigned_elements( + dom.bounding_client_rect() |> decode.map(fn(rect) { rect.height }), + lenient: False, + ), ) + let height = float.sum(heights) - Ok(float.sum(heights)) + decode.success(ParentChangedContent(height)) } - -@external(javascript, "../../../dom.ffi.mjs", "assigned_elements") -fn assigned_elements(_slot: Dynamic) -> Result(Dynamic, List(DecodeError)) diff --git a/src/lustre/ui/primitives/popover.gleam b/src/lustre/ui/primitives/popover.gleam index bc1eb38..c991eff 100644 --- a/src/lustre/ui/primitives/popover.gleam +++ b/src/lustre/ui/primitives/popover.gleam @@ -1,19 +1,18 @@ // IMPORTS --------------------------------------------------------------------- -import decipher import gleam/bool -import gleam/dict.{type Dict} -import gleam/dynamic.{type DecodeError, type Decoder, type Dynamic} +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode.{type Decoder} import gleam/json -import gleam/list -import gleam/result import gleam/string import lustre import lustre/attribute.{type Attribute, attribute} +import lustre/component import lustre/effect.{type Effect} import lustre/element.{type Element} import lustre/element/html import lustre/event +import lustre/ffi/dom // TYPES ----------------------------------------------------------------------- @@ -37,7 +36,17 @@ pub type Anchor { pub const name: String = "lustre-ui-popover" pub fn register() -> Result(Nil, lustre.Error) { - let app = lustre.component(init, update, view, on_attribute_change()) + let app = + lustre.component(init, update, view, [ + component.adopt_styles(True), + component.on_attribute_change("aria-expanded", fn(value) { + case value { + "true" | "" -> Ok(ParentSetOpen(True)) + "false" -> Ok(ParentSetOpen(False)) + _ -> Error(Nil) + } + }), + ]) lustre.register(app, name) } @@ -101,28 +110,25 @@ pub fn equal_width() -> Attribute(msg) { /// to override that gap, or remove it entirely. /// pub fn gap(value: String) -> Attribute(msg) { - attribute.style([#("--gap", value)]) + attribute.style("--gap", value) } // EVENTS ---------------------------------------------------------------------- pub fn on_change(handler: fn(Bool) -> msg) -> Attribute(msg) { - use event <- event.on("change") - use is_open <- result.try(decipher.at(["detail", "open"], dynamic.bool)(event)) + event.on("change", { + use is_open <- decode.subfield(["detail", "open"], decode.bool) - Ok(handler(is_open)) + decode.success(handler(is_open)) + }) } pub fn on_open(handler: msg) -> Attribute(msg) { - use _ <- event.on("open") - - Ok(handler) + event.on("open", decode.success(handler)) } pub fn on_close(handler: msg) -> Attribute(msg) { - use _ <- event.on("close") - - Ok(handler) + event.on("close", decode.success(handler)) } // MODEL ----------------------------------------------------------------------- @@ -137,7 +143,7 @@ type Model { fn init(_) -> #(Model, Effect(Msg)) { let model = Collapsed - let effect = effect.batch([set_state("collapsed")]) + let effect = effect.batch([component.set_pseudo_state("collapsed")]) #(model, effect) } @@ -149,112 +155,104 @@ type Msg { ParentSetOpen(Bool) SchedulerDidTick TransitionDidEnd - UserPressedTrigger + UserPressedTrigger(event: Dynamic) } fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { - case msg, model { + case echo msg, model { ParentSetOpen(True), WillCollapse | ParentSetOpen(True), Collapsed -> #( WillExpand, - effect.batch([tick(), set_state("will-expand")]), + effect.batch([tick(), component.set_pseudo_state("will-expand")]), ) + ParentSetOpen(True), _ -> #(model, effect.none()) + ParentSetOpen(False), WillExpand | ParentSetOpen(False), Expanded -> #( WillCollapse, - effect.batch([tick(), set_state("will-collapse")]), + effect.batch([tick(), component.set_pseudo_state("will-collapse")]), ) + ParentSetOpen(False), _ -> #(model, effect.none()) - SchedulerDidTick, WillExpand -> #(Expanded, set_state("expanded")) - SchedulerDidTick, WillCollapse -> #(Collapsing, set_state("collapsing")) + + SchedulerDidTick, WillExpand -> #( + Expanded, + component.set_pseudo_state("expanded"), + ) + + SchedulerDidTick, WillCollapse -> #( + Collapsing, + component.set_pseudo_state("collapsing"), + ) + SchedulerDidTick, _ -> #(model, effect.none()) - TransitionDidEnd, Collapsing -> #(Collapsed, set_state("collapsed")) + + TransitionDidEnd, Collapsing -> #( + Collapsed, + component.set_pseudo_state("collapsed"), + ) + TransitionDidEnd, _ -> #(model, effect.none()) - UserPressedTrigger, WillExpand | UserPressedTrigger, Expanded -> #( + + UserPressedTrigger(event:), WillExpand + | UserPressedTrigger(event:), Expanded + -> #( model, effect.batch([ event.emit("close", json.null()), event.emit("change", json.object([#("open", json.bool(False))])), + dom.prevent_default(event), ]), ) - UserPressedTrigger, WillCollapse - | UserPressedTrigger, Collapsing - | UserPressedTrigger, Collapsed + + UserPressedTrigger(event:), WillCollapse + | UserPressedTrigger(event:), Collapsing + | UserPressedTrigger(event:), Collapsed -> #( model, effect.batch([ event.emit("open", json.null()), event.emit("change", json.object([#("open", json.bool(True))])), + dom.prevent_default(event), ]), ) } } -fn on_attribute_change() -> Dict(String, Decoder(Msg)) { - dict.from_list([ - #("aria-expanded", fn(value) { - value - |> decipher.bool_string - |> result.map(ParentSetOpen) - }), - ]) -} - // EFFECTS --------------------------------------------------------------------- -fn set_state(value: String) -> Effect(msg) { - use _, root <- element.get_root - use state <- list.each([ - "will-expand", "expanded", "will-collapse", "collapsing", "collapsed", - ]) - - case state == value { - True -> do_set_state(value, root) - False -> do_remove_state(state, root) - } -} - -@external(javascript, "../../../dom.ffi.mjs", "set_state") -fn do_set_state(value: String, root: Dynamic) -> Nil - -@external(javascript, "../../../dom.ffi.mjs", "remove_state") -fn do_remove_state(value: String, root: Dynamic) -> Nil - fn tick() -> Effect(Msg) { - use dispatch <- effect.from - use <- after_paint + use dispatch, _ <- effect.after_paint dispatch(SchedulerDidTick) } -@external(javascript, "../../../scheduler.ffi.mjs", "after_paint") -fn after_paint(k: fn() -> Nil) -> Nil - // VIEW ------------------------------------------------------------------------ fn view(model: Model) -> Element(Msg) { - html.div([attribute.style([#("position", "relative")])], [ + html.div([attribute.style("position", "relative")], [ view_trigger(), view_popover(model), ]) } fn view_trigger() -> Element(Msg) { - html.slot([ - attribute.name("trigger"), - event.on_click(UserPressedTrigger), - event.on("keydown", handle_keydown), - ]) + html.slot( + [ + attribute.name("trigger"), + event.on("click", decode.map(decode.dynamic, UserPressedTrigger)), + event.on("keydown", handle_keydown()), + ], + [], + ) } -fn handle_keydown(event: Dynamic) -> Result(Msg, List(DecodeError)) { - use key <- result.try(dynamic.field("key", dynamic.string)(event)) +fn handle_keydown() -> Decoder(Msg) { + use event <- decode.then(decode.dynamic) + use key <- decode.field("key", decode.string) case key { - "Enter" | " " -> { - event.prevent_default(event) - Ok(UserPressedTrigger) - } - _ -> Error([]) + "Enter" | " " -> decode.success(UserPressedTrigger(event:)) + _ -> decode.failure(UserPressedTrigger(event:), "") } } @@ -264,12 +262,8 @@ fn view_popover(model: Model) -> Element(Msg) { html.div( [ attribute("part", "popover-content"), - event.on("transitionend", handle_transitionend), + event.on("transitionend", decode.success(TransitionDidEnd)), ], - [html.slot([attribute.name("popover")])], + [html.slot([attribute.name("popover")], [])], ) } - -fn handle_transitionend(_: Dynamic) -> Result(Msg, List(DecodeError)) { - Ok(TransitionDidEnd) -} diff --git a/src/lustre/ui/reveal.gleam b/src/lustre/ui/reveal.gleam deleted file mode 100644 index df564e3..0000000 --- a/src/lustre/ui/reveal.gleam +++ /dev/null @@ -1,295 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import decipher -import gleam/dict.{type Dict} -import gleam/dynamic.{type DecodeError, type Decoder, type Dynamic, dynamic} -import gleam/float -import gleam/int -import gleam/result -import gleam/string -import lustre -import lustre/attribute.{type Attribute, attribute} -import lustre/effect.{type Effect} -import lustre/element.{type Element} -import lustre/element/html -import lustre/event -import lustre/ui/tween.{tween} - -// // -// PUBLIC API ------------------------------------------------------------------ -// // - -// ELEMENTS -------------------------------------------------------------------- - -// The name of the custom element as rendered in the DOM: "lustre-ui-collapse". -// -pub const name: String = "lustre-ui-reveal" - -// Register the collapse component with the tag name "lustre-ui-collapse". You -// must do this before the component will properly render. -// -pub fn register() -> Result(Nil, lustre.Error) { - case register_intersection_observer() { - // We don't care if the intersection observer had already been registered, - // we just want to make sure it's registered now. - Ok(Nil) | Error(lustre.ComponentAlreadyRegistered(_)) -> { - let app = lustre.component(init, update, view, on_attribute_change()) - lustre.register(app, name) - } - error -> error - } -} - -@external(javascript, "../../dom.ffi.mjs", "register_intersection_observer") -fn register_intersection_observer() -> Result(Nil, lustre.Error) - -// -// The `trigger` element should **not** contain interactive controls such as -// buttons or inputs. -// -// The size of the `content` element is measured whenever it changes (but not -// if its children change) and the collapse will adjust its height accordingly. -// -pub fn element(attributes: List(Attribute(msg))) -> Element(msg) { - element.element(name, attributes, []) -} - -// ATTRIBUTES ------------------------------------------------------------------ - -// -pub fn value(message: String) -> Attribute(msg) { - attribute("value", message) -} - -// -pub fn duration(ms: Int) -> Attribute(msg) { - attribute("duration", int.to_string(ms)) -} - -pub fn easing_function(function: tween.Function) -> Attribute(msg) { - attribute("easing-function", tween.to_string(function)) -} - -// -- -// INTERNALS ------------------------------------------------------------------- -// -- - -// MODEL ----------------------------------------------------------------------- - -type Model { - Model( - message: String, - from: Float, - value: Float, - to: Float, - duration: Float, - now: Float, - start: Float, - target: Float, - function: tween.Function, - can_animate: Bool, - ) -} - -fn init(_) -> #(Model, Effect(Msg)) { - let model = - Model( - message: "", - from: 0.0, - value: 0.0, - to: 0.0, - duration: 5000.0, - now: 0.0, - start: 0.0, - target: 0.0, - function: tween.Exponential(tween.InOut), - can_animate: False, - ) - let effect = effect.none() - - #(model, effect) -} - -// UPDATE ---------------------------------------------------------------------- - -type Msg { - ElementEnteredViewport - ElementExitedViewport - ParentSetDuration(Float) - ParentSetFunction(tween.Function) - ParentSetTarget(String, Float) - SchedulerNextTick(Float) -} - -fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { - case msg { - ElementEnteredViewport -> { - let model = Model(..model, can_animate: True) - let effect = after_paint() - - #(model, effect) - } - - ElementExitedViewport -> { - let model = Model(..model, can_animate: False) - let effect = effect.none() - - #(model, effect) - } - - ParentSetDuration(duration) -> { - let model = - Model(..model, duration: duration, target: model.start +. duration) - let effect = effect.none() - - #(model, effect) - } - - ParentSetFunction(function) -> { - let model = Model(..model, function: function) - let effect = effect.none() - - #(model, effect) - } - - ParentSetTarget(message, start) if model.duration <. 1.0 -> { - let to = string.length(message) |> int.to_float - let model = - Model( - ..model, - message:, - to:, - value: to, - start:, - target: start, - now: start, - ) - - let effect = effect.none() - - #(model, effect) - } - - ParentSetTarget(message, start) -> { - let to = string.length(message) |> int.to_float - let model = - Model( - ..model, - message:, - from: 0.0, - value: 0.0, - to: to, - start: start, - target: start +. model.duration, - now: start, - ) - - let effect = case model.can_animate { - True -> after_paint() - False -> effect.none() - } - - #(model, effect) - } - - SchedulerNextTick(now) if model.start >. model.target -> { - let model = Model(..model, now:) - let effect = effect.none() - - #(model, effect) - } - - SchedulerNextTick(now) -> { - let t = { now -. model.start } /. { model.target -. model.start } - let t = float.min(t, 1.0) - let value = tween(t, model.from, model.to, model.function) - let model = Model(..model, now: now, value: value) - let effect = case t >=. 1.0 { - True -> effect.none() - False if model.can_animate -> after_paint() - False -> effect.none() - } - - #(model, effect) - } - } -} - -fn after_paint() -> Effect(Msg) { - use dispatch <- effect.from - use timestamp <- do_after_paint - - dispatch(SchedulerNextTick(timestamp)) -} - -@external(javascript, "../../scheduler.ffi.mjs", "after_paint") -fn do_after_paint(k: fn(Float) -> Nil) -> Nil - -fn on_attribute_change() -> Dict(String, Decoder(Msg)) { - dict.from_list([ - // - #("value", fn(value) { - let now = animation_time() - - value - |> dynamic.string - |> result.map(ParentSetTarget(_, now)) - }), - // - #("duration", fn(duration) { - duration - |> dynamic.any([decipher.int_string, dynamic.int]) - |> result.map(int.to_float) - |> result.map(ParentSetDuration) - }), - // - #("easing-function", fn(function) { - function - |> dynamic.string - |> result.then(fn(function) { - case tween.from_string(function) { - Ok(function) -> Ok(ParentSetFunction(function)) - Error(_) -> Error([]) - } - }) - }), - ]) -} - -@external(javascript, "../../dom.ffi.mjs", "animation_time") -fn animation_time() -> Float - -// VIEW ------------------------------------------------------------------------ - -fn view(model: Model) -> Element(Msg) { - let target = float.round(model.target) - let value = float.round(model.value) - - element.element( - "lustre-ui-intersection-observer", - [event.on("intersection", handle_intersection)], - [ - value - |> int.min(target) - |> string.slice(model.message, 0, _) - |> html.text, - ], - ) -} - -fn handle_intersection(event: Dynamic) -> Result(Msg, List(DecodeError)) { - use is_intersecting <- result.try(decipher.at( - ["detail", "isIntersecting"], - dynamic.bool, - )(event)) - use ratio <- result.try(decipher.at( - ["detail", "intersectionRatio"], - dynamic.float, - )(event)) - - case is_intersecting { - True if ratio >=. 0.8 -> Ok(ElementEnteredViewport) - False -> Ok(ElementExitedViewport) - _ -> Error([]) - } -} diff --git a/src/lustre/ui/theme.gleam b/src/lustre/ui/theme.gleam index d81cc8b..ef7e7f3 100644 --- a/src/lustre/ui/theme.gleam +++ b/src/lustre/ui/theme.gleam @@ -1,18 +1,19 @@ // IMPORTS --------------------------------------------------------------------- -import gleam/dynamic.{type DecodeError, type Dynamic, DecodeError} +import gleam/dynamic/decode.{type Decoder} import gleam/float import gleam/int import gleam/json.{type Json} import gleam/option.{type Option, None, Some} import gleam/pair -import gleam/result import gleam/string -import gleam_community/colour.{type Colour} as gleam_community_colour +import gleam_community/colour as gleam_community_colour import lustre/attribute.{attribute} import lustre/element.{type Element} import lustre/element/html -import lustre/ui/colour.{type ColourPalette, type ColourScale, ColourPalette} +import lustre/ui/colour.{ + type Colour, type ColourPalette, type ColourScale, ColourPalette, +} // TYPES ----------------------------------------------------------------------- @@ -25,7 +26,7 @@ import lustre/ui/colour.{type ColourPalette, type ColourScale, ColourPalette} /// to colour a span of text with the primary colour of your theme by doing: /// /// ```gleam -/// html.span([attribute.style([#("color", theme.primary.solid_text)])], [ +/// html.span([attribute.style("color", theme.primary.solid_text)], [ /// html.text("Hello, world!") /// ]) /// ``` @@ -88,6 +89,7 @@ pub type SizeScale { /// pub type Selector { Global + Host Class(String) DataAttribute(String, String) } @@ -169,6 +171,18 @@ pub fn default() -> Theme { ) } +pub fn constant_size(base: Float) -> SizeScale { + SizeScale( + xs: base, + sm: base, + md: base, + lg: base, + xl: base, + xl_2: base, + xl_3: base, + ) +} + pub fn perfect_fourth(base: Float) -> SizeScale { SizeScale( xs: base /. 1.333 /. 1.333 /. 1.333, @@ -219,6 +233,11 @@ pub fn golden_ratio(base: Float) -> SizeScale { // MANIPULATIONS --------------------------------------------------------------- +pub fn with_scope(theme: Theme, selector: Selector) -> Theme { + Theme(..theme, selector:) +} + +/// /// Set all of the fonts in a theme at once. /// pub fn with_fonts(theme: Theme, fonts: Fonts) -> Theme { @@ -351,9 +370,10 @@ pub fn with_dark_palette( pub fn with_dark_base_scale(theme: Theme, scale: ColourScale) -> Theme { Theme( ..theme, - dark: option.map(theme.dark, pair.map_second(_, fn(dark) { - ColourPalette(..dark, base: scale) - })), + dark: option.map( + theme.dark, + pair.map_second(_, fn(dark) { ColourPalette(..dark, base: scale) }), + ), ) } @@ -367,9 +387,10 @@ pub fn with_dark_base_scale(theme: Theme, scale: ColourScale) -> Theme { pub fn with_dark_primary_scale(theme: Theme, scale: ColourScale) -> Theme { Theme( ..theme, - dark: option.map(theme.dark, pair.map_second(_, fn(dark) { - ColourPalette(..dark, primary: scale) - })), + dark: option.map( + theme.dark, + pair.map_second(_, fn(dark) { ColourPalette(..dark, primary: scale) }), + ), ) } @@ -383,9 +404,10 @@ pub fn with_dark_primary_scale(theme: Theme, scale: ColourScale) -> Theme { pub fn with_dark_secondary_scale(theme: Theme, scale: ColourScale) -> Theme { Theme( ..theme, - dark: option.map(theme.dark, pair.map_second(_, fn(dark) { - ColourPalette(..dark, secondary: scale) - })), + dark: option.map( + theme.dark, + pair.map_second(_, fn(dark) { ColourPalette(..dark, secondary: scale) }), + ), ) } @@ -399,9 +421,10 @@ pub fn with_dark_secondary_scale(theme: Theme, scale: ColourScale) -> Theme { pub fn with_dark_success_scale(theme: Theme, scale: ColourScale) -> Theme { Theme( ..theme, - dark: option.map(theme.dark, pair.map_second(_, fn(dark) { - ColourPalette(..dark, success: scale) - })), + dark: option.map( + theme.dark, + pair.map_second(_, fn(dark) { ColourPalette(..dark, success: scale) }), + ), ) } @@ -415,9 +438,10 @@ pub fn with_dark_success_scale(theme: Theme, scale: ColourScale) -> Theme { pub fn with_dark_warning_scale(theme: Theme, scale: ColourScale) -> Theme { Theme( ..theme, - dark: option.map(theme.dark, pair.map_second(_, fn(dark) { - ColourPalette(..dark, warning: scale) - })), + dark: option.map( + theme.dark, + pair.map_second(_, fn(dark) { ColourPalette(..dark, warning: scale) }), + ), ) } @@ -431,9 +455,10 @@ pub fn with_dark_warning_scale(theme: Theme, scale: ColourScale) -> Theme { pub fn with_dark_danger_scale(theme: Theme, scale: ColourScale) -> Theme { Theme( ..theme, - dark: option.map(theme.dark, pair.map_second(_, fn(dark) { - ColourPalette(..dark, danger: scale) - })), + dark: option.map( + theme.dark, + pair.map_second(_, fn(dark) { ColourPalette(..dark, danger: scale) }), + ), ) } @@ -665,6 +690,7 @@ ${selector}${dark_selector}, ${selector} ${dark_selector} { fn to_css_selector(selector: Selector) -> String { case selector { Global -> "" + Host -> ":root, :host" Class(class) -> "." <> class DataAttribute(name, "") -> "[data-" <> name <> "]" DataAttribute(name, value) -> "[data-" <> name <> "=" <> value <> "]" @@ -799,6 +825,7 @@ pub fn encode(theme: Theme) -> Json { fn encode_selector(selector: Selector) -> Json { case selector { Global -> json.object([#("kind", json.string("Global"))]) + Host -> json.object([#("kind", json.string("Host"))]) Class(class) -> json.object([ #("kind", json.string("Class")), @@ -833,67 +860,67 @@ fn encode_sizes(sizes: SizeScale) -> Json { ]) } -pub fn decoder(json: Dynamic) -> Result(Theme, List(DecodeError)) { - dynamic.decode7( - Theme, - dynamic.field("id", dynamic.string), - dynamic.field("selector", selector_decoder), - dynamic.field("font", fonts_decoder), - dynamic.field("radius", sizes_decoder), - dynamic.field("space", sizes_decoder), - dynamic.field("light", colour.palette_decoder), - dynamic.field( - "dark", - dynamic.optional(dynamic.tuple2(selector_decoder, colour.palette_decoder)), - ), - )(json) +pub fn decoder() -> Decoder(Theme) { + use id <- decode.field("id", decode.string) + use selector <- decode.field("selector", selector_decoder()) + use font <- decode.field("font", fonts_decoder()) + use radius <- decode.field("radius", sizes_decoder()) + use space <- decode.field("space", sizes_decoder()) + use light <- decode.field("light", colour.palette_decoder()) + use dark <- decode.field( + "dark", + decode.optional({ + use selector <- decode.field(0, selector_decoder()) + use palette <- decode.field(1, colour.palette_decoder()) + + decode.success(#(selector, palette)) + }), + ) + + decode.success(Theme(id:, selector:, font:, radius:, space:, light:, dark:)) } -fn selector_decoder(json: Dynamic) -> Result(Selector, List(DecodeError)) { - use kind <- result.try(dynamic.field("kind", dynamic.string)(json)) +fn selector_decoder() -> Decoder(Selector) { + use kind <- decode.field("kind", decode.string) case kind { - "Global" -> Ok(Global) - "Class" -> - json |> dynamic.decode1(Class, dynamic.field("class", dynamic.string)) - "DataAttribute" -> - json - |> dynamic.decode2( - DataAttribute, - dynamic.field("name", dynamic.string), - dynamic.field("value", dynamic.string), - ) - _ -> - Error([ - DecodeError( - expected: "'Global' | 'Class' | 'DataAttribute'", - found: kind, - path: ["kind"], - ), - ]) + "Global" -> decode.success(Global) + "Host" -> decode.success(Host) + "Class" -> { + use class <- decode.field("class", decode.string) + + decode.success(Class(class)) + } + + "DataAttribute" -> { + use name <- decode.field("name", decode.string) + use value <- decode.field("value", decode.string) + + decode.success(DataAttribute(name, value)) + } + + _ -> decode.failure(Global, "") } } -fn fonts_decoder(json: Dynamic) -> Result(Fonts, List(DecodeError)) { - dynamic.decode3( - Fonts, - dynamic.field("heading", dynamic.string), - dynamic.field("body", dynamic.string), - dynamic.field("code", dynamic.string), - )(json) -} - -fn sizes_decoder(json: Dynamic) -> Result(SizeScale, List(DecodeError)) { - dynamic.decode7( - SizeScale, - dynamic.field("xs", dynamic.float), - dynamic.field("sm", dynamic.float), - dynamic.field("md", dynamic.float), - dynamic.field("lg", dynamic.float), - dynamic.field("xl", dynamic.float), - dynamic.field("xl-2", dynamic.float), - dynamic.field("xl-3", dynamic.float), - )(json) +fn fonts_decoder() -> Decoder(Fonts) { + use heading <- decode.field("heading", decode.string) + use body <- decode.field("body", decode.string) + use code <- decode.field("code", decode.string) + + decode.success(Fonts(heading:, body:, code:)) +} + +fn sizes_decoder() -> Decoder(SizeScale) { + use xs <- decode.field("xs", decode.float) + use sm <- decode.field("sm", decode.float) + use md <- decode.field("md", decode.float) + use lg <- decode.field("lg", decode.float) + use xl <- decode.field("xl", decode.float) + use xl_2 <- decode.field("xl_2", decode.float) + use xl_3 <- decode.field("xl_3", decode.float) + + decode.success(SizeScale(xs:, sm:, md:, lg:, xl:, xl_2:, xl_3:)) } // THEME TOKENS --------------------------------------------------------------- diff --git a/src/lustre/ui/ticker.gleam b/src/lustre/ui/ticker.gleam deleted file mode 100644 index 552e5de..0000000 --- a/src/lustre/ui/ticker.gleam +++ /dev/null @@ -1,275 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import decipher -import gleam/dict.{type Dict} -import gleam/dynamic.{type DecodeError, type Decoder, type Dynamic, dynamic} -import gleam/float -import gleam/int -import gleam/result -import lustre -import lustre/attribute.{type Attribute, attribute} -import lustre/effect.{type Effect} -import lustre/element.{type Element} -import lustre/element/html -import lustre/event -import lustre/ui/tween.{tween} - -// // -// PUBLIC API ------------------------------------------------------------------ -// // - -// ELEMENTS -------------------------------------------------------------------- - -// The name of the custom element as rendered in the DOM: "lustre-ui-collapse". -// -pub const name: String = "lustre-ui-ticker" - -// Register the collapse component with the tag name "lustre-ui-collapse". You -// must do this before the component will properly render. -// -pub fn register() -> Result(Nil, lustre.Error) { - case register_intersection_observer() { - // We don't care if the intersection observer had already been registered, - // we just want to make sure it's registered now. - Ok(Nil) | Error(lustre.ComponentAlreadyRegistered(_)) -> { - let app = lustre.component(init, update, view, on_attribute_change()) - lustre.register(app, name) - } - error -> error - } -} - -@external(javascript, "../../dom.ffi.mjs", "register_intersection_observer") -fn register_intersection_observer() -> Result(Nil, lustre.Error) - -// -// The `trigger` element should **not** contain interactive controls such as -// buttons or inputs. -// -// The size of the `content` element is measured whenever it changes (but not -// if its children change) and the collapse will adjust its height accordingly. -// -pub fn element(attributes: List(Attribute(msg))) -> Element(msg) { - element.element(name, attributes, []) -} - -// ATTRIBUTES ------------------------------------------------------------------ - -// -pub fn value(num: Int) -> Attribute(msg) { - attribute("value", int.to_string(num)) -} - -// -pub fn duration(ms: Int) -> Attribute(msg) { - attribute("duration", int.to_string(ms)) -} - -// -- -// INTERNALS ------------------------------------------------------------------- -// -- - -// MODEL ----------------------------------------------------------------------- - -type Model { - Model( - from: Float, - value: Float, - to: Float, - duration: Float, - now: Float, - start: Float, - target: Float, - function: tween.Function, - can_animate: Bool, - ) -} - -fn init(_) -> #(Model, Effect(Msg)) { - let model = - Model( - from: 0.0, - value: 0.0, - to: 0.0, - duration: 1000.0, - now: 0.0, - start: 0.0, - target: 0.0, - function: tween.Exponential(tween.InOut), - can_animate: False, - ) - let effect = effect.none() - - #(model, effect) -} - -// UPDATE ---------------------------------------------------------------------- - -type Msg { - ElementEnteredViewport - ElementExitedViewport - ParentSetDuration(Float) - ParentSetFunction(tween.Function) - ParentSetTarget(Int, Float) - SchedulerNextTick(Float) -} - -fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { - case msg { - ElementEnteredViewport -> { - let model = Model(..model, can_animate: True) - let effect = after_paint() - - #(model, effect) - } - - ElementExitedViewport -> { - let model = Model(..model, can_animate: False) - let effect = effect.none() - - #(model, effect) - } - - ParentSetDuration(duration) -> { - let target = model.start +. duration - let model = Model(..model, target:, duration:) - let effect = case model.can_animate { - True -> after_paint() - False -> effect.none() - } - - #(model, effect) - } - - ParentSetFunction(function) -> { - let model = Model(..model, function:) - let effect = effect.none() - - #(model, effect) - } - - ParentSetTarget(to, start) if model.duration <. 1.0 -> { - let to = int.to_float(to) - let model = - Model(..model, to:, value: to, start:, target: start, now: start) - let effect = effect.none() - - #(model, effect) - } - - ParentSetTarget(to, start) -> { - let to = int.to_float(to) - let model = - Model( - ..model, - to: to, - start: start, - target: start +. model.duration, - now: start, - ) - - let effect = case model.can_animate { - True -> after_paint() - False -> effect.none() - } - - #(model, effect) - } - - SchedulerNextTick(now) if model.start >. model.target -> { - let model = Model(..model, now:) - let effect = effect.none() - - #(model, effect) - } - - SchedulerNextTick(now) -> { - let t = { now -. model.start } /. { model.target -. model.start } - let t = float.min(t, 1.0) - let value = tween(t, model.from, model.to, model.function) - - let model = Model(..model, now: now, value: value) - let effect = case t >=. 1.0 { - True -> effect.none() - False if model.can_animate -> after_paint() - False -> effect.none() - } - - #(model, effect) - } - } -} - -fn after_paint() -> Effect(Msg) { - use dispatch <- effect.from - use timestamp <- do_after_paint - - dispatch(SchedulerNextTick(timestamp)) -} - -@external(javascript, "../../scheduler.ffi.mjs", "after_paint") -fn do_after_paint(k: fn(Float) -> Nil) -> Nil - -fn on_attribute_change() -> Dict(String, Decoder(Msg)) { - dict.from_list([ - // - #("value", fn(value) { - let now = animation_time() - - value - |> dynamic.any([decipher.int_string, dynamic.int]) - |> result.map(ParentSetTarget(_, now)) - }), - // - #("duration", fn(duration) { - duration - |> dynamic.any([decipher.int_string, dynamic.int]) - |> result.map(int.to_float) - |> result.map(ParentSetDuration) - }), - // - #("easing-function", fn(function) { - function - |> dynamic.string - |> result.then(fn(function) { - case tween.from_string(function) { - Ok(function) -> Ok(ParentSetFunction(function)) - Error(_) -> Error([]) - } - }) - }), - ]) -} - -@external(javascript, "../../dom.ffi.mjs", "animation_time") -fn animation_time() -> Float - -// VIEW ------------------------------------------------------------------------ - -fn view(model: Model) -> Element(Msg) { - let to = float.round(model.to) - let value = float.round(model.value) - - element.element( - "lustre-ui-intersection-observer", - [event.on("intersection", handle_intersection)], - [value |> int.min(to) |> int.to_string |> html.text], - ) -} - -fn handle_intersection(event: Dynamic) -> Result(Msg, List(DecodeError)) { - use is_intersecting <- result.try(decipher.at( - ["detail", "isIntersecting"], - dynamic.bool, - )(event)) - use ratio <- result.try(decipher.at( - ["detail", "intersectionRatio"], - dynamic.float, - )(event)) - - case is_intersecting { - True if ratio >=. 0.8 -> Ok(ElementEnteredViewport) - False -> Ok(ElementExitedViewport) - _ -> Error([]) - } -} diff --git a/src/lustre/ui/tween.gleam b/src/lustre/ui/tween.gleam deleted file mode 100644 index 572c564..0000000 --- a/src/lustre/ui/tween.gleam +++ /dev/null @@ -1,575 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import gleam/bool -import gleam/float -import gleam/result -import gleam_community/maths/elementary.{cos, pi, sin} - -// TYPES ----------------------------------------------------------------------- - -/// The type of easing function to use. -/// -pub type Function { - Linear - Sine(Kind) - Quadratic(Kind) - Cubic(Kind) - Quartic(Kind) - Quintic(Kind) - Exponential(Kind) - Circular(Kind) - Elastic(Kind) - Back(Kind) - Bounce(Kind) -} - -pub type Kind { - In - Out - InOut -} - -pub fn tween(at: Float, from: Float, to: Float, using: Function) -> Float { - case using { - Linear -> linear(at, from, to) - Sine(kind) -> sine(at, from, to, kind) - Quadratic(kind) -> quadratic(at, from, to, kind) - Cubic(kind) -> cubic(at, from, to, kind) - Quartic(kind) -> quartic(at, from, to, kind) - Quintic(kind) -> quintic(at, from, to, kind) - Exponential(kind) -> exponential(at, from, to, kind) - Circular(kind) -> circular(at, from, to, kind) - Elastic(kind) -> elastic(at, from, to, kind) - Back(kind) -> back(at, from, to, kind) - Bounce(kind) -> bounce(at, from, to, kind) - } -} - -// LINEAR TWEENING ------------------------------------------------------------- - -pub fn linear(at: Float, from: Float, to: Float) -> Float { - from +. { to -. from } *. at -} - -// SINE TWEENING --------------------------------------------------------------- - -pub fn sine(at: Float, from: Float, to: Float, kind: Kind) -> Float { - let t = at - let b = from - let c = to -. from - let d = 1.0 - - case kind { - In -> sine_in(t, b, c, d) - Out -> sine_out(t, b, c, d) - InOut -> sine_in_out(t, b, c, d) - } -} - -fn sine_in(t: Float, b: Float, c: Float, d: Float) -> Float { - c *. -1.0 *. cos(t /. d *. { pi() /. 2.0 }) +. c +. b -} - -fn sine_out(t: Float, b: Float, c: Float, d: Float) -> Float { - c *. sin(t /. d *. { pi() /. 2.0 }) +. b -} - -fn sine_in_out(t: Float, b: Float, c: Float, d: Float) -> Float { - c *. -1.0 /. 2.0 *. { cos(pi() *. t /. d) -. 1.0 } +. b -} - -// QUADRATIC TWEENING --------------------------------------------------------- - -pub fn quadratic(at: Float, from: Float, to: Float, kind: Kind) -> Float { - let t = at - let b = from - let c = to -. from - let d = 1.0 - - case kind { - In -> quadratic_in(t, b, c, d) - Out -> quadratic_out(t, b, c, d) - InOut -> quadratic_in_out(t, b, c, d) - } -} - -fn quadratic_in(t: Float, b: Float, c: Float, d: Float) -> Float { - c *. { t /. d } *. { t /. d } +. b -} - -fn quadratic_out(t: Float, b: Float, c: Float, d: Float) -> Float { - c *. { -1.0 *. t /. d *. { t /. d -. 2.0 } } +. b -} - -fn quadratic_in_out(t: Float, b: Float, c: Float, d: Float) -> Float { - let t = t /. { d /. 2.0 } - - case t <. 1.0 { - True -> c /. 2.0 *. t *. t +. b - False -> { - let t = t -. 1.0 - - c /. -2.0 *. { t *. { t -. 2.0 } -. 1.0 } +. b - } - } -} - -// CUBIC TWEENING ------------------------------------------------------------- - -pub fn cubic(at: Float, from: Float, to: Float, kind: Kind) -> Float { - let t = at - let b = from - let c = to -. from - let d = 1.0 - - case kind { - In -> cubic_in(t, b, c, d) - Out -> cubic_out(t, b, c, d) - InOut -> cubic_in_out(t, b, c, d) - } -} - -fn cubic_in(t: Float, b: Float, c: Float, d: Float) -> Float { - c *. { t /. d } *. { t /. d } *. { t /. d } +. b -} - -fn cubic_out(t: Float, b: Float, c: Float, d: Float) -> Float { - let t = t /. d -. 1.0 - - c *. { t *. t *. t +. 1.0 } +. b -} - -fn cubic_in_out(t: Float, b: Float, c: Float, d: Float) -> Float { - let t = t /. { d /. 2.0 } - - case t <. 1.0 { - True -> c /. 2.0 *. t *. t *. t +. b - False -> { - let t = t -. 2.0 - - c /. 2.0 *. { t *. t *. t +. 2.0 } +. b - } - } -} - -// QUARTIC TWEENING ------------------------------------------------------------ - -pub fn quartic(at: Float, from: Float, to: Float, kind: Kind) -> Float { - let t = at - let b = from - let c = to -. from - let d = 1.0 - - case kind { - In -> quartic_in(t, b, c, d) - Out -> quartic_out(t, b, c, d) - InOut -> quartic_in_out(t, b, c, d) - } -} - -fn quartic_in(t: Float, b: Float, c: Float, d: Float) -> Float { - c *. pow(t /. d, 4.0) +. b -} - -fn quartic_out(t: Float, b: Float, c: Float, d: Float) -> Float { - let t = t /. d -. 1.0 - - c *. { 1.0 -. pow(t, 4.0) } +. b -} - -fn quartic_in_out(t: Float, b: Float, c: Float, d: Float) -> Float { - let t = t /. { d /. 2.0 } - - case t <. 1.0 { - True -> c /. 2.0 *. pow(t, 4.0) +. b - - False -> { - let t = t -. 2.0 - - c /. -2.0 *. { pow(t, 4.0) -. 2.0 } +. b - } - } -} - -// QUINTIC TWEENING ------------------------------------------------------------ - -pub fn quintic(at: Float, from: Float, to: Float, kind: Kind) -> Float { - let t = at - let b = from - let c = to -. from - let d = 1.0 - - case kind { - In -> quintic_in(t, b, c, d) - Out -> quintic_out(t, b, c, d) - InOut -> quintic_in_out(t, b, c, d) - } -} - -fn quintic_in(t: Float, b: Float, c: Float, d: Float) -> Float { - c *. pow(t /. d, 5.0) +. b -} - -fn quintic_out(t: Float, b: Float, c: Float, d: Float) -> Float { - let t = t /. d -. 1.0 - - c *. { pow(t, 5.0) +. 1.0 } +. b -} - -fn quintic_in_out(t: Float, b: Float, c: Float, d: Float) -> Float { - let t = t /. { d /. 2.0 } - - case t <. 1.0 { - True -> c /. 2.0 *. pow(t, 5.0) +. b - - False -> { - let t = t -. 2.0 - - c /. 2.0 *. { pow(t, 5.0) +. 2.0 } +. b - } - } -} - -// EXPONENTIAL TWEENING -------------------------------------------------------- - -pub fn exponential(at: Float, from: Float, to: Float, kind: Kind) -> Float { - let t = at - let b = from - let c = to -. from - let d = 1.0 - - case kind { - In -> exponential_in(t, b, c, d) - Out -> exponential_out(t, b, c, d) - InOut -> exponential_in_out(t, b, c, d) - } -} - -fn exponential_in(t: Float, b: Float, c: Float, d: Float) -> Float { - case t == 0.0 { - True -> b - False -> c *. pow(2.0, 10.0 *. { t /. d -. 1.0 }) +. b - } -} - -fn exponential_out(t: Float, b: Float, c: Float, d: Float) -> Float { - case t == d { - True -> b +. c - False -> c *. { -1.0 *. pow(2.0, -10.0 *. t /. d) +. 1.0 } +. b - } -} - -fn exponential_in_out(t: Float, b: Float, c: Float, d: Float) -> Float { - use <- bool.guard(t == 0.0, b) - use <- bool.guard(t == d, b +. c) - - let t = t /. { d /. 2.0 } - - case t <. 1.0 { - True -> c /. 2.0 *. pow(2.0, 10.0 *. { t -. 1.0 }) +. b - - False -> { - let t = t -. 1.0 - - c /. 2.0 *. { -1.0 *. pow(2.0, -10.0 *. t) +. 2.0 } +. b - } - } -} - -// CIRCULAR TWEENING ----------------------------------------------------------- - -pub fn circular(at: Float, from: Float, to: Float, kind: Kind) -> Float { - let t = at - let b = from - let c = to -. from - let d = 1.0 - - case kind { - In -> circular_in(t, b, c, d) - Out -> circular_out(t, b, c, d) - InOut -> circular_in_out(t, b, c, d) - } -} - -fn circular_in(t: Float, b: Float, c: Float, d: Float) -> Float { - let t = t /. d - - c *. { -1.0 *. { sqrt(1.0 -. t *. t) -. 1.0 } } +. b -} - -fn circular_out(t: Float, b: Float, c: Float, d: Float) -> Float { - let t = t /. d -. 1.0 - - c *. sqrt(1.0 -. t *. t) +. b -} - -fn circular_in_out(t: Float, b: Float, c: Float, d: Float) -> Float { - let t = t /. { d /. 2.0 } - - case t <. 1.0 { - True -> c /. -2.0 *. { sqrt(1.0 -. t *. t) -. 1.0 } +. b - - False -> { - let t = t -. 2.0 - - c /. 2.0 *. { sqrt(1.0 -. t *. t) +. 1.0 } +. b - } - } -} - -// ELASTIC TWEENING ------------------------------------------------------------ - -pub fn elastic(at: Float, from: Float, to: Float, kind: Kind) -> Float { - let t = at - let b = from - let c = to -. from - let d = 1.0 - - case kind { - In -> elastic_in(t, b, c, d) - Out -> elastic_out(t, b, c, d) - InOut -> elastic_in_out(t, b, c, d) - } -} - -fn elastic_in(t: Float, b: Float, c: Float, d: Float) -> Float { - use <- bool.guard(t == 0.0, b) - use <- bool.guard(t /. d == 1.0, b +. c) - - let p = d *. 0.3 - let s = p /. 4.0 - let t = t /. d -. 1.0 - - c - *. -1.0 - *. pow(2.0, 10.0 *. t) - *. sin({ t *. d -. s } *. { 2.0 *. pi() } /. p) - +. b -} - -fn elastic_out(t: Float, b: Float, c: Float, d: Float) -> Float { - use <- bool.guard(t == 0.0, b) - use <- bool.guard(t /. d == 1.0, b +. c) - - let p = d *. 0.3 - let s = p /. 4.0 - - c - *. pow(2.0, -10.0 *. t /. d) - *. sin({ t *. d -. s } *. { 2.0 *. pi() } /. p) - +. c - +. b -} - -fn elastic_in_out(t: Float, b: Float, c: Float, d: Float) -> Float { - use <- bool.guard(t == 0.0, b) - use <- bool.guard(t /. d == 1.0, b +. c) - - let p = d *. { 0.3 *. 1.5 } - let s = p /. 4.0 - let t = t /. { d /. 2.0 } - - case t <. 1.0 { - True -> { - let t = t -. 1.0 - - c - /. -2.0 - *. pow(2.0, 10.0 *. t) - *. sin({ t *. d -. s } *. { 2.0 *. pi() } /. p) - +. b - } - - False -> { - let t = t -. 1.0 - - c - /. 2.0 - *. pow(2.0, -10.0 *. t) - *. sin({ t *. d -. s } *. { 2.0 *. pi() } /. p) - +. c - +. b - } - } -} - -// BACK TWEENING --------------------------------------------------------------- - -pub fn back(at: Float, from: Float, to: Float, kind: Kind) -> Float { - let t = at - let b = from - let c = to -. from - let d = 1.0 - - case kind { - In -> back_in(t, b, c, d) - Out -> back_out(t, b, c, d) - InOut -> back_in_out(t, b, c, d) - } -} - -fn back_in(t: Float, b: Float, c: Float, d: Float) -> Float { - let s = 1.70158 - let t = t /. d - - c *. t *. t *. { { s +. 1.0 } *. t -. s } +. b -} - -fn back_out(t: Float, b: Float, c: Float, d: Float) -> Float { - let s = 1.70158 - let t = t /. d -. 1.0 - - c *. { t *. t *. { { s +. 1.0 } *. t +. s } +. 1.0 } +. b -} - -fn back_in_out(t: Float, b: Float, c: Float, d: Float) -> Float { - let s = 1.70158 *. 1.525 - let t = t /. { d /. 2.0 } - - case t <. 1.0 { - True -> c /. 2.0 *. { t *. t *. { { s +. 1.0 } *. t -. s } } +. b - - False -> { - let t = t -. 2.0 - - c /. 2.0 *. { t *. t *. { { s +. 1.0 } *. t +. s } +. 2.0 } +. b - } - } -} - -// BOUNCE TWEENING ------------------------------------------------------------- - -pub fn bounce(at: Float, from: Float, to: Float, kind: Kind) -> Float { - let t = at - let b = from - let c = to -. from - let d = 1.0 - - case kind { - In -> bounce_in(t, b, c, d) - Out -> bounce_out(t, b, c, d) - InOut -> bounce_in_out(t, b, c, d) - } -} - -fn bounce_in(t: Float, b: Float, c: Float, d: Float) -> Float { - c -. bounce_out(d -. t, 0.0, c, d) +. b -} - -fn bounce_out(t: Float, b: Float, c: Float, d: Float) -> Float { - let t = t /. d - - case t <. 1.0 /. 2.75 { - True -> c *. { 7.5625 *. t *. t } +. b - - False if t <. 2.0 /. 2.75 -> { - let t = t -. { 1.5 /. 2.75 } - - c *. { 7.5625 *. t *. t +. 0.75 } +. b - } - - False if t <. 2.5 /. 2.75 -> { - let t = t -. { 2.25 /. 2.75 } - - c *. { 7.5625 *. t *. t +. 0.9375 } +. b - } - - False -> { - let t = t -. { 2.625 /. 2.75 } - - c *. { 7.5625 *. t *. t +. 0.984375 } +. b - } - } -} - -fn bounce_in_out(t: Float, b: Float, c: Float, d: Float) -> Float { - case t <. d /. 2.0 { - True -> bounce_in(t *. 2.0, 0.0, c, d) *. 0.5 +. b - False -> bounce_out(t *. 2.0 -. d, 0.0, c, d) *. 0.5 +. c *. 0.5 +. b - } -} - -// CONVERSIONS ----------------------------------------------------------------- - -pub fn to_string(function: Function) -> String { - case function { - Linear -> "linear" - Sine(In) -> "sine-in" - Sine(Out) -> "sine-out" - Sine(InOut) -> "sine-in-out" - Quadratic(In) -> "quadratic-in" - Quadratic(Out) -> "quadratic-out" - Quadratic(InOut) -> "quadratic-in-out" - Cubic(In) -> "cubic-in" - Cubic(Out) -> "cubic-out" - Cubic(InOut) -> "cubic-in-out" - Quartic(In) -> "quartic-in" - Quartic(Out) -> "quartic-out" - Quartic(InOut) -> "quartic-in-out" - Quintic(In) -> "quintic-in" - Quintic(Out) -> "quintic-out" - Quintic(InOut) -> "quintic-in-out" - Exponential(In) -> "exponential-in" - Exponential(Out) -> "exponential-out" - Exponential(InOut) -> "exponential-in-out" - Circular(In) -> "circular-in" - Circular(Out) -> "circular-out" - Circular(InOut) -> "circular-in-out" - Elastic(In) -> "elastic-in" - Elastic(Out) -> "elastic-out" - Elastic(InOut) -> "elastic-in-out" - Back(In) -> "back-in" - Back(Out) -> "back-out" - Back(InOut) -> "back-in-out" - Bounce(In) -> "bounce-in" - Bounce(Out) -> "bounce-out" - Bounce(InOut) -> "bounce-in-out" - } -} - -pub fn from_string(function: String) -> Result(Function, Nil) { - case function { - "linear" -> Ok(Linear) - "sine-in" -> Ok(Sine(In)) - "sine-out" -> Ok(Sine(Out)) - "sine-in-out" -> Ok(Sine(InOut)) - "quadratic-in" -> Ok(Quadratic(In)) - "quadratic-out" -> Ok(Quadratic(Out)) - "quadratic-in-out" -> Ok(Quadratic(InOut)) - "cubic-in" -> Ok(Cubic(In)) - "cubic-out" -> Ok(Cubic(Out)) - "cubic-in-out" -> Ok(Cubic(InOut)) - "quartic-in" -> Ok(Quartic(In)) - "quartic-out" -> Ok(Quartic(Out)) - "quartic-in-out" -> Ok(Quartic(InOut)) - "quintic-in" -> Ok(Quintic(In)) - "quintic-out" -> Ok(Quintic(Out)) - "quintic-in-out" -> Ok(Quintic(InOut)) - "exponential-in" -> Ok(Exponential(In)) - "exponential-out" -> Ok(Exponential(Out)) - "exponential-in-out" -> Ok(Exponential(InOut)) - "circular-in" -> Ok(Circular(In)) - "circular-out" -> Ok(Circular(Out)) - "circular-in-out" -> Ok(Circular(InOut)) - "elastic-in" -> Ok(Elastic(In)) - "elastic-out" -> Ok(Elastic(Out)) - "elastic-in-out" -> Ok(Elastic(InOut)) - "back-in" -> Ok(Back(In)) - "back-out" -> Ok(Back(Out)) - "back-in-out" -> Ok(Back(InOut)) - "bounce-in" -> Ok(Bounce(In)) - "bounce-out" -> Ok(Bounce(Out)) - "bounce-in-out" -> Ok(Bounce(InOut)) - _ -> Error(Nil) - } -} - -// UTILS ----------------------------------------------------------------------- - -fn pow(base: Float, exponent: Float) -> Float { - float.power(base, exponent) |> result.unwrap(0.0) -} - -fn sqrt(value: Float) -> Float { - float.square_root(value) |> result.unwrap(0.0) -} diff --git a/src/lustre_ui_components.mjs b/src/lustre_ui_components.mjs new file mode 100644 index 0000000..30f8de2 --- /dev/null +++ b/src/lustre_ui_components.mjs @@ -0,0 +1,11 @@ +import { register as register_collapse } from "../build/dev/javascript/lustre_ui/lustre/ui/primitives/collapse.mjs"; +import { register as register_popover } from "../build/dev/javascript/lustre_ui/lustre/ui/primitives/popover.mjs"; + +import { register as register_accordion } from "../build/dev/javascript/lustre_ui/lustre/ui/accordion.mjs"; +import { register as register_combobox } from "../build/dev/javascript/lustre_ui/lustre/ui/combobox.mjs"; + +register_collapse(); +register_popover(); + +register_accordion(); +register_combobox(); diff --git a/src/scheduler.ffi.mjs b/src/scheduler.ffi.mjs deleted file mode 100644 index 5534f14..0000000 --- a/src/scheduler.ffi.mjs +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @param {() => void} k - A microtask to schedule before the DOM is next painted. - */ -export const before_paint = (k) => { - window.queueMicrotask(k); -}; - -/** - * @param {(timestamp: number) => void} k - A callback to schedule after the DOM - * has been painted. - */ -export const after_paint = (k) => { - window.requestAnimationFrame(k); -}; diff --git a/test/build.gleam b/test/build.gleam deleted file mode 100644 index ba30a2f..0000000 --- a/test/build.gleam +++ /dev/null @@ -1,27 +0,0 @@ -import gleam/list -import gleam/result -import globlin -import simplifile - -pub fn main() { - let assert Ok(files) = simplifile.get_files("./src") - let assert Ok(pattern) = globlin.new_pattern("./**/*.css") - - let assert Ok(css) = - files - |> list.filter(globlin.match_pattern(pattern:, path: _)) - |> list.try_fold("@layer reset, primitives, components;\n\n", fn(css, path) { - use src <- result.map(simplifile.read(path)) - let src = case path { - "./src/lustre/ui/primitives/reset.css" -> - "@layer reset { " <> src <> " }" - "./src/lustre/ui/primitives/" <> _ -> - "@layer primitives { " <> src <> " }" - _ -> "@layer components { " <> src <> " }" - } - - css <> src <> "\n\n" - }) - - let assert Ok(_) = simplifile.write("./priv/static/lustre_ui.css", css) -} diff --git a/test/lustre/ui/tween_test.gleam b/test/lustre/ui/tween_test.gleam deleted file mode 100644 index c406460..0000000 --- a/test/lustre/ui/tween_test.gleam +++ /dev/null @@ -1,200 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import birdie -import gleam/float -import gleam/int -import gleam/list -import gleam/set.{type Set} -import gleam/string -import lustre/ui/tween - -// TESTS ----------------------------------------------------------------------- - -pub fn linear_tween_test() { - use at, from, to <- plot("Tween linear") - tween.linear(at, from, to) -} - -pub fn sine_in_tween_test() { - use at, from, to <- plot("Tween in sine") - tween.sine(at, from, to, tween.In) -} - -pub fn sine_out_tween_test() { - use at, from, to <- plot("Tween out sine") - tween.sine(at, from, to, tween.Out) -} - -pub fn sine_in_out_tween_test() { - use at, from, to <- plot("Tween in out sine") - tween.sine(at, from, to, tween.InOut) -} - -pub fn quad_in_tween_test() { - use at, from, to <- plot("Tween in quadratic") - tween.quadratic(at, from, to, tween.In) -} - -pub fn quad_out_tween_test() { - use at, from, to <- plot("Tween out quadratic") - tween.quadratic(at, from, to, tween.Out) -} - -pub fn quad_in_out_tween_test() { - use at, from, to <- plot("Tween in out quadratic") - tween.quadratic(at, from, to, tween.InOut) -} - -pub fn cubic_in_tween_test() { - use at, from, to <- plot("Tween in cubic") - tween.cubic(at, from, to, tween.In) -} - -pub fn cubic_out_tween_test() { - use at, from, to <- plot("Tween out cubic") - tween.cubic(at, from, to, tween.Out) -} - -pub fn cubic_in_out_tween_test() { - use at, from, to <- plot("Tween in out cubic") - tween.cubic(at, from, to, tween.InOut) -} - -pub fn quart_in_tween_test() { - use at, from, to <- plot("Tween in quartic") - tween.quartic(at, from, to, tween.In) -} - -pub fn quart_out_tween_test() { - use at, from, to <- plot("Tween out quartic") - tween.quartic(at, from, to, tween.Out) -} - -pub fn quart_in_out_tween_test() { - use at, from, to <- plot("Tween in out quartic") - tween.quartic(at, from, to, tween.InOut) -} - -pub fn quint_in_tween_test() { - use at, from, to <- plot("Tween in Quint") - tween.quintic(at, from, to, tween.In) -} - -pub fn quint_out_tween_test() { - use at, from, to <- plot("Tween out Quint") - tween.quintic(at, from, to, tween.Out) -} - -pub fn quint_in_out_tween_test() { - use at, from, to <- plot("Tween in out Quint") - tween.quintic(at, from, to, tween.InOut) -} - -pub fn expo_in_tween_test() { - use at, from, to <- plot("Tween in Expo") - tween.exponential(at, from, to, tween.In) -} - -pub fn expo_out_tween_test() { - use at, from, to <- plot("Tween out Expo") - tween.exponential(at, from, to, tween.Out) -} - -pub fn expo_in_out_tween_test() { - use at, from, to <- plot("Tween in out Expo") - tween.exponential(at, from, to, tween.InOut) -} - -pub fn circ_in_tween_test() { - use at, from, to <- plot("Tween in Circ") - tween.circular(at, from, to, tween.In) -} - -pub fn circ_out_tween_test() { - use at, from, to <- plot("Tween out Circ") - tween.circular(at, from, to, tween.Out) -} - -pub fn circ_in_out_tween_test() { - use at, from, to <- plot("Tween in out Circ") - tween.circular(at, from, to, tween.InOut) -} - -pub fn back_in_tween_test() { - use at, from, to <- plot("Tween in Back") - tween.back(at, from, to, tween.In) -} - -pub fn back_out_tween_test() { - use at, from, to <- plot("Tween out Back") - tween.back(at, from, to, tween.Out) -} - -pub fn back_in_out_tween_test() { - use at, from, to <- plot("Tween in out Back") - tween.back(at, from, to, tween.InOut) -} - -pub fn elastic_in_tween_test() { - use at, from, to <- plot("Tween in Elastic") - tween.elastic(at, from, to, tween.In) -} - -pub fn elastic_out_tween_test() { - use at, from, to <- plot("Tween out Elastic") - tween.elastic(at, from, to, tween.Out) -} - -pub fn elastic_in_out_tween_test() { - use at, from, to <- plot("Tween in out Elastic") - tween.elastic(at, from, to, tween.InOut) -} - -pub fn bounce_in_tween_test() { - use at, from, to <- plot("Tween in Bounce") - tween.bounce(at, from, to, tween.In) -} - -pub fn bounce_out_tween_test() { - use at, from, to <- plot("Tween out Bounce") - tween.bounce(at, from, to, tween.Out) -} - -pub fn bounce_in_out_tween_test() { - use at, from, to <- plot("Tween in out Bounce") - tween.bounce(at, from, to, tween.InOut) -} - -// HELPERS --------------------------------------------------------------------- - -const steps = 35 - -/// Plots a function and then take a snapshot using birdie. -fn plot(title: String, function: fn(Float, Float, Float) -> Float) -> Nil { - function - |> make_plot - |> show_plot - |> birdie.snap(title) -} - -fn make_plot(function: fn(Float, Float, Float) -> Float) -> Set(#(Int, Int)) { - use plot, x <- list.fold(list.range(0, steps), set.new()) - let at = int.to_float(x) /. int.to_float(steps) - let y = function(at, 0.0, int.to_float(steps)) - - set.insert(plot, #(x, float.round(y))) -} - -fn show_plot(points: Set(#(Int, Int))) -> String { - let lines = { - use y <- list.map(list.range(steps, 0)) - use line, x <- list.fold(list.range(0, steps), "") - - case set.contains(points, #(x, y)) { - True -> line <> "◍" - False -> line <> " " - } - } - - string.join(lines, "\n") -} From f334a543dde1d25d8e3193429bf6d54e2944e39b Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Sat, 19 Jul 2025 06:13:30 +0100 Subject: [PATCH 07/56] :fire: Blank slate. --- assets/diagram-alert.svg | 505 ---- birdie_snapshots/tween_in_back.accepted | 40 - birdie_snapshots/tween_in_bounce.accepted | 40 - birdie_snapshots/tween_in_circ.accepted | 40 - birdie_snapshots/tween_in_cubic.accepted | 40 - birdie_snapshots/tween_in_elastic.accepted | 40 - birdie_snapshots/tween_in_expo.accepted | 40 - birdie_snapshots/tween_in_out_back.accepted | 40 - birdie_snapshots/tween_in_out_bounce.accepted | 40 - birdie_snapshots/tween_in_out_circ.accepted | 40 - birdie_snapshots/tween_in_out_cubic.accepted | 40 - .../tween_in_out_elastic.accepted | 40 - birdie_snapshots/tween_in_out_expo.accepted | 40 - .../tween_in_out_quadratic.accepted | 40 - .../tween_in_out_quartic.accepted | 40 - birdie_snapshots/tween_in_out_quint.accepted | 40 - birdie_snapshots/tween_in_out_sine.accepted | 40 - birdie_snapshots/tween_in_quadratic.accepted | 40 - birdie_snapshots/tween_in_quartic.accepted | 40 - birdie_snapshots/tween_in_quint.accepted | 40 - birdie_snapshots/tween_in_sine.accepted | 40 - birdie_snapshots/tween_linear.accepted | 40 - birdie_snapshots/tween_out_back.accepted | 40 - birdie_snapshots/tween_out_bounce.accepted | 40 - birdie_snapshots/tween_out_circ.accepted | 40 - birdie_snapshots/tween_out_cubic.accepted | 40 - birdie_snapshots/tween_out_elastic.accepted | 40 - birdie_snapshots/tween_out_expo.accepted | 40 - birdie_snapshots/tween_out_quadratic.accepted | 40 - birdie_snapshots/tween_out_quartic.accepted | 40 - birdie_snapshots/tween_out_quint.accepted | 40 - birdie_snapshots/tween_out_sine.accepted | 40 - dev/build.gleam | 71 +- dev/lustre/ui/accordion_stories.gleam | 27 - dev/lustre/ui/alert_stories.gleam | 43 - dev/lustre/ui/badge_stories.gleam | 36 - dev/lustre/ui/breadcrumb_stories.gleam | 46 - dev/lustre/ui/button_stories.gleam | 32 - dev/lustre/ui/card_stories.gleam | 49 - dev/lustre/ui/combobox_stories.gleam | 35 - dev/lustre/ui/divider_stories.gleam | 52 - dev/lustre_ui_storybook.gleam | 25 +- gleam.toml | 12 +- manifest.toml | 10 - pages/01-getting-started.md | 225 -- pages/02-elements-vs-components.md | 236 -- pages/components.md | 59 - src/lustre/ffi/dom.ffi.mjs | 72 - src/lustre/ffi/dom.gleam | 138 - src/lustre/ui/accordion.css | 51 - src/lustre/ui/accordion.gleam | 631 ----- src/lustre/ui/alert.css | 49 - src/lustre/ui/alert.gleam | 383 --- src/lustre/ui/badge.css | 66 - src/lustre/ui/badge.gleam | 291 --- src/lustre/ui/breadcrumb.css | 67 - src/lustre/ui/breadcrumb.gleam | 234 -- src/lustre/ui/button.css | 143 -- src/lustre/ui/button.gleam | 403 --- src/lustre/ui/card.css | 31 - src/lustre/ui/card.gleam | 201 -- src/lustre/ui/checkbox.css | 51 - src/lustre/ui/checkbox.gleam | 80 - src/lustre/ui/colour.gleam | 1416 ----------- src/lustre/ui/combobox.css | 86 - src/lustre/ui/combobox.gleam | 776 ------ src/lustre/ui/data/bidict.gleam | 108 - src/lustre/ui/divider.css | 47 - src/lustre/ui/divider.gleam | 122 - src/lustre/ui/input.css | 59 - src/lustre/ui/input.gleam | 21 - src/lustre/ui/primitives/collapse.css | 12 - src/lustre/ui/primitives/collapse.gleam | 232 -- src/lustre/ui/primitives/icon.css | 4 - src/lustre/ui/primitives/icon.gleam | 2241 ----------------- src/lustre/ui/primitives/popover.css | 181 -- src/lustre/ui/primitives/popover.gleam | 269 -- src/lustre/ui/primitives/reset.css | 246 -- src/lustre/ui/theme.gleam | 1165 --------- src/lustre_ui_components.mjs | 11 - 80 files changed, 4 insertions(+), 12586 deletions(-) delete mode 100644 assets/diagram-alert.svg delete mode 100644 birdie_snapshots/tween_in_back.accepted delete mode 100644 birdie_snapshots/tween_in_bounce.accepted delete mode 100644 birdie_snapshots/tween_in_circ.accepted delete mode 100644 birdie_snapshots/tween_in_cubic.accepted delete mode 100644 birdie_snapshots/tween_in_elastic.accepted delete mode 100644 birdie_snapshots/tween_in_expo.accepted delete mode 100644 birdie_snapshots/tween_in_out_back.accepted delete mode 100644 birdie_snapshots/tween_in_out_bounce.accepted delete mode 100644 birdie_snapshots/tween_in_out_circ.accepted delete mode 100644 birdie_snapshots/tween_in_out_cubic.accepted delete mode 100644 birdie_snapshots/tween_in_out_elastic.accepted delete mode 100644 birdie_snapshots/tween_in_out_expo.accepted delete mode 100644 birdie_snapshots/tween_in_out_quadratic.accepted delete mode 100644 birdie_snapshots/tween_in_out_quartic.accepted delete mode 100644 birdie_snapshots/tween_in_out_quint.accepted delete mode 100644 birdie_snapshots/tween_in_out_sine.accepted delete mode 100644 birdie_snapshots/tween_in_quadratic.accepted delete mode 100644 birdie_snapshots/tween_in_quartic.accepted delete mode 100644 birdie_snapshots/tween_in_quint.accepted delete mode 100644 birdie_snapshots/tween_in_sine.accepted delete mode 100644 birdie_snapshots/tween_linear.accepted delete mode 100644 birdie_snapshots/tween_out_back.accepted delete mode 100644 birdie_snapshots/tween_out_bounce.accepted delete mode 100644 birdie_snapshots/tween_out_circ.accepted delete mode 100644 birdie_snapshots/tween_out_cubic.accepted delete mode 100644 birdie_snapshots/tween_out_elastic.accepted delete mode 100644 birdie_snapshots/tween_out_expo.accepted delete mode 100644 birdie_snapshots/tween_out_quadratic.accepted delete mode 100644 birdie_snapshots/tween_out_quartic.accepted delete mode 100644 birdie_snapshots/tween_out_quint.accepted delete mode 100644 birdie_snapshots/tween_out_sine.accepted delete mode 100644 dev/lustre/ui/accordion_stories.gleam delete mode 100644 dev/lustre/ui/alert_stories.gleam delete mode 100644 dev/lustre/ui/badge_stories.gleam delete mode 100644 dev/lustre/ui/breadcrumb_stories.gleam delete mode 100644 dev/lustre/ui/button_stories.gleam delete mode 100644 dev/lustre/ui/card_stories.gleam delete mode 100644 dev/lustre/ui/combobox_stories.gleam delete mode 100644 dev/lustre/ui/divider_stories.gleam delete mode 100644 pages/01-getting-started.md delete mode 100644 pages/02-elements-vs-components.md delete mode 100644 pages/components.md delete mode 100644 src/lustre/ffi/dom.ffi.mjs delete mode 100644 src/lustre/ffi/dom.gleam delete mode 100644 src/lustre/ui/accordion.css delete mode 100644 src/lustre/ui/accordion.gleam delete mode 100644 src/lustre/ui/alert.css delete mode 100644 src/lustre/ui/alert.gleam delete mode 100644 src/lustre/ui/badge.css delete mode 100644 src/lustre/ui/badge.gleam delete mode 100644 src/lustre/ui/breadcrumb.css delete mode 100644 src/lustre/ui/breadcrumb.gleam delete mode 100644 src/lustre/ui/button.css delete mode 100644 src/lustre/ui/button.gleam delete mode 100644 src/lustre/ui/card.css delete mode 100644 src/lustre/ui/card.gleam delete mode 100644 src/lustre/ui/checkbox.css delete mode 100644 src/lustre/ui/checkbox.gleam delete mode 100644 src/lustre/ui/colour.gleam delete mode 100644 src/lustre/ui/combobox.css delete mode 100644 src/lustre/ui/combobox.gleam delete mode 100644 src/lustre/ui/data/bidict.gleam delete mode 100644 src/lustre/ui/divider.css delete mode 100644 src/lustre/ui/divider.gleam delete mode 100644 src/lustre/ui/input.css delete mode 100644 src/lustre/ui/input.gleam delete mode 100644 src/lustre/ui/primitives/collapse.css delete mode 100644 src/lustre/ui/primitives/collapse.gleam delete mode 100644 src/lustre/ui/primitives/icon.css delete mode 100644 src/lustre/ui/primitives/icon.gleam delete mode 100644 src/lustre/ui/primitives/popover.css delete mode 100644 src/lustre/ui/primitives/popover.gleam delete mode 100644 src/lustre/ui/primitives/reset.css delete mode 100644 src/lustre/ui/theme.gleam delete mode 100644 src/lustre_ui_components.mjs diff --git a/assets/diagram-alert.svg b/assets/diagram-alert.svg deleted file mode 100644 index b310d57..0000000 --- a/assets/diagram-alert.svg +++ /dev/null @@ -1,505 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Alert - - -   - - - title - - - - - Alert - - -   - - - title - - - - - - - - - - - - - - - - - - - - - - - Alert - - -   - - - description... - - - - - Alert - - -   - - - description... - - - - - - - - - - - - - - - - - - - - - - - alert.indicator - - - - - alert.indicator - - - - - - - alert.title - - - - - alert.title - - - - - - - alert.content - - - - - alert.content - - - - - - - - - - - - - - - - - - - - - - - alert - - - - - alert - - - - diff --git a/birdie_snapshots/tween_in_back.accepted b/birdie_snapshots/tween_in_back.accepted deleted file mode 100644 index 76da680..0000000 --- a/birdie_snapshots/tween_in_back.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in Back ---- - ◍ - - - - - ◍ - - - - ◍ - - - - ◍ - - - ◍ - - - ◍ - - - ◍ - - - ◍ - - ◍ - - ◍ - - ◍ - - ◍ - ◍ -◍◍◍◍ ◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_bounce.accepted b/birdie_snapshots/tween_in_bounce.accepted deleted file mode 100644 index 886643e..0000000 --- a/birdie_snapshots/tween_in_bounce.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in Bounce ---- - ◍◍ - ◍ - ◍ - ◍ - - ◍ - - - ◍ - - - ◍ - - - ◍ - - - - ◍ - - - - ◍ - - - - ◍◍ ◍ - ◍ ◍◍ - ◍ ◍ - - ◍ ◍ - ◍ ◍ - ◍ - ◍◍◍◍ - ◍ ◍ ◍◍ ◍ -◍◍ ◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_circ.accepted b/birdie_snapshots/tween_in_circ.accepted deleted file mode 100644 index 495fb10..0000000 --- a/birdie_snapshots/tween_in_circ.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in Circ ---- - ◍ - - - - - - - - ◍ - - - - ◍ - - ◍ - - ◍ - - ◍ - - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍◍ - ◍ - ◍◍ - ◍◍◍ - ◍◍ - ◍◍◍◍◍ -◍◍◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_cubic.accepted b/birdie_snapshots/tween_in_cubic.accepted deleted file mode 100644 index 917ab4e..0000000 --- a/birdie_snapshots/tween_in_cubic.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in cubic ---- - ◍ - - - ◍ - - - ◍ - - ◍ - - - ◍ - - ◍ - - ◍ - - ◍ - - ◍ - - ◍ - ◍ - - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍◍ - ◍◍ - ◍◍◍◍ -◍◍◍◍◍◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_elastic.accepted b/birdie_snapshots/tween_in_elastic.accepted deleted file mode 100644 index b83474b..0000000 --- a/birdie_snapshots/tween_in_elastic.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in Elastic ---- - ◍ - - - - - - - - - - - ◍ - - - - - - - - - - - - - - - ◍ - - - - ◍ - ◍ ◍ - - ◍ - ◍◍ ◍ -◍◍◍◍◍◍◍◍◍◍◍◍◍◍ ◍◍ ◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_expo.accepted b/birdie_snapshots/tween_in_expo.accepted deleted file mode 100644 index 69ea919..0000000 --- a/birdie_snapshots/tween_in_expo.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in Expo ---- - ◍ - - - - - - ◍ - - - - - ◍ - - - - - ◍ - - - ◍ - - - ◍ - - ◍ - - ◍ - - ◍ - ◍ - ◍ - ◍ - ◍◍ - ◍◍ - ◍◍◍◍◍◍ -◍◍◍◍◍◍◍◍◍◍◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_out_back.accepted b/birdie_snapshots/tween_in_out_back.accepted deleted file mode 100644 index 58d82ba..0000000 --- a/birdie_snapshots/tween_in_out_back.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in out Back ---- - ◍◍◍ - ◍ - - ◍ - - - ◍ - - - - ◍ - - - - - ◍ - - - - - ◍ - - - - - ◍ - - - - ◍ - - - ◍ - - ◍ -◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_out_bounce.accepted b/birdie_snapshots/tween_in_out_bounce.accepted deleted file mode 100644 index d2620df..0000000 --- a/birdie_snapshots/tween_in_out_bounce.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in out Bounce ---- - ◍ ◍◍◍ - ◍◍◍ - - ◍ ◍ - ◍ ◍◍◍ - - - - - ◍ - - - ◍ - - - ◍ - - ◍◍ - ◍◍ - - ◍ - - - ◍ - - - ◍ - - - - - ◍◍◍ ◍ - ◍ ◍ - - ◍◍◍ -◍◍◍ ◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_out_circ.accepted b/birdie_snapshots/tween_in_out_circ.accepted deleted file mode 100644 index e649469..0000000 --- a/birdie_snapshots/tween_in_out_circ.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in out Circ ---- - ◍◍◍◍◍ - ◍◍◍ - ◍◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - - ◍ - - - ◍ - - - - - - - - - ◍ - - - ◍ - - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍◍ - ◍◍◍ -◍◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_out_cubic.accepted b/birdie_snapshots/tween_in_out_cubic.accepted deleted file mode 100644 index 608b4d5..0000000 --- a/birdie_snapshots/tween_in_out_cubic.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in out cubic ---- - ◍◍◍◍◍◍ - ◍◍ - ◍◍ - ◍ - ◍ - - ◍ - ◍ - - ◍ - - ◍ - - ◍ - - - ◍ - - - ◍ - - - ◍ - - ◍ - - ◍ - - ◍ - ◍ - - ◍ - ◍ - ◍◍ - ◍◍ -◍◍◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_out_elastic.accepted b/birdie_snapshots/tween_in_out_elastic.accepted deleted file mode 100644 index fd1a4c3..0000000 --- a/birdie_snapshots/tween_in_out_elastic.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in out Elastic ---- - ◍◍◍◍◍◍◍◍◍ - ◍◍◍ - - - ◍ - - - - - - - - - ◍ - - - - - - - - - ◍ - - - - - - - - - ◍ - - - ◍◍◍ -◍◍◍◍◍◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_out_expo.accepted b/birdie_snapshots/tween_in_out_expo.accepted deleted file mode 100644 index fb61182..0000000 --- a/birdie_snapshots/tween_in_out_expo.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in out Expo ---- - ◍◍◍◍◍◍◍◍◍ - ◍◍◍ - ◍ - ◍ - ◍ - - - ◍ - - - ◍ - - - - ◍ - - - - - - - ◍ - - - - ◍ - - - ◍ - - - ◍ - ◍ - ◍ - ◍◍◍ -◍◍◍◍◍◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_out_quadratic.accepted b/birdie_snapshots/tween_in_out_quadratic.accepted deleted file mode 100644 index 55eff74..0000000 --- a/birdie_snapshots/tween_in_out_quadratic.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in out quadratic ---- - ◍◍◍ - ◍◍◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - - ◍ - ◍ - - ◍ - - ◍ - - ◍ - ◍ - - ◍ - - ◍ - - ◍ - ◍ - - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍◍◍ -◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_out_quartic.accepted b/birdie_snapshots/tween_in_out_quartic.accepted deleted file mode 100644 index f7511c5..0000000 --- a/birdie_snapshots/tween_in_out_quartic.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in out quartic ---- - ◍◍◍◍◍◍◍◍ - ◍◍ - ◍ - ◍ - ◍ - ◍ - - ◍ - - ◍ - - - ◍ - - - - ◍ - - - ◍ - - - - ◍ - - - ◍ - - ◍ - - ◍ - ◍ - ◍ - ◍ - ◍◍ -◍◍◍◍◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_out_quint.accepted b/birdie_snapshots/tween_in_out_quint.accepted deleted file mode 100644 index 04507f1..0000000 --- a/birdie_snapshots/tween_in_out_quint.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in out Quint ---- - ◍◍◍◍◍◍◍◍◍ - ◍◍ - ◍ - ◍ - ◍ - - ◍ - - ◍ - - - ◍ - - - - ◍ - - - - - ◍ - - - - ◍ - - - ◍ - - ◍ - - ◍ - ◍ - ◍ - ◍◍ -◍◍◍◍◍◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_out_sine.accepted b/birdie_snapshots/tween_in_out_sine.accepted deleted file mode 100644 index 39eca9d..0000000 --- a/birdie_snapshots/tween_in_out_sine.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in out sine ---- - ◍◍◍ - ◍◍ - ◍◍ - ◍ - ◍ - ◍ - - ◍ - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - ◍ - - ◍ - ◍ - ◍ - ◍◍ - ◍◍ -◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_quadratic.accepted b/birdie_snapshots/tween_in_quadratic.accepted deleted file mode 100644 index eff832c..0000000 --- a/birdie_snapshots/tween_in_quadratic.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in quadratic ---- - ◍ - - ◍ - - ◍ - - ◍ - - ◍ - ◍ - - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - ◍ - ◍ - - ◍ - ◍ - ◍ - ◍ - ◍ - ◍◍ - ◍ - ◍ - ◍◍ - ◍◍ - ◍◍◍ -◍◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_quartic.accepted b/birdie_snapshots/tween_in_quartic.accepted deleted file mode 100644 index b1be99b..0000000 --- a/birdie_snapshots/tween_in_quartic.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in quartic ---- - ◍ - - - - ◍ - - - ◍ - - - - ◍ - - ◍ - - - ◍ - - - ◍ - - ◍ - - ◍ - ◍ - - ◍ - ◍ - ◍ - - ◍◍ - ◍ - ◍ - ◍◍◍ - ◍◍◍ -◍◍◍◍◍◍◍◍◍◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_quint.accepted b/birdie_snapshots/tween_in_quint.accepted deleted file mode 100644 index 85a8124..0000000 --- a/birdie_snapshots/tween_in_quint.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in Quint ---- - ◍ - - - - - ◍ - - - - ◍ - - - - ◍ - - - ◍ - - - ◍ - - ◍ - - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - ◍◍ - ◍◍ - ◍◍◍◍ -◍◍◍◍◍◍◍◍◍◍◍◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_in_sine.accepted b/birdie_snapshots/tween_in_sine.accepted deleted file mode 100644 index 2dc0c66..0000000 --- a/birdie_snapshots/tween_in_sine.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween in sine ---- - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - ◍ - - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍◍ - ◍◍ - ◍◍◍ -◍◍◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_linear.accepted b/birdie_snapshots/tween_linear.accepted deleted file mode 100644 index 0b84098..0000000 --- a/birdie_snapshots/tween_linear.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween linear ---- - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ -◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_out_back.accepted b/birdie_snapshots/tween_out_back.accepted deleted file mode 100644 index a825244..0000000 --- a/birdie_snapshots/tween_out_back.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween out Back ---- - ◍ ◍◍◍◍ - ◍ - ◍ - - ◍ - - ◍ - - ◍ - - ◍ - - - ◍ - - - ◍ - - - ◍ - - - ◍ - - - - ◍ - - - - ◍ - - - - -◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_out_bounce.accepted b/birdie_snapshots/tween_out_bounce.accepted deleted file mode 100644 index 076ef89..0000000 --- a/birdie_snapshots/tween_out_bounce.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween out Bounce ---- - ◍ ◍◍ - ◍ ◍◍ ◍ ◍ - ◍◍◍◍ - ◍ - ◍ ◍ - ◍ ◍ - - ◍ ◍ - ◍◍ ◍ - ◍ ◍◍ - - - - ◍ - - - - ◍ - - - - ◍ - - - ◍ - - - ◍ - - - ◍ - - ◍ - ◍ - ◍ -◍◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_out_circ.accepted b/birdie_snapshots/tween_out_circ.accepted deleted file mode 100644 index b253d6f..0000000 --- a/birdie_snapshots/tween_out_circ.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween out Circ ---- - ◍◍◍◍◍◍ - ◍◍◍◍◍ - ◍◍ - ◍◍◍ - ◍◍ - ◍ - ◍◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - - ◍ - - ◍ - - ◍ - - ◍ - - - - ◍ - - - - - - - -◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_out_cubic.accepted b/birdie_snapshots/tween_out_cubic.accepted deleted file mode 100644 index 575984c..0000000 --- a/birdie_snapshots/tween_out_cubic.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween out cubic ---- - ◍◍◍◍◍◍◍◍◍ - ◍◍◍◍ - ◍◍ - ◍◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - - ◍ - ◍ - - ◍ - - ◍ - - ◍ - - ◍ - - ◍ - - - ◍ - - ◍ - - - ◍ - - -◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_out_elastic.accepted b/birdie_snapshots/tween_out_elastic.accepted deleted file mode 100644 index 46f98cb..0000000 --- a/birdie_snapshots/tween_out_elastic.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween out Elastic ---- - ◍ ◍◍ ◍◍◍◍◍◍◍◍◍◍◍◍◍◍ - ◍ ◍◍ - ◍ - - ◍ ◍ - ◍ - - - - ◍ - - - - - - - - - - - - - - - ◍ - - - - - - - - - - -◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_out_expo.accepted b/birdie_snapshots/tween_out_expo.accepted deleted file mode 100644 index 4100c69..0000000 --- a/birdie_snapshots/tween_out_expo.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween out Expo ---- - ◍◍◍◍◍◍◍◍◍◍◍◍◍◍ - ◍◍◍◍◍◍ - ◍◍ - ◍◍ - ◍ - ◍ - ◍ - ◍ - - ◍ - - ◍ - - ◍ - - - ◍ - - - ◍ - - - - - ◍ - - - - - ◍ - - - - - -◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_out_quadratic.accepted b/birdie_snapshots/tween_out_quadratic.accepted deleted file mode 100644 index c132092..0000000 --- a/birdie_snapshots/tween_out_quadratic.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween out quadratic ---- - ◍◍◍◍◍ - ◍◍◍ - ◍◍ - ◍◍ - ◍ - ◍ - ◍◍ - ◍ - ◍ - ◍ - ◍ - ◍ - - ◍ - ◍ - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - - ◍ - ◍ - - ◍ - - ◍ - - ◍ - -◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_out_quartic.accepted b/birdie_snapshots/tween_out_quartic.accepted deleted file mode 100644 index 9027755..0000000 --- a/birdie_snapshots/tween_out_quartic.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween out quartic ---- - ◍◍◍◍◍◍◍◍◍◍◍◍◍ - ◍◍◍ - ◍◍◍ - ◍ - ◍ - ◍◍ - - ◍ - ◍ - ◍ - - ◍ - ◍ - - ◍ - - ◍ - - - ◍ - - - ◍ - - ◍ - - - - ◍ - - - ◍ - - - -◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_out_quint.accepted b/birdie_snapshots/tween_out_quint.accepted deleted file mode 100644 index f8f18dd..0000000 --- a/birdie_snapshots/tween_out_quint.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween out Quint ---- - ◍◍◍◍◍◍◍◍◍◍◍◍◍◍◍ - ◍◍◍◍ - ◍◍ - ◍◍ - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - - - ◍ - - ◍ - - - ◍ - - - ◍ - - - - ◍ - - - - ◍ - - - - -◍ \ No newline at end of file diff --git a/birdie_snapshots/tween_out_sine.accepted b/birdie_snapshots/tween_out_sine.accepted deleted file mode 100644 index 7d6a6f9..0000000 --- a/birdie_snapshots/tween_out_sine.accepted +++ /dev/null @@ -1,40 +0,0 @@ ---- -version: 1.1.6 -title: Tween out sine ---- - ◍◍◍◍ - ◍◍◍ - ◍◍ - ◍◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - ◍ - - ◍ - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - - ◍ - ◍ - -◍ \ No newline at end of file diff --git a/dev/build.gleam b/dev/build.gleam index 41e1c48..630dbda 100644 --- a/dev/build.gleam +++ b/dev/build.gleam @@ -1,8 +1,4 @@ -import esgleam import esgleam/mod/install -import gleam/list -import gleam/result -import globlin import simplifile pub fn main() { @@ -11,70 +7,5 @@ pub fn main() { _ -> install.fetch() } - // Process CSS files - let assert Ok(_) = process_css(True) - let assert Ok(_) = process_css(False) - - // Process JS files - let assert Ok(_) = bundle_js(False) - let assert Ok(_) = bundle_js(True) -} - -fn process_css(include_reset: Bool) { - let assert Ok(files) = simplifile.get_files("./src") - let assert Ok(pattern) = globlin.new_pattern("./**/*.css") - let initial_layers = case include_reset { - True -> "@layer reset, primitives, components;\n\n" - False -> "@layer primitives, components;\n\n" - } - - let assert Ok(css) = - files - |> list.filter(globlin.match_pattern(pattern:, path: _)) - |> list.try_fold(initial_layers, fn(css, path) { - use src <- result.map(simplifile.read(path)) - let src = case path { - "./src/lustre/ui/primitives/reset.css" if include_reset -> - case include_reset { - True -> "@layer reset {\n" <> src <> "\n}" - False -> "" - } - - "./src/lustre/ui/primitives/" <> _ -> - "@layer primitives {\n" <> src <> "\n}" - - _ -> "@layer components {\n" <> src <> "\n}" - } - - css <> src <> "\n\n" - }) - - let filename = case include_reset { - True -> "lustre_ui" - False -> "lustre_ui_no_reset" - } - - let assert Ok(_) = - simplifile.write("./priv/static/" <> filename <> ".css", css) - - let assert Ok(_) = - esgleam.new("") - |> esgleam.entry("../../../../priv/static/" <> filename <> ".css") - |> esgleam.minify(True) - |> esgleam.raw("--outfile=./priv/static/" <> filename <> ".min.css") - |> esgleam.bundle -} - -fn bundle_js(minify: Bool) { - let filename = case minify { - True -> ".min.mjs" - False -> ".mjs" - } - - let assert Ok(_) = - esgleam.new("") - |> esgleam.entry("../../../../src/lustre_ui_components.mjs") - |> esgleam.minify(minify) - |> esgleam.raw("--outfile=./priv/static/lustre_ui_components" <> filename) - |> esgleam.bundle + todo } diff --git a/dev/lustre/ui/accordion_stories.gleam b/dev/lustre/ui/accordion_stories.gleam deleted file mode 100644 index 591547e..0000000 --- a/dev/lustre/ui/accordion_stories.gleam +++ /dev/null @@ -1,27 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import lustre/dev/fable -import lustre/element/html -import lustre/ui/accordion -import lustre/ui/theme - -// STORIES --------------------------------------------------------------------- - -pub fn all() { - fable.chapter("Accordion", [faq_story()]) -} - -fn faq_story() { - use <- fable.story("FAQ") - use _ <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - accordion.element([], [ - accordion.item(value: "q1", label: "What is an accordion?", content: [ - html.text("An interactive element for showing/hiding content"), - ]), - accordion.item(value: "q2", label: "When should I use one?", content: [ - html.text("When you want to organize content into sections"), - ]), - ]) -} diff --git a/dev/lustre/ui/alert_stories.gleam b/dev/lustre/ui/alert_stories.gleam deleted file mode 100644 index 2b76707..0000000 --- a/dev/lustre/ui/alert_stories.gleam +++ /dev/null @@ -1,43 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import lustre/dev/fable -import lustre/element/html -import lustre/ui/accordion -import lustre/ui/alert -import lustre/ui/primitives/icon -import lustre/ui/theme - -// STORIES --------------------------------------------------------------------- - -pub fn all() { - let assert Ok(_) = accordion.register() - - fable.chapter("Alert", [ - title_only_success_alert_story(), - title_indicator_content_error_alert_story(), - ]) -} - -fn title_only_success_alert_story() { - use <- fable.story("Title only, success") - use _ <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - alert.element([alert.success()], [ - alert.title([], [html.text("New todo added to your list.")]), - ]) -} - -fn title_indicator_content_error_alert_story() { - use <- fable.story("Title + indicator + content, error") - use _ <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - alert.element([alert.danger()], [ - alert.indicator(icon.exclamation_triangle([])), - alert.title([], [html.text("Could not delete todo")]), - alert.content([], [ - html.p([], [html.text("Check your internet connection and try again.")]), - ]), - ]) -} diff --git a/dev/lustre/ui/badge_stories.gleam b/dev/lustre/ui/badge_stories.gleam deleted file mode 100644 index 76fcf42..0000000 --- a/dev/lustre/ui/badge_stories.gleam +++ /dev/null @@ -1,36 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import lustre/attribute -import lustre/dev/fable -import lustre/element/html -import lustre/ui/badge -import lustre/ui/theme - -// STORIES --------------------------------------------------------------------- - -pub fn all() { - fable.chapter("Badge", [online_avatar_story()]) -} - -fn online_avatar_story() { - use <- fable.story("Online Avatar Indicator") - use _ <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - html.div([attribute.class("inline-block relative")], [ - html.img([ - attribute.class("h-10 w-10 rounded-full"), - attribute.src("https://placehold.co/100"), - attribute.alt("Avatar"), - ]), - badge.element( - [ - badge.background("green"), - badge.solid(), - attribute.class("absolute top-0 right-0"), - attribute.title("online"), - ], - [], - ), - ]) -} diff --git a/dev/lustre/ui/breadcrumb_stories.gleam b/dev/lustre/ui/breadcrumb_stories.gleam deleted file mode 100644 index a17f9e8..0000000 --- a/dev/lustre/ui/breadcrumb_stories.gleam +++ /dev/null @@ -1,46 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import lustre/attribute -import lustre/dev/fable -import lustre/element/html -import lustre/ui/breadcrumb -import lustre/ui/theme - -// STORIES --------------------------------------------------------------------- - -pub fn all() { - fable.chapter("Breadcrumb", [ - basic_breadcrumb_story(), - breadcrumb_with_collapsed_items_story(), - ]) -} - -fn basic_breadcrumb_story() { - use <- fable.story("Basic Breadcrumb Navigation") - use _ <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - breadcrumb.element([], [ - breadcrumb.item([], [html.a([attribute.href("/")], [html.text("Home")])]), - breadcrumb.chevron([]), - breadcrumb.item([], [ - html.a([attribute.href("/documents")], [html.text("Documents")]), - ]), - breadcrumb.chevron([]), - breadcrumb.current([], [html.text("My Document")]), - ]) -} - -fn breadcrumb_with_collapsed_items_story() { - use <- fable.story("Breadcrumb with Collapsed Items") - use _ <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - breadcrumb.element([], [ - breadcrumb.item([], [html.a([attribute.href("/")], [html.text("Home")])]), - breadcrumb.slash([]), - breadcrumb.ellipsis([], "Collapsed navigation items"), - breadcrumb.slash([]), - breadcrumb.current([], [html.text("My Document")]), - ]) -} diff --git a/dev/lustre/ui/button_stories.gleam b/dev/lustre/ui/button_stories.gleam deleted file mode 100644 index 7989ab1..0000000 --- a/dev/lustre/ui/button_stories.gleam +++ /dev/null @@ -1,32 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import lustre/dev/fable -import lustre/element/html -import lustre/ui/button -import lustre/ui/primitives/icon -import lustre/ui/theme - -// STORIES --------------------------------------------------------------------- - -pub fn all() { - fable.chapter("Button", [basic_button_story(), command_palette_button_story()]) -} - -fn basic_button_story() { - use <- fable.story("Basic Button with Icon and Text") - use _ <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - button.element([], [icon.bookmark([]), html.text(" Save")]) -} - -fn command_palette_button_story() { - use <- fable.story("Command Palette Button with Keyboard Shortcut") - use _ <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - button.element([button.solid()], [ - html.text("Open"), - button.shortcut_badge([], ["⌘", "k"]), - ]) -} diff --git a/dev/lustre/ui/card_stories.gleam b/dev/lustre/ui/card_stories.gleam deleted file mode 100644 index bfd0865..0000000 --- a/dev/lustre/ui/card_stories.gleam +++ /dev/null @@ -1,49 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import lustre/dev/fable -import lustre/element/html -import lustre/ui/button -import lustre/ui/card -import lustre/ui/theme - -// STORIES --------------------------------------------------------------------- - -pub fn all() { - fable.chapter("Card", [basic_card_story(), dialog_card_story()]) -} - -fn basic_card_story() { - use <- fable.story("Basic Content Card with Header and Content") - use _ <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - card.element([], [ - card.header([], [html.h2([], [html.text("Easy Chocolate Chip Cookies")])]), - card.content([], [ - html.p([], [ - html.text( - "A simple recipe for delicious chocolate chip cookies that are crisp at the edges and chewy in the middle.", - ), - ]), - ]), - ]) -} - -fn dialog_card_story() { - use <- fable.story("Dialog-Style Card with Footer Actions") - use _ <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - card.element([], [ - card.header([], [html.text("Delete recipe?")]), - card.content([], [ - html.p([], [ - html.text("Are you sure that you want to delete this recipe?"), - ]), - ]), - card.footer([], [ - button.element([button.clear()], [html.text("Cancel")]), - button.element([button.solid(), button.danger()], [html.text("Delete")]), - ]), - ]) -} diff --git a/dev/lustre/ui/combobox_stories.gleam b/dev/lustre/ui/combobox_stories.gleam deleted file mode 100644 index 5f13554..0000000 --- a/dev/lustre/ui/combobox_stories.gleam +++ /dev/null @@ -1,35 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import lustre/dev/fable -import lustre/ui/combobox -import lustre/ui/theme - -// STORIES --------------------------------------------------------------------- - -pub fn all() { - let assert Ok(_) = combobox.register() - - fable.chapter("Combobox", [basic_story()]) -} - -fn basic_story() { - use <- fable.story("Typeahead filter") - use value <- fable.input("Value", "gleam") - use controls <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - combobox.element( - [ - combobox.value(fable.get(controls, value)), - combobox.on_change(fable.set(value, _)), - ], - [ - combobox.option(value: "gleam", label: "Gleam"), - combobox.option(value: "go", label: "Go"), - combobox.option(value: "javascript", label: "JavaScript"), - combobox.option(value: "kotlin", label: "Kotlin"), - combobox.option(value: "rust", label: "Rust"), - combobox.option(value: "typescript", label: "TypeScript"), - ], - ) -} diff --git a/dev/lustre/ui/divider_stories.gleam b/dev/lustre/ui/divider_stories.gleam deleted file mode 100644 index 2febd01..0000000 --- a/dev/lustre/ui/divider_stories.gleam +++ /dev/null @@ -1,52 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import lustre/dev/fable -import lustre/element/html -import lustre/ui/divider -import lustre/ui/theme - -// STORIES --------------------------------------------------------------------- - -pub fn all() { - fable.chapter("Divider", [basic_divider_story(), text_label_divider_story()]) -} - -fn basic_divider_story() { - use <- fable.story("Basic Divider with No Content") - use _ <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - html.div([], [ - html.p([], [ - html.text( - "This is some content before the divider. The divider below has no content and serves as a simple horizontal rule.", - ), - ]), - divider.element([], []), - html.p([], [ - html.text( - "This is some content after the divider. Notice how the divider creates a clear separation between content sections.", - ), - ]), - ]) -} - -fn text_label_divider_story() { - use <- fable.story("Divider with Text Label") - use _ <- fable.scene - use <- theme.inject(theme.default() |> theme.with_scope(theme.Host)) - - html.div([], [ - html.p([], [html.text("Sign in with your email and password")]), - html.div([], [ - // Form fields would go here in a real implementation - html.p([], [html.text("(Form fields placeholder)")]), - ]), - divider.element([], [html.text("OR")]), - html.p([], [html.text("Continue with social login")]), - html.div([], [ - // Social login buttons would go here - html.p([], [html.text("(Social login buttons placeholder)")]), - ]), - ]) -} diff --git a/dev/lustre_ui_storybook.gleam b/dev/lustre_ui_storybook.gleam index 3ae72e9..044f059 100644 --- a/dev/lustre_ui_storybook.gleam +++ b/dev/lustre_ui_storybook.gleam @@ -1,34 +1,11 @@ // IMPORTS --------------------------------------------------------------------- -import lustre import lustre/dev/fable -import lustre/element -import lustre/element/html -import lustre/ui/accordion_stories -import lustre/ui/alert_stories -import lustre/ui/badge_stories -import lustre/ui/breadcrumb_stories -import lustre/ui/button_stories -import lustre/ui/card_stories -import lustre/ui/combobox -import lustre/ui/combobox_stories -import lustre/ui/divider_stories // MAIN ------------------------------------------------------------------------ pub fn main() { - let book = - fable.book("Lustre UI", [ - accordion_stories.all(), - alert_stories.all(), - badge_stories.all(), - breadcrumb_stories.all(), - button_stories.all(), - card_stories.all(), - combobox_stories.all(), - divider_stories.all(), - fable.external_stylesheet("/priv/static/lustre_ui.css"), - ]) + let book = fable.book("Lustre UI", []) fable.start(book) } diff --git a/gleam.toml b/gleam.toml index 6908786..1dfe6af 100644 --- a/gleam.toml +++ b/gleam.toml @@ -10,25 +10,17 @@ links = [ { title = "Sponsor", href = "https://github.com/sponsors/hayleigh-dot-dev" }, ] -internal_modules = [ - "lustre_ui", - "lustre/ui/data/*", - "lustre/ui/internals/*", - "lustre/ui/primitives/*", -] +internal_modules = [] [dependencies] -gleam_community_colour = ">= 2.0.0 and < 3.0.0" -gleam_community_maths = ">= 2.0.0 and < 3.0.0" gleam_json = ">= 3.0.0 and < 4.0.0" gleam_stdlib = ">= 0.60.0 and < 2.0.0" lustre = ">= 5.0.0 and < 6.0.0" [dev-dependencies] -birdie = ">= 1.1.6 and < 2.0.0" esgleam = ">= 0.7.0 and < 1.0.0" gleeunit = "~> 1.0" globlin = ">= 2.0.2 and < 3.0.0" +lustre_dev_tools = ">= 1.8.2 and < 2.0.0" lustre_fable = { git = "https://github.com/lustre-labs/fable.git", ref = "main" } simplifile = ">= 2.2.0 and < 3.0.0" -lustre_dev_tools = ">= 1.8.2 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index 9586104..5312848 100644 --- a/manifest.toml +++ b/manifest.toml @@ -3,18 +3,14 @@ packages = [ { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, - { name = "birdie", version = "1.3.1", build_tools = ["gleam"], requirements = ["argv", "edit_distance", "filepath", "glance", "gleam_community_ansi", "gleam_stdlib", "justin", "rank", "simplifile", "term_size", "trie_again"], otp_app = "birdie", source = "hex", outer_checksum = "F811C9EDAF920EF48597A26E788907AAF80D9239A5E8C8CCFBD0DD1BB10184D7" }, { name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" }, - { name = "edit_distance", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "edit_distance", source = "hex", outer_checksum = "A1E485C69A70210223E46E63985FA1008B8B2DDA9848B7897469171B29020C05" }, { name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" }, { name = "esgleam", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_regexp", "gleam_stdlib", "simplifile"], otp_app = "esgleam", source = "hex", outer_checksum = "00D1579DF4A8FEA70B0A2C6FADCD990C912317DB467F1A5616930CEBE312B2F0" }, { name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" }, { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, { name = "fs", version = "11.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "fs", source = "hex", outer_checksum = "DD00A61D89EAC01D16D3FC51D5B0EB5F0722EF8E3C1A3A547CD086957F3260A9" }, - { name = "glance", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "FAA3DAC74AF71D47C67D88EB32CE629075169F878D148BB1FF225439BE30070A" }, { name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" }, { name = "gleam_community_colour", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "F0ACE69E3A47E913B03D3D0BB23A5563A91A4A7D20956916286068F4A9F817FE" }, - { name = "gleam_community_maths", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_yielder"], otp_app = "gleam_community_maths", source = "hex", outer_checksum = "C9EFF6BCF3C9D113EF9837655B0128CA8CAC295131E9F3468F93C1D78EC3E013" }, { name = "gleam_crypto", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "917BC8B87DBD584830E3B389CBCAB140FFE7CB27866D27C6D0FB87A9ECF35602" }, { name = "gleam_deque", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_deque", source = "hex", outer_checksum = "64D77068931338CF0D0CB5D37522C3E3CCA7CB7D6C5BACB41648B519CC0133C7" }, { name = "gleam_erlang", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "7E6A5234F927C4B24F8054AB1E4572206C41F9E6D5C6C02273CB7531E7E5CED0" }, @@ -29,7 +25,6 @@ packages = [ { name = "gleam_stdlib", version = "0.60.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "621D600BB134BC239CB2537630899817B1A42E60A1D46C5E9F3FAE39F88C800B" }, { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, { name = "gleeunit", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D33B7736CF0766ED3065F64A1EBB351E72B2E8DE39BAFC8ADA0E35E92A6A934F" }, - { name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" }, { name = "glint", version = "1.2.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "2214C7CEFDE457CEE62140C3D4899B964E05236DA74E4243DFADF4AF29C382BB" }, { name = "glisten", version = "8.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "17B3CC2E5093662404DDCF7C837D1CA093E5C436CE5F8A532F8EA0D12B5B2172" }, { name = "globlin", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_regexp", "gleam_stdlib"], otp_app = "globlin", source = "hex", outer_checksum = "923BC16814DF95C4BB28111C266F0B873499480E6E125D5A16DBDF732E62CEB4" }, @@ -45,7 +40,6 @@ packages = [ { name = "mist", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "7B5F043DDF8010C580CD7131B1DBB3DE2411612E6F103EA7ECE49583F5613438" }, { name = "modem", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "lustre"], otp_app = "modem", source = "hex", outer_checksum = "8CAACFCCE88C46A36CE71158B13F541F7A8E36BF63F59A2E554070BBCD09FE9F" }, { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, - { name = "rank", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "rank", source = "hex", outer_checksum = "5660E361F0E49CBB714CC57CC4C89C63415D8986F05B2DA0C719D5642FAD91C9" }, { name = "repeatedly", version = "2.1.2", build_tools = ["gleam"], requirements = [], otp_app = "repeatedly", source = "hex", outer_checksum = "93AE1938DDE0DC0F7034F32C1BF0D4E89ACEBA82198A1FE21F604E849DA5F589" }, { name = "rsvp", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_fetch", "gleam_http", "gleam_httpc", "gleam_javascript", "gleam_json", "gleam_stdlib", "lustre"], otp_app = "rsvp", source = "hex", outer_checksum = "0C0732577712E7CB0E55F057637E62CD36F35306A5E830DC4874B83DA8CE4638" }, { name = "simplifile", version = "2.3.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0A868DAC6063D9E983477981839810DC2E553285AB4588B87E3E9C96A7FB4CB4" }, @@ -53,15 +47,11 @@ packages = [ { name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" }, { name = "term_size", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "term_size", source = "hex", outer_checksum = "D00BD2BC8FB3EBB7E6AE076F3F1FF2AC9D5ED1805F004D0896C784D06C6645F1" }, { name = "tom", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "0910EE688A713994515ACAF1F486A4F05752E585B9E3209D8F35A85B234C2719" }, - { name = "trie_again", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "trie_again", source = "hex", outer_checksum = "5B19176F52B1BD98831B57FDC97BD1F88C8A403D6D8C63471407E78598E27184" }, { name = "wisp", version = "1.8.0", build_tools = ["gleam"], requirements = ["directories", "exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "0FE9049AFFB7C8D5FC0B154EEE2704806F4D51B97F44925D69349B3F4F192957" }, ] [requirements] -birdie = { version = ">= 1.1.6 and < 2.0.0" } esgleam = { version = ">= 0.7.0 and < 1.0.0" } -gleam_community_colour = { version = ">= 2.0.0 and < 3.0.0" } -gleam_community_maths = { version = ">= 2.0.0 and < 3.0.0" } gleam_json = { version = ">= 3.0.0 and < 4.0.0" } gleam_stdlib = { version = ">= 0.60.0 and < 2.0.0" } gleeunit = { version = "~> 1.0" } diff --git a/pages/01-getting-started.md b/pages/01-getting-started.md deleted file mode 100644 index c4836b6..0000000 --- a/pages/01-getting-started.md +++ /dev/null @@ -1,225 +0,0 @@ -# 01 Getting started - -Lustre/ui is a component library for building interactive user interfaces built -around the central idea of a _theme_. The library provides a set of styled -components that draw from a common pool of design tokens and then gives you a -way to customise those tokens to fit your own style or brand. - -To see how it all comes together, let's build a simple one-button counter. This -isn't a _lustre_ tutorial, so if you're brand new to lustre you might want to -head over to the main [quickstart guide](https://hexdocs.pm/lustre/guide/01-quickstart.html) -first. - -## Setup - -Let's start by creating a new Gleam project, and adding the dependencies we'll -need. - -```sh -# Create a new gleam project and enter it -gleam new lustre_ui_quickstart && cd lustre_ui_quickstart - -# Add dependencies -gleam add lustre lustre_ui - -# Add lustre_dev_tools to access the development server -gleam add lustre_dev_tools --dev -``` - -Before we look at lustre/ui, we'll put together the application logic so we can -focus on styling for the rest of the guide. - -```gleam -import lustre -import lustre/element/html -import lustre/event - -pub fn main() { - let app = lustre.simple(init, update, view) - let assert Ok(_) = lustre.start(app, "#app", Nil) - - Nil -} - -type Model { - Model(count: Int) -} - -fn init(_) { - Model(0) -} - -type Msg { - Increment -} - -fn update(model, msg) { - case msg { - Increment -> Model(..model, count: model.count + 1 - } -} - -fn view(model) { - let count = int.to_string(model.count) - - html.button([event.on_click(Increment)], [html.text(count)]) -} -``` - -To confirm everything is working, run `gleam run -m lustre/dev start` to start -the development server and open `localhost:1234` in your browser. Clicking the -button should update the count. - -## Adding lustre/ui styles - -In order for lustre/ui to work correctly we need to do two things: - -1. Include the lustre/ui stylesheet in your app. This stylesheet contains all - the necessary CSS to style each component and can be found in your project's - build directory at `build/packages/lustre_ui/priv/static/lustre_ui.css`. - -2. Construct a new theme and dynamically inject its styles into your application. - -Let's start with the base static stylesheet. How you serve lustre/ui's stylesheet -will depend on your application and how you deploy it: common approaches include -copying the CSS file from the package into your own application's `priv/static` -directory, or serving it from a server using something like wisp's -[`serve_static`](https://hexdocs.pm/wisp/wisp.html#serve_static) function. - -For simplicity during development, we can add a `` tag to the generated -HTML document and point it directly to the stylesheet: - -```diff - - - - - - - 🚧 lustre_ui_quickstart - -- -- -+ - - - -
- - -``` - -If you refresh the page (or restart the development server if you stopped it) you -might already notice some changes. Lustre/ui includes a **CSS reset** which is -a common way for styling frameworks to ensure consistent base styles across -browsers. - -This isn't quite enough to get going yet, though. Lustre/ui's stylesheet contains -all the styles for each component, but the look and feel of your application is -dictated by your _theme_. - -## Configuring your theme - -A theme is the collection of design tokens lustre/ui uses to style each component: -it configures things like padding, colours, and border radius. By making the theme -flexible, lustre/ui can author many components without dictating a strict visual -style! - -The library ships with a default theme, which we'll use for now by storing it in -our model. - -```diff -+ import lustre/ui/theme.{type Theme} - - type Model { -+ Model(count: Int, theme: Theme) - } - - fn init(_) { - Model(0, theme.default()) - } -``` - -Lustre/ui gives us two ways to render a theme into a stylesheet that defines all -your design tokens as CSS variables. For more control over where you insert the -stylehseet, you can use `theme.to_style`: this function is useful to injecting -the tokens into the `` of a server-rendered document. - -For SPAs and client applications, `theme.inject` can be used to wrap your `view` -function and automatically insert the stylesheet: - - -```diff - fn view(model) { -+ use <- theme.inject(model.theme) - let count = int.to_string(model.count) - - html.button([event.on_click(Increment)], [html.text(count)]) - } -``` - -## Rendering lustre/ui elements! - -We've set up everything we need to start using lustre/ui in our pages and elements. -Among other things, lustre/ui includes a [button element](https://hexdocs.pm/lustre_ui/lustre/ui/button.html) -we can use instead of the unstyled HTML button: - -```diff -+ import lustre/ui/button - - fn view(model) { - use <- theme.inject(model.theme) - let count = int.to_string(model.count) - -+ button.element([event.on_click(Increment)], [html.text(count)]) - } -``` - -All modules in lustre/ui follow the same pattern: the main element or container -in the module is always called "element" to encourage qualified usage. That means -we have `button.element`, or `checkbox.element`, or `combobox.element`. - -A module may also contain other elements intended to be used as children. In the -button's case we can add a number badge to show the count: - -```diff -+ import lustre/ui/button - - fn view(model) { - use <- theme.inject(model.theme) -- let count = int.to_string(model.count) - -+ button.element([event.on_click(Increment)], [ - html.text("Increment"), - button.count_badge([], model.count) - ]) - } -``` - -Elements in lustre/ui are designed to be flexible rather than prescriptive so -often children can be supplied in any order or combination and the element will -adapt accordingly. - -## Customising your theme - -The default theme is a great to drop in when you're just starting out, but once -your application has grown a bit you might want to start customising it to better -suit your own design. - -It's possible to construct a theme from scratch, but the `lustre/ui/theme` module -also exposes a number of builders to modify an existing theme. Let's tweak the -default theme by changing the primary colour and removing the rounded borders of -all elements: - -```diff -+ import lustre/ui/colour - - fn init(_) { -+ let theme = -+ theme.default() -+ |> theme.with_primary_scale(colour.sage()) -+ |> theme.with_radius(theme.constant_size(0.0)) - -+ Model(0, theme) - } -``` diff --git a/pages/02-elements-vs-components.md b/pages/02-elements-vs-components.md deleted file mode 100644 index 5707775..0000000 --- a/pages/02-elements-vs-components.md +++ /dev/null @@ -1,236 +0,0 @@ -# 02 Components vs elements - -In many frontend frameworks, the word "component" is a general term used to -describe any construct in the framework that can render something. There's often -an implicit but _optional_ ability for components to encapsulate state too. As an -example, the following two snippets are both considered React **components**: - -```jsx -export function ButtonComponent({ onClick, children }) { - return ( - - ) -} -``` - -```jsx -import { useState } from 'react' - -export function CounterComponent() { - const [count, setCount] = useState(0) - - return ( -
- setCount(count - 1)}>Decr -

{count}

- setCount(count + 1)}>Incr -
- ) -} -``` - -State locality and encapsulation is a positive feature for these frameworks, and -to work out if a component does contain its own state you have to peak at the -implementation. - -In Lustre we do things a bit differently, and centralising state in your application's -`Model` is a key part of what makes the framework feel robust and approachable. -Lustre _does_ have an abstraction for stateful components though, and so for -clarity we make an explicit difference between stateless _elements_ (also known -as "view functions") and stateful _components_. - -If we take the React components from above and translate them to Lustre, we get -something like: - -```gleam -import lustre/attribute -import lustre/element/html -import lustre/event - -pub fn button_element(on_click, children) { - html.button([attribute.class("my-button"), event.on_click(on_click)], children) -} -``` - -```gleam -import lustre -import lustre/element -import lustre/element/html - -pub fn register() { - lustre.register("counter-component", lustre.simple(init, update, view)) -} - -pub fn counter_component() { - element.element("counter-component", [], []) -} - -fn init(_) { - 0 -} - -type Msg { - Incr - Decr -} - -fn update(model, msg) { - case msg { - Incr -> model + 1 - Decr -> model - 1 - } -} - -fn view(model) { - let count = int.to_string(model) - - html.div([], [ - button_element(Decr, [html.text("Decr")]), - html.p([], [html.text(count)]), - button_element(Incr, [html.text("Incr")]), - ]) -} -``` - -Woah, that's quite a bit more! Components in Lustre are complete applications -registered as custom elements, and then rendered like all other HTML elements. -Because of the set up, the number of components a typical application have will -be far fewer than other frontend frameworks and the decision to encapsulate state -in a component tends to be given more weight even as the project grows. - -Throughout Lustre's documentation - both in the core library and other packages -such as this one - the word **component** will always refer to stateful components, -and everything else will always be referred to as an "element" or "view function". -We encourage you to adopt this naming when writing your own applications or -content about Lustre, so everyone stays on the same page! - -## Why does lustre/ui need components? - -Elm - one of the frameworks Lustre is heavily inspired by - has this to say about -components: - -> Folks coming from React expect everything to be components. Actively trying to -> make components is a recipe for disaster in Elm. The root issue is that components -> are objects. It would be odd to start using Elm and wonder "how do I structure -> my application with objects?" There are no objects in Elm! - -Given that both Elm and Gleam are immutable functional programming languages, you -might question why Lustre (and lustre/ui) need components at all. In our experience -with a number of large production Elm codebases, a number of problems tend to -arise when components are not available: - -- Developers tend to naturally gravitate to quasi-components over time with modules - that contain their own `Model`, `update` and `view` to make related functionality - more manageable. - -- When more than one of the same "component" is on the page, the application model - must come up with an ad-hoc system to distinguish messages and state for each - component such as `Dict String ComponentModel`. - -- Storing component state far away from where it's (exclusively) used leads to - problems. It becomes difficult to understand the responsibilities of different - view functions when unrelated state needs to be passed down through these elements. - -There's also a reason unique to Lustre that makes components an interesting -option: - -- Components create a hard boundary between the component and its parent which - is particularly useful when taking advantage of _server components_. That boundary - makes it possible to blend server and client components in a render tree without - sending unnecessary data over the wire. - -## How to use components in lustre/ui - -You can always register a component in lustre/ui because the module will expose -a `register` function. Some examples of components available in lustre/ui include: - -- [`accordion`](https://hexdocs.pm/lustre_ui/lustre/ui/accordion.html) -- [`combobox`](https://hexdocs.pm/lustre_ui/lustre/ui/combobox.html) - -If you are using Lustre and lustre/ui to build a Single Page Application (SPA) on -the **client**, it's important to call the `register` function of any component -you intend to use. - -Typically this is done before you start your application: - -```gleam -import lustre -import lustre/ui/combobox - -pub fn main() { - let app = lustre.simple(init, update, view) - - let assert Ok(_) = combobox.register() - let assert Ok(_) = lustre.start(app, "#app", Nil) - - Nil -} -``` - -Lustre components are built on top of the [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) -standard, which means the browser needs to know about the components you want to -use before they can be rendered properly. - -If you are rendering components on the **server** either with server-side rendering -or static site generation, you should include a supplementary bundle as a ` - - - - " - - pub fn handle_request(req: Request, ctx: Context) -> Response { - use _req <- web.middleware(req, ctx) - wisp.html_response(string_tree.from_string(html), 200) - } -``` diff --git a/pages/components.md b/pages/components.md deleted file mode 100644 index eef6d5d..0000000 --- a/pages/components.md +++ /dev/null @@ -1,59 +0,0 @@ -# How we use components - -Some parts of Lustre UI use Lustre's component system to hide state and logic -from the rest of your application. This can be an effective way to reduce -boilerplate and manage complexity, but it might also challenge how you're used to -thinking about Lustre applications. - -The rest of this doc will explain how we use components in Lustre UI and what you -need to do to make sure things are working correctly. - -## Knowing when something is a component - -Your view function in a Lustre application always has to return `Element(msg)` so -it can be difficult to work out at a glance when something is a component or not. -In Lustre UI we follow a convention where modules that contain components will -*always* have at least two exports: - -- a `name` constant that is the name of the component, like - `pub const name: String = "lustre-ui-collapse"`. - -- a `register` function that registers the custom element in the browser. More - on that in a moment. - -If a module does _not_ define these two exports, then it is not defining a -component. - -Some modules might _use_ components without defining them directly. In those -cases the module will always return a `register` function to register any of the -components it depends on. This function will behave slightly differently to the -individual register functions, and _will not fail_ if a component has already -been defined. - -## Components must be registered - -If you are using Lustre UI in a browser-based Lustre application, you must -remember to register any components you use _before_ you try to render them. - - -Lustre's components are built on top of the Web Component standards and appear as -real HTML elements in the DOM. - -## Components are always controlled - -Native HTML elements like `` or `