import gleam/dynamic.{type Dynamic} import gleam/dynamic/decode.{type Decoder} import gleam/option.{type Option} import lustre/attribute.{type Attribute} import lustre/effect.{type Effect} import lustre/event /// WARNING: Lifecycle hooks in Lustre are currently limited to non-existent, /// so it's not possible to unsubscribe from context updates on component /// teardown. Be cautious using this effect with components that are not /// expected to be permanent fixtures on a page. (Refer to /// https://github.com/lustre-labs/lustre/issues/320.) pub fn request_context( context context: Dynamic, subscribe subscribe: Bool, decoder decoder: Decoder(msg), ) -> Effect(msg) { use dispatch, root <- effect.before_paint use value, _unsubscribe <- emit_context_request(root, context, subscribe) case decode.run(value, decoder) { Ok(msg) -> { dispatch(msg) } Error(_) -> Nil } Nil } @external(javascript, "./context.ffi.mjs", "emitContextRequest") fn emit_context_request( root _root: Dynamic, context _context: Dynamic, subscribe _subscribe: Bool, callback _callback: fn(Dynamic, Option(fn() -> Nil)) -> Nil, ) -> Nil { Nil } /// Capture "context-request" events querying a particular context type. /// Intended to be used in a context provider, on a child element near the /// component root. pub fn on_context_request( context match_context: Dynamic, handler handler: fn(Dynamic, Bool) -> msg, ) -> Attribute(msg) { event.advanced("context-request", { use ev_context <- decode.field("context", decode.dynamic) use subscribe <- decode.field("subscribe", decode.bool) use callback <- decode.field("callback", decode.dynamic) case ev_context == match_context { True -> { decode.success(event.handler( handler(callback, subscribe), False, ev_context == match_context, )) } False -> // returning a DecodeError seems like the only way to not trigger a message dispatch decode.failure( event.handler(handler(callback, subscribe), False, False), "Context", ) } }) } /// Effect for passing context value back to consumers who have requested it. pub fn update_consumers( callbacks callbacks: List(Dynamic), value value: Dynamic, ) -> Effect(msg) { use _dispatch <- effect.from do_update_consumers(callbacks:, value:) } fn do_update_consumers( callbacks callbacks: List(Dynamic), value value: Dynamic, ) -> Nil { case callbacks { [] -> Nil [cb, ..rest] -> { call_callback(callback: cb, value:) do_update_consumers(rest, value) } } } @external(javascript, "./context.ffi.mjs", "callCallback") fn call_callback(callback _callback: Dynamic, value _value: Dynamic) -> Nil { Nil }