Created
May 22, 2026 16:44
-
-
Save tjdevries/14a3395746208af5a4d5ae19b7d5390b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Counter browser program — compiler-known TEA shape. | |
| // init/update return model only; `command` statements emit; handlers live in command block. | |
| program Counter { | |
| model { | |
| count: Int, | |
| step: Int, | |
| is_resetting: Bool, | |
| } | |
| message { | |
| :clicked_increment, | |
| :clicked_decrement, | |
| :clicked_reset, | |
| :clicked_reset_after_delay, | |
| :changed_step(Int), | |
| :reset_completed, | |
| } | |
| command { | |
| :schedule_reset { after_ms: Int } => { | |
| use Browser | |
| Browser.set_timeout(~after_ms, fn() { | |
| send(:reset_completed); | |
| }); | |
| } | |
| } | |
| fn init() { | |
| return { count: 0, step: 1, is_resetting: false }; | |
| } | |
| fn scaled_total(model: Model): Int = model.count * model.step | |
| fn update(model: Model, message: Message) { | |
| return match message { | |
| | :clicked_increment -> { model with count: model.count + model.step } | |
| | :clicked_decrement -> { model with count: model.count - model.step } | |
| | :clicked_reset -> { model with count: 0, is_resetting: false } | |
| | :clicked_reset_after_delay -> { | |
| command :schedule_reset { after_ms: 1200 }; | |
| { model with is_resetting: true }; | |
| } | |
| | :changed_step(step) -> { | |
| if step < 1 { | |
| return model; | |
| } | |
| { model with step }; | |
| } | |
| | :reset_completed -> { model with count: 0, is_resetting: false } | |
| }; | |
| } | |
| fn view(model: Model): Html { | |
| let total = scaled_total(model); | |
| let reset_label = if model.is_resetting { | |
| "Resetting…" | |
| } else { | |
| "Reset after delay" | |
| }; | |
| return html.main( | |
| ~class: "counter-app", | |
| [ | |
| html.h1([], ["Browser counter"]), | |
| html.p( | |
| ~class: "counter-display", | |
| ~aria_live: "polite", | |
| [Int.to_string(model.count)], | |
| ), | |
| html.p( | |
| ~class: "counter-derived muted", | |
| ["Scaled total: " ++ Int.to_string(total)], | |
| ), | |
| html.div( | |
| ~class: "counter-controls", | |
| [ | |
| html.button( | |
| ~type: "button", | |
| ~class: "btn", | |
| ~onclick: :clicked_decrement, | |
| ["−"], | |
| ), | |
| html.button( | |
| ~type: "button", | |
| ~class: "btn btn-primary", | |
| ~onclick: :clicked_increment, | |
| ["+"], | |
| ), | |
| html.button( | |
| ~type: "button", | |
| ~class: "btn", | |
| ~onclick: :clicked_reset, | |
| ["Reset now"], | |
| ), | |
| ], | |
| ), | |
| html.label( | |
| ~for: "step-input", | |
| ["Step"], | |
| ), | |
| html.input( | |
| ~id: "step-input", | |
| ~type: "number", | |
| ~min: "1", | |
| ~value: Int.to_string(model.step), | |
| ~oninput: fn(raw) { | |
| let step = Int.parse(raw) else 1; | |
| return :changed_step(step); | |
| }, | |
| ), | |
| html.button( | |
| ~type: "button", | |
| ~class: "btn", | |
| ~disabled: model.is_resetting, | |
| ~onclick: :clicked_reset_after_delay, | |
| [reset_label], | |
| ), | |
| ], | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment