use crate::{Key, caller_location_key, effect_key, with_current_composer};
#[derive(Default)]
pub(crate) struct DisposableEffectState {
key: Option<effect_key::EffectKey>,
cleanup: Option<Box<dyn FnOnce()>>,
}
impl DisposableEffectState {
fn should_run<K: PartialEq + 'static>(&self, keys: &K) -> bool {
self.key.as_ref().is_none_or(|current| !current.holds(keys))
}
fn set_key(&mut self, key: effect_key::EffectKey) {
self.key = Some(key);
}
fn set_cleanup(&mut self, cleanup: Option<Box<dyn FnOnce()>>) {
self.cleanup = cleanup;
}
fn run_cleanup(&mut self) {
if let Some(cleanup) = self.cleanup.take() {
cleanup();
}
}
}
impl Drop for DisposableEffectState {
fn drop(&mut self) {
self.run_cleanup();
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct DisposableEffectScope;
#[derive(Default)]
pub struct DisposableEffectResult {
cleanup: Option<Box<dyn FnOnce()>>,
}
impl DisposableEffectScope {
pub fn on_dispose(&self, cleanup: impl FnOnce() + 'static) -> DisposableEffectResult {
DisposableEffectResult::new(cleanup)
}
}
impl DisposableEffectResult {
pub fn new(cleanup: impl FnOnce() + 'static) -> Self {
Self {
cleanup: Some(Box::new(cleanup)),
}
}
fn into_cleanup(self) -> Option<Box<dyn FnOnce()>> {
self.cleanup
}
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
pub fn SideEffect(effect: impl FnOnce() + 'static) {
with_current_composer(|composer| composer.register_side_effect(effect));
}
#[doc(hidden)]
pub fn __disposable_effect_impl<K, F>(group_key: Key, keys: K, effect: F)
where
K: PartialEq + 'static,
F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
{
with_current_composer(|composer| {
let state = composer.remember_effect::<DisposableEffectState>(group_key);
if state.with(|state| state.should_run(&keys)) {
let key = effect_key::EffectKey::new(keys);
state.update(|state| {
state.run_cleanup();
state.set_key(key);
});
let mut effect_opt = Some(effect);
composer.register_side_effect(move || {
if let Some(effect) = effect_opt.take() {
let result = effect(DisposableEffectScope);
state.update(|state| state.set_cleanup(result.into_cleanup()));
}
});
}
});
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
#[track_caller]
pub fn DisposableEffect<K, F>(keys: K, effect: F)
where
K: PartialEq + 'static,
F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
{
__disposable_effect_impl(caller_location_key(), keys, effect);
}
#[macro_export]
macro_rules! clone_captures {
($($alias:ident $(= $value:expr)?),+ $(,)?; $body:expr) => {{
$(let $alias = $crate::clone_captures!(@clone $alias $(= $value)?);)+
$body
}};
(@clone $alias:ident = $value:expr) => {
($value).clone()
};
(@clone $alias:ident) => {
$alias.clone()
};
}