Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/core/src/diff/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ impl VNode {
} else {
to.remove_node(id);
}
to.free_id(id);
dom.reclaim(id);
} else {
let id = dom.get_mounted_root_node(mount, idx);
Expand Down Expand Up @@ -382,6 +383,7 @@ impl VNode {
} else {
to.remove_node(id);
}
to.free_id(id);
}
dom.reclaim(id)
}
Expand Down
25 changes: 25 additions & 0 deletions packages/core/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ impl<T: ?Sized> Event<T> {
}
}

/// Create a new event with different data but the same metadata.
///
/// Unlike `map`, this takes an `Rc` directly, allowing you to share
/// ownership of the data (e.g., for accessing it after the handler returns).
pub fn with_data<U: 'static>(&self, data: Rc<U>) -> Event<U> {
Event {
data,
metadata: self.metadata.clone(),
}
}

/// Prevent this event from continuing to bubble up the tree to parent elements.
///
/// # Example
Expand Down Expand Up @@ -656,6 +667,20 @@ impl<T> ListenerCallback<T> {
}
}

/// Create a new [`ListenerCallback`] from a raw callback that receives `Event<dyn Any>`.
///
/// This is useful when you need custom downcast logic, such as handling multiple
/// possible event data types.
///
/// This is expected to be called within a runtime scope.
pub fn new_raw(f: impl FnMut(Event<dyn Any>) + 'static) -> Self {
Self {
origin: current_scope_id(),
callback: Rc::new(RefCell::new(f)),
_marker: PhantomData,
}
}

/// Call the callback with an event
///
/// This is expected to be called within a runtime scope. Make sure a runtime is current before
Expand Down
26 changes: 26 additions & 0 deletions packages/core/src/mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,25 @@ pub trait WriteMutations {
///
/// Id: The ID of the root node to push.
fn push_root(&mut self, id: ElementId);

/// Signal that an element ID has been freed and can be reused.
///
/// Renderers should invoke any cleanup callbacks associated with this element
/// and release any resources (e.g., garbage collect DOM nodes).
///
/// Id: The ID of the element being freed.
///
/// Default implementation is a no-op for backward compatibility with existing
/// custom renderers. Override this method to support cleanup callbacks.
fn free_id(&mut self, _id: ElementId) {}
}

/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
/// of the Dioxus VirtualDom.
///
/// These edits can be serialized and sent over the network or through any interface
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum Mutation {
/// Add these m children to the target element
AppendChildren {
Expand Down Expand Up @@ -279,6 +291,14 @@ pub enum Mutation {
/// The ID of the root node to push.
id: ElementId,
},

/// Signal that an element ID has been freed and can be reused.
///
/// Renderers should invoke any cleanup callbacks and release resources.
FreeId {
/// The ID of the element being freed.
id: ElementId,
},
}

/// A static list of mutations that can be applied to the DOM. Note: this list does not contain any `Any` attribute values
Expand Down Expand Up @@ -378,6 +398,10 @@ impl WriteMutations for Mutations {
fn push_root(&mut self, id: ElementId) {
self.edits.push(Mutation::PushRoot { id })
}

fn free_id(&mut self, id: ElementId) {
self.edits.push(Mutation::FreeId { id })
}
}

/// A struct that ignores all mutations
Expand Down Expand Up @@ -420,4 +444,6 @@ impl WriteMutations for NoOpMutations {
fn remove_node(&mut self, _: ElementId) {}

fn push_root(&mut self, _: ElementId) {}

fn free_id(&mut self, _: ElementId) {}
}
13 changes: 10 additions & 3 deletions packages/core/tests/attr_cleanup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,20 @@ fn attrs_cycle() {
SetAttribute { name: "class", value: "1".into_value(), id: ElementId(3,), ns: None },
SetAttribute { name: "id", value: "1".into_value(), id: ElementId(3,), ns: None },
ReplaceWith { id: ElementId(1,), m: 1 },
FreeId { id: ElementId(1) },
]
);

// Going from div { h1 { ... } } back to div {}
// ElementId(2) is the outer div, ElementId(3) is the h1
// Only ElementId(2) is freed because ElementId(3) was a child (handled internally)
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(1) },
ReplaceWith { id: ElementId(2), m: 1 }
ReplaceWith { id: ElementId(2), m: 1 },
FreeId { id: ElementId(2) },
]
);

Expand All @@ -67,7 +72,8 @@ fn attrs_cycle() {
id: ElementId(3),
ns: None
},
ReplaceWith { id: ElementId(1), m: 1 }
ReplaceWith { id: ElementId(1), m: 1 },
FreeId { id: ElementId(1) },
]
);

Expand All @@ -77,7 +83,8 @@ fn attrs_cycle() {
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(1) },
ReplaceWith { id: ElementId(2), m: 1 }
ReplaceWith { id: ElementId(2), m: 1 },
FreeId { id: ElementId(2) },
]
);
}
3 changes: 3 additions & 0 deletions packages/core/tests/cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ fn cycling_elements() {
[
LoadTemplate { index: 0, id: ElementId(2,) },
ReplaceWith { id: ElementId(1,), m: 1 },
FreeId { id: ElementId(1) },
]
);

Expand All @@ -38,6 +39,7 @@ fn cycling_elements() {
[
LoadTemplate { index: 0, id: ElementId(1,) },
ReplaceWith { id: ElementId(2,), m: 1 },
FreeId { id: ElementId(2) },
]
);

