1
0
Fork 0
forked from 2sys/phonograph
phonograph/webc/src/context.gleam
2025-07-22 00:22:05 -07:00

93 lines
2.7 KiB
Gleam

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
}