diff options
Diffstat (limited to 'docs/src/app/ui/hooks.gleam')
-rw-r--r-- | docs/src/app/ui/hooks.gleam | 120 |
1 files changed, 0 insertions, 120 deletions
diff --git a/docs/src/app/ui/hooks.gleam b/docs/src/app/ui/hooks.gleam deleted file mode 100644 index 5fd807e..0000000 --- a/docs/src/app/ui/hooks.gleam +++ /dev/null @@ -1,120 +0,0 @@ -// 🚨 This module makes quite judicious use of `dynamic.unsafe_coerce` to wire -// things up. As things are, this is sound because we control things in such a -// way that it's impossible to pass in things that don't match up to the expected -// types. -// -// If you're defining a new hook to export for this module, pay extra attention -// to make sure you aren't introducing any soundness issues! 🚨 - -// IMPORTS --------------------------------------------------------------------- - -import gleam/dynamic.{Dynamic, dynamic} -import gleam/function -import gleam/map.{Map} -import gleam/result -import lustre.{Error} -import lustre/attribute.{Attribute, property} -import lustre/effect.{Effect} -import lustre/element.{Element, element} -import lustre/event - -// HOOKS: STATE ---------------------------------------------------------------- - -/// -/// -pub fn use_state( - init: state, - view: fn(state, fn(state) -> Msg, fn(msg) -> Msg) -> Element(Msg), -) -> Element(msg) { - let attrs = [property("state", init), property("view", view), on_dispatch()] - let assert Ok(_) = register_hook("use-state") - - element("use-state", attrs, []) -} - -// HOOKS: REDUCER -------------------------------------------------------------- - -/// -/// -pub fn use_reducer( - init: state, - update: fn(state, action) -> state, - view: fn(state, fn(action) -> Msg, fn(action) -> Msg) -> Element(Msg), -) -> Element(msg) { - // The `use_reducer` hook is actually just the `use_state` hook under the hood - // with a wrapper around the `set_state` callback. We could just call out to - // `use_state` directly but we're doing it like this so that the DOM renders - // a separate `use-reducer` element, which I think is nicer. - let view = fn(state, set_state, emit) { - view(state, function.compose(update(state, _), set_state), emit) - } - let attrs = [property("state", init), property("view", view), on_dispatch()] - let assert Ok(_) = register_hook("use-reducer") - - element("use-reducer", attrs, []) -} - -// HOOKS: INTERNAL COMPONENT --------------------------------------------------- - -fn register_hook(name: String) -> Result(Nil, Error) { - // If a component is already registered we will just assume it's because this - // hook as already been used before. This isn't really an error state so we'll - // just return `Ok(Nil)` and let our hooks continue. - case lustre.is_registered(name) { - True -> Ok(Nil) - False -> - lustre.component( - name, - init_hook, - update_hook, - view_hook, - map.from_list([ - #("state", dynamic.decode1(Set("state", _), Ok)), - #("view", dynamic.decode1(Set("view", _), Ok)), - ]), - ) - } -} - -type Model = - Map(String, Dynamic) - -fn init_hook() -> #(Model, Effect(msg)) { - #(map.new(), effect.none()) -} - -/// The type for messages handled internally by the different hooks. You typially -/// won't need to import or refer to this type directly. -/// -pub opaque type Msg { - Set(String, Dynamic) - Emit(Dynamic) -} - -fn update_hook(model: Model, msg: Msg) -> #(Model, Effect(msg)) { - case msg { - Set(key, val) -> #(map.insert(model, key, val), effect.none()) - Emit(msg) -> #(model, event.emit("dispatch", msg)) - } -} - -fn view_hook(model: Model) -> Element(Msg) { - case map.get(model, "state"), map.get(model, "view") { - Ok(state), Ok(view) -> { - let state = dynamic.unsafe_coerce(state) - let view = dynamic.unsafe_coerce(view) - - view(state, Set("state", _), Emit) - } - _, _ -> element.text("???") - } -} - -// EVENTS ---------------------------------------------------------------------- - -fn on_dispatch() -> Attribute(msg) { - use event <- event.on("dispatch") - event - |> dynamic.field("detail", dynamic) - |> result.map(dynamic.unsafe_coerce) -} |