Expand All @@ -47,6 +49,7 @@ fn cycling_elements() {
[
LoadTemplate { index: 0, id: ElementId(2,) },
ReplaceWith { id: ElementId(1,), m: 1 },
FreeId { id: ElementId(1) },
]
);
}
9 changes: 6 additions & 3 deletions packages/core/tests/diff_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ fn component_swap() {
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(6) },
ReplaceWith { id: ElementId(5), m: 1 }
ReplaceWith { id: ElementId(5), m: 1 },
FreeId { id: ElementId(5) },
]
);

Expand All @@ -101,7 +102,8 @@ fn component_swap() {
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(5) },
ReplaceWith { id: ElementId(6), m: 1 }
ReplaceWith { id: ElementId(6), m: 1 },
FreeId { id: ElementId(6) },
]
);

Expand All @@ -110,7 +112,8 @@ fn component_swap() {
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(6) },
ReplaceWith { id: ElementId(5), m: 1 }
ReplaceWith { id: ElementId(5), m: 1 },
FreeId { id: ElementId(5) },
]
);
}
6 changes: 6 additions & 0 deletions packages/core/tests/diff_dynamic_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fn toggle_option_text() {
[
CreateTextNode { value: "hello".to_string(), id: ElementId(3,) },
ReplaceWith { id: ElementId(2,), m: 1 },
FreeId { id: ElementId(2) },
]
);

Expand All @@ -45,6 +46,7 @@ fn toggle_option_text() {
[
CreatePlaceholder { id: ElementId(2,) },
ReplaceWith { id: ElementId(3,), m: 1 },
FreeId { id: ElementId(3) },
]
);
}
Expand Down Expand Up @@ -83,6 +85,7 @@ fn toggle_template() {
[
CreatePlaceholder { id: ElementId(2) },
ReplaceWith { id: ElementId(1), m: 1 },
FreeId { id: ElementId(1) },
]
);

Expand All @@ -92,6 +95,7 @@ fn toggle_template() {
[
CreateTextNode { value: "true".to_string(), id: ElementId(1) },
ReplaceWith { id: ElementId(2), m: 1 },
FreeId { id: ElementId(2) },
]
);

Expand All @@ -101,6 +105,7 @@ fn toggle_template() {
[
CreatePlaceholder { id: ElementId(2) },
ReplaceWith { id: ElementId(1), m: 1 },
FreeId { id: ElementId(1) },
]
);

Expand All @@ -110,6 +115,7 @@ fn toggle_template() {
[
CreateTextNode { value: "true".to_string(), id: ElementId(1) },
ReplaceWith { id: ElementId(2), m: 1 },
FreeId { id: ElementId(2) },
]
);
}
5 changes: 5 additions & 0 deletions packages/core/tests/diff_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ fn element_swap() {
[
LoadTemplate { index: 0, id: ElementId(2,) },
ReplaceWith { id: ElementId(1,), m: 1 },
FreeId { id: ElementId(1) },
]
);

Expand All @@ -62,6 +63,7 @@ fn element_swap() {
[
LoadTemplate { index: 0, id: ElementId(1,) },
ReplaceWith { id: ElementId(2,), m: 1 },
FreeId { id: ElementId(2) },
]
);

Expand All @@ -71,6 +73,7 @@ fn element_swap() {
[
LoadTemplate { index: 0, id: ElementId(2,) },
ReplaceWith { id: ElementId(1,), m: 1 },
FreeId { id: ElementId(1) },
]
);

Expand All @@ -80,6 +83,7 @@ fn element_swap() {
[
LoadTemplate { index: 0, id: ElementId(1,) },
ReplaceWith { id: ElementId(2,), m: 1 },
FreeId { id: ElementId(2) },
]
);
}
Expand Down Expand Up @@ -204,6 +208,7 @@ fn diff_empty() {
[
CreatePlaceholder { id: ElementId(2,) },
ReplaceWith { id: ElementId(1,), m: 1 },
FreeId { id: ElementId(1) },
]
)
}
10 changes: 9 additions & 1 deletion packages/core/tests/diff_keyed_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ fn controlled_keyed_diffing_out_of_order() {
[
// remove 7
Remove { id: ElementId(4,) },
FreeId { id: ElementId(4,) },
// move 4 to after 6
PushRoot { id: ElementId(1) },
InsertAfter { id: ElementId(3,), m: 1 },
Expand Down Expand Up @@ -295,6 +296,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
dom.render_immediate_to_vec().edits,
[
Remove { id: ElementId(5,) },
FreeId { id: ElementId(5,) },
LoadTemplate { index: 0, id: ElementId(5) },
InsertBefore { id: ElementId(3,), m: 1 },
PushRoot { id: ElementId(4) },
Expand Down Expand Up @@ -324,8 +326,11 @@ fn remove_list() {
dom.render_immediate_to_vec().edits,
[
Remove { id: ElementId(5) },
FreeId { id: ElementId(5) },
Remove { id: ElementId(4) },
FreeId { id: ElementId(4) },
Remove { id: ElementId(3) },
FreeId { id: ElementId(3) },
]
);
}
Expand All @@ -352,8 +357,11 @@ fn no_common_keys() {
LoadTemplate { index: 0, id: ElementId(5) },
LoadTemplate { index: 0, id: ElementId(6) },
Remove { id: ElementId(3) },
FreeId { id: ElementId(3) },
Remove { id: ElementId(2) },
ReplaceWith { id: ElementId(1), m: 3 }
FreeId { id: ElementId(2) },
ReplaceWith { id: ElementId(1), m: 3 },
FreeId { id: ElementId(1) },
]
);
}
Expand Down
Loading