diff --git a/src/off_topic.gleam b/src/off_topic.gleam index f6d8ffc..5552ec7 100644 --- a/src/off_topic.gleam +++ b/src/off_topic.gleam @@ -105,6 +105,7 @@ //// [window_hover](#window_hover "Dispatch the current window hover state"), //// [title](#title "Update the document title"), //// [meta](#meta "Upsert a document meta tag"), +//// [body_class](#body_class "Set a class on the body tag"), //// [prevent_unload](#prevent_unload "Block the browser unload dialog") //// //// #### DOM @@ -267,6 +268,49 @@ pub fn script() -> Element(msg) { ) } +// Run the application init function and start subscriptions. +/// +/// The low-level counterpart to [application](#application), for use when you +/// need to compose off_topic into an existing `lustre.application` manually. +/// +/// See also: [update](#update), [view](#view), [application](#application) +pub fn init( + flags flags: flags, + init init: fn(flags) -> #(model, Effect(message)), +) -> #(Model(model, message), Effect(Message(message))) { + runtime.init(flags, init) +} + +/// Handle a message in the subscription runtime. +/// +/// The low-level counterpart to [application](#application), for use when you +/// need to compose off_topic into an existing `lustre.application` manually. +/// +/// See also: [init](#init), [view](#view), [application](#application) +pub fn update( + model: Model(model, message), + message: Message(message), + subscriptions: fn(model) -> Subscription(message), + update: fn(model, message) -> #(model, Effect(message)), +) -> #(Model(model, message), Effect(Message(message))) { + runtime.update(model, message, subscriptions, update) +} + +/// Render the application view through the subscription runtime. +/// +/// The low-level counterpart to [application](#application), for use when you +/// need to compose off_topic into an existing `lustre.application` manually. +/// On the server target, this also injects the `ot-client-runtime` web +/// component into the rendered output. +/// +/// See also: [init](#init), [update](#update), [application](#application) +pub fn view( + model: Model(model, message), + view: fn(model) -> Element(message), +) -> Element(Message(message)) { + runtime.view(model, view) +} + // -- MAKING SUBSCRIPTIONS ---------------------------------------------------- /// A subscription that never fires. @@ -938,6 +982,23 @@ pub fn meta( } } +/// Add a CSS class to the document body. +/// +/// The class is removed when the subscription stops or the class name changes. +/// +/// See also: [title](#title) +pub fn body_class(class_name: String) -> Subscription(message) { + case lustre.is_browser() { + True -> { + use _ <- from(watching: [dep(class_name)]) + subscriptions.body_class(class_name) + } + False -> { + remote("off-topic/body-class", [json.string(class_name)], no_decoder()) + } + } +} + /// Block the user from immediately leaving the page when enabled, showing a /// simple popup instead. /// diff --git a/src/off_topic/internal/component.mjs b/src/off_topic/internal/component.mjs index 6606a0b..1ec3efe 100644 --- a/src/off_topic/internal/component.mjs +++ b/src/off_topic/internal/component.mjs @@ -203,6 +203,7 @@ Object.assign(window.OffTopic.subscriptions, { "off-topic/session-storage": Subscriptions.session_storage, "off-topic/title": Subscriptions.title, "off-topic/meta": Subscriptions.meta, + "off-topic/body-class": Subscriptions.body_class, "off-topic/aria": Subscriptions.aria, "off-topic/pseudo-state": Subscriptions.pseudo_state, "off-topic/form-value": Subscriptions.form_value, diff --git a/src/off_topic/internal/subscriptions.gleam b/src/off_topic/internal/subscriptions.gleam index ad4a07a..240a410 100644 --- a/src/off_topic/internal/subscriptions.gleam +++ b/src/off_topic/internal/subscriptions.gleam @@ -99,6 +99,11 @@ pub fn meta(_name: String, _content: String) -> fn() -> Nil { fn() { Nil } } +@external(javascript, "./subscriptions_ffi.mjs", "body_class") +pub fn body_class(_name: String) -> fn() -> Nil { + fn() { Nil } +} + @external(javascript, "./subscriptions_ffi.mjs", "aria") pub fn aria( _name: String, diff --git a/src/off_topic/internal/subscriptions_ffi.mjs b/src/off_topic/internal/subscriptions_ffi.mjs index ecbff5d..5d6741f 100644 --- a/src/off_topic/internal/subscriptions_ffi.mjs +++ b/src/off_topic/internal/subscriptions_ffi.mjs @@ -271,6 +271,13 @@ export function meta(name, content) { return noop; } +export function body_class(className) { + const classes = className.split(" "); + + document.body.classList.add(...classes); + return () => document.body.classList.remove(...classes); +} + export function aria(name, value, _handler, root) { const host = root instanceof ShadowRoot ? root.host : root; const internals = host?.internals;