mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Fix portal shifting on reconciliation too often (#2891)
* fix portal shifting on reconciliation too often the public vdom api changes to only allow directly setting a Node as sibling (still optional) instead of a NodeRef. This was the intention all along, since the NodeRef was not dynamically tracked, and creating a portal into a subtree already controlled by yew is not supported anway. * fix feature soundness * fix doc tests
This commit is contained in:
parent
6c91afa13e
commit
0ecee11a2f
@ -48,6 +48,11 @@ impl Reconcilable for VPortal {
|
|||||||
inner_sibling,
|
inner_sibling,
|
||||||
node,
|
node,
|
||||||
} = self;
|
} = self;
|
||||||
|
let inner_sibling = {
|
||||||
|
let sib_ref = NodeRef::default();
|
||||||
|
sib_ref.set(inner_sibling);
|
||||||
|
sib_ref
|
||||||
|
};
|
||||||
let inner_root = root.create_subroot(parent.clone(), &host);
|
let inner_root = root.create_subroot(parent.clone(), &host);
|
||||||
let (_, inner) = node.attach(&inner_root, parent_scope, &host, inner_sibling.clone());
|
let (_, inner) = node.attach(&inner_root, parent_scope, &host, inner_sibling.clone());
|
||||||
(
|
(
|
||||||
@ -92,9 +97,11 @@ impl Reconcilable for VPortal {
|
|||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let old_host = std::mem::replace(&mut portal.host, host);
|
let old_host = std::mem::replace(&mut portal.host, host);
|
||||||
let old_inner_sibling = std::mem::replace(&mut portal.inner_sibling, inner_sibling);
|
|
||||||
|
|
||||||
if old_host != portal.host || old_inner_sibling != portal.inner_sibling {
|
let should_shift = old_host != portal.host || portal.inner_sibling.get() != inner_sibling;
|
||||||
|
portal.inner_sibling.set(inner_sibling);
|
||||||
|
|
||||||
|
if should_shift {
|
||||||
// Remount the inner node somewhere else instead of diffing
|
// Remount the inner node somewhere else instead of diffing
|
||||||
// Move the node, but keep the state
|
// Move the node, but keep the state
|
||||||
let inner_sibling = portal.inner_sibling.clone();
|
let inner_sibling = portal.inner_sibling.clone();
|
||||||
@ -123,12 +130,15 @@ impl BPortal {
|
|||||||
mod layout_tests {
|
mod layout_tests {
|
||||||
extern crate self as yew;
|
extern crate self as yew;
|
||||||
|
|
||||||
|
use gloo::utils::document;
|
||||||
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
|
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
|
||||||
|
use web_sys::HtmlInputElement;
|
||||||
use yew::virtual_dom::VPortal;
|
use yew::virtual_dom::VPortal;
|
||||||
|
|
||||||
use crate::html;
|
use super::*;
|
||||||
use crate::tests::layout_tests::{diff_layouts, TestLayout};
|
use crate::tests::layout_tests::{diff_layouts, TestLayout};
|
||||||
use crate::virtual_dom::VNode;
|
use crate::virtual_dom::VNode;
|
||||||
|
use crate::{create_portal, html};
|
||||||
|
|
||||||
wasm_bindgen_test_configure!(run_in_browser);
|
wasm_bindgen_test_configure!(run_in_browser);
|
||||||
|
|
||||||
@ -200,4 +210,43 @@ mod layout_tests {
|
|||||||
|
|
||||||
diff_layouts(layouts)
|
diff_layouts(layouts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_parent_with_portal() -> (BSubtree, AnyScope, Element, Element) {
|
||||||
|
let scope = AnyScope::test();
|
||||||
|
let parent = document().create_element("div").unwrap();
|
||||||
|
let portal_host = document().create_element("div").unwrap();
|
||||||
|
let root = BSubtree::create_root(&parent);
|
||||||
|
|
||||||
|
let body = document().body().unwrap();
|
||||||
|
body.append_child(&parent).unwrap();
|
||||||
|
body.append_child(&portal_host).unwrap();
|
||||||
|
|
||||||
|
(root, scope, parent, portal_host)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_shift() {
|
||||||
|
// Portals shouldn't shift (which e.g. causes internal inputs to unfocus) when sibling
|
||||||
|
// doesn't change.
|
||||||
|
let (root, scope, parent, portal_host) = setup_parent_with_portal();
|
||||||
|
let input_ref = NodeRef::default();
|
||||||
|
|
||||||
|
let portal = create_portal(
|
||||||
|
html! { <input type="text" ref={&input_ref} /> },
|
||||||
|
portal_host,
|
||||||
|
);
|
||||||
|
let (_, mut bundle) = portal
|
||||||
|
.clone()
|
||||||
|
.attach(&root, &scope, &parent, NodeRef::default());
|
||||||
|
|
||||||
|
// Focus the input, then reconcile again
|
||||||
|
let input_el = input_ref.cast::<HtmlInputElement>().unwrap();
|
||||||
|
input_el.focus().unwrap();
|
||||||
|
|
||||||
|
let _ = portal.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut bundle);
|
||||||
|
|
||||||
|
let new_input_el = input_ref.cast::<HtmlInputElement>().unwrap();
|
||||||
|
assert_eq!(input_el, new_input_el);
|
||||||
|
assert_eq!(document().active_element(), Some(new_input_el.into()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -124,13 +124,6 @@ impl NodeRef {
|
|||||||
let node = self.get();
|
let node = self.get();
|
||||||
node.map(Into::into).map(INTO::from)
|
node.map(Into::into).map(INTO::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Place a Node in a reference for later use
|
|
||||||
pub(crate) fn set(&self, node: Option<Node>) {
|
|
||||||
let mut this = self.0.borrow_mut();
|
|
||||||
this.node = node;
|
|
||||||
this.link = None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "csr")]
|
#[cfg(feature = "csr")]
|
||||||
@ -156,6 +149,13 @@ mod feat_csr {
|
|||||||
node_ref.set(Some(node));
|
node_ref.set(Some(node));
|
||||||
node_ref
|
node_ref
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Place a Node in a reference for later use
|
||||||
|
pub(crate) fn set(&self, node: Option<Node>) {
|
||||||
|
let mut this = self.0.borrow_mut();
|
||||||
|
this.node = node;
|
||||||
|
this.link = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,14 +3,13 @@
|
|||||||
use web_sys::{Element, Node};
|
use web_sys::{Element, Node};
|
||||||
|
|
||||||
use super::VNode;
|
use super::VNode;
|
||||||
use crate::html::NodeRef;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct VPortal {
|
pub struct VPortal {
|
||||||
/// The element under which the content is inserted.
|
/// The element under which the content is inserted.
|
||||||
pub host: Element,
|
pub host: Element,
|
||||||
/// The next sibling after the inserted content. Most be a child of `host`.
|
/// The next sibling after the inserted content. Most be a child of `host`.
|
||||||
pub inner_sibling: NodeRef,
|
pub inner_sibling: Option<Node>,
|
||||||
/// The inserted node
|
/// The inserted node
|
||||||
pub node: Box<VNode>,
|
pub node: Box<VNode>,
|
||||||
}
|
}
|
||||||
@ -20,7 +19,7 @@ impl VPortal {
|
|||||||
pub fn new(content: VNode, host: Element) -> Self {
|
pub fn new(content: VNode, host: Element) -> Self {
|
||||||
Self {
|
Self {
|
||||||
host,
|
host,
|
||||||
inner_sibling: NodeRef::default(),
|
inner_sibling: None,
|
||||||
node: Box::new(content),
|
node: Box::new(content),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,11 +30,7 @@ impl VPortal {
|
|||||||
pub fn new_before(content: VNode, host: Element, inner_sibling: Option<Node>) -> Self {
|
pub fn new_before(content: VNode, host: Element, inner_sibling: Option<Node>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
host,
|
host,
|
||||||
inner_sibling: {
|
inner_sibling,
|
||||||
let sib_ref = NodeRef::default();
|
|
||||||
sib_ref.set(inner_sibling);
|
|
||||||
sib_ref
|
|
||||||
},
|
|
||||||
node: Box::new(content),
|
node: Box::new(content),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -156,14 +156,18 @@ free to [chime in on this issue](https://github.com/yewstack/yew/issues/1334).
|
|||||||
Attributes are set on elements in the same way as in normal HTML:
|
Attributes are set on elements in the same way as in normal HTML:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
let value = "something";
|
let value = "something";
|
||||||
html! { <div attribute={value} />
|
html! { <div attribute={value} /> };
|
||||||
```
|
```
|
||||||
|
|
||||||
Properties are specified with `~` before the element name:
|
Properties are specified with `~` before the element name:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
html! { <my-element ~property="abc" /> }
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
html! { <my-element ~property="abc" /> };
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user