diff --git a/Cargo.toml b/Cargo.toml index 64e59f7..5f495e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ futures-signals = "0.3.5" wasm-bindgen = "0.2.45" js-sys = "0.3.22" # TODO fix this before release -gloo = { git = "https://github.com/rustwasm/gloo", commit = "e8e505fbdbe96164381bd6fe7ef350438d54a84f" } +gloo = { git = "https://github.com/rustwasm/gloo" } [dependencies.wasm-bindgen-futures] version = "0.3.22" diff --git a/src/animation.rs b/src/animation.rs index 418b02b..d805f3f 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -25,6 +25,7 @@ struct RafState { } // TODO generalize this so it works for any target, not just JS +// TODO move this into gloo struct Raf { state: Rc>>, } @@ -468,6 +469,9 @@ impl SignalVec for AnimatedMap pub struct Percentage(f64); impl Percentage { + pub const START: Percentage = Percentage(0.0); + pub const END: Percentage = Percentage(1.0); + #[inline] pub fn new(input: f64) -> Self { debug_assert!(input >= 0.0 && input <= 1.0); diff --git a/src/dom.rs b/src/dom.rs index 0c968b5..85ba5ac 100644 --- a/src/dom.rs +++ b/src/dom.rs @@ -296,26 +296,30 @@ fn set_style(style: &CssStyleDeclaration, name: &A, value: B, important: b let mut names = vec![]; let mut values = vec![]; + fn try_set_style(style: &CssStyleDeclaration, names: &mut Vec, values: &mut Vec, name: &str, value: &str, important: bool) -> Result { + assert!(value != ""); + + // TODO handle browser prefixes ? + style.remove_property(name)?; + + style.set_property_with_priority(name, value, if important { "important" } else { "" })?; + + // TODO maybe use cfg(debug_assertions) ? + let is_changed = style.get_property_value(name)? != ""; + + if is_changed { + Ok(true) + + } else { + names.push(name.to_string()); + values.push(value.to_string()); + Ok(false) + } + } + let okay = name.any(|name| { value.any(|value| { - assert!(value != ""); - - // TODO handle browser prefixes ? - style.remove_property(name).unwrap_throw(); - - style.set_property_with_priority(name, value, if important { "important" } else { "" }).unwrap_throw(); - - // TODO maybe use cfg(debug_assertions) ? - let is_changed = style.get_property_value(name).unwrap_throw() != ""; - - if is_changed { - true - - } else { - names.push(name.to_string()); - values.push(value.to_string()); - false - } + try_set_style(style, &mut names, &mut values, name, value, important).unwrap_throw() }) }); @@ -527,7 +531,7 @@ impl DomBuilder where A: AsRef { #[inline] pub fn children<'a, B: IntoIterator>(mut self, children: B) -> Self { self.check_children(); - operations::insert_children_iter(self.element.as_ref(), &mut self.callbacks, children); + operations::insert_children_iter(self.element.as_ref(), &mut self.callbacks, children).unwrap_throw(); self } @@ -578,7 +582,7 @@ impl DomBuilder where A: AsRef { #[inline] pub fn attribute(self, name: B, value: &str) -> Self where B: MultiStr { name.each(|name| { - dom_operations::set_attribute(self.element.as_ref(), name, value); + dom_operations::set_attribute(self.element.as_ref(), name, value).unwrap_throw(); }); self } @@ -586,7 +590,7 @@ impl DomBuilder where A: AsRef { #[inline] pub fn attribute_namespace(self, namespace: &str, name: B, value: &str) -> Self where B: MultiStr { name.each(|name| { - dom_operations::set_attribute_ns(self.element.as_ref(), namespace, name, value); + dom_operations::set_attribute_ns(self.element.as_ref(), namespace, name, value).unwrap_throw(); }); self } @@ -596,7 +600,7 @@ impl DomBuilder where A: AsRef { let list = self.element.as_ref().class_list(); name.each(|name| { - dom_operations::add_class(&list, name); + dom_operations::add_class(&list, name).unwrap_throw(); }); self @@ -628,12 +632,12 @@ impl DomBuilder where A: AsRef { let value = value.as_str(); name.each(|name| { - dom_operations::set_attribute(element, &name, value); + dom_operations::set_attribute(element, &name, value).unwrap_throw(); }); }, None => { name.each(|name| { - dom_operations::remove_attribute(element, &name) + dom_operations::remove_attribute(element, &name).unwrap_throw(); }); }, } @@ -666,12 +670,12 @@ impl DomBuilder where A: AsRef { let value = value.as_str(); name.each(|name| { - dom_operations::set_attribute_ns(element, &namespace, &name, value); + dom_operations::set_attribute_ns(element, &namespace, &name, value).unwrap_throw(); }); }, None => { name.each(|name| { - dom_operations::remove_attribute_ns(element, &namespace, &name); + dom_operations::remove_attribute_ns(element, &namespace, &name).unwrap_throw(); }); }, } @@ -704,7 +708,7 @@ impl DomBuilder where A: AsRef { is_set = true; name.each(|name| { - dom_operations::add_class(&list, name); + dom_operations::add_class(&list, name).unwrap_throw(); }); } @@ -713,7 +717,7 @@ impl DomBuilder where A: AsRef { is_set = false; name.each(|name| { - dom_operations::remove_class(&list, name); + dom_operations::remove_class(&list, name).unwrap_throw(); }); } } @@ -816,7 +820,7 @@ impl DomBuilder where A: AsRef { // This needs to use `after_insert` because calling `.focus()` on an element before it is in the DOM has no effect self.callbacks.after_insert(move |_| { // TODO avoid updating if the focused state hasn't changed ? - dom_operations::set_focused(&element, value); + dom_operations::set_focused(&element, value).unwrap_throw(); }); self @@ -833,7 +837,7 @@ impl DomBuilder where A: AsRef { // TODO verify that this is correct under all circumstances callbacks.after_remove(for_each(value, move |value| { // TODO avoid updating if the focused state hasn't changed ? - dom_operations::set_focused(&element, value); + dom_operations::set_focused(&element, value).unwrap_throw(); })); }); } @@ -1035,14 +1039,14 @@ mod tests { builder.style("foo", "bar") } - a.apply(my_mixin); + let _ = a.apply(my_mixin); } #[test] fn text_signal_types() { - text_signal(always("foo")); - text_signal(always("foo".to_owned())); - text_signal(always("foo".to_owned()).map(|x| RefFn::new(x, |x| x.as_str()))); + let _ = text_signal(always("foo")); + let _ = text_signal(always("foo".to_owned())); + let _ = text_signal(always("foo".to_owned()).map(|x| RefFn::new(x, |x| x.as_str()))); //text_signal(always(Arc::new("foo"))); //text_signal(always(Arc::new("foo".to_owned()))); //text_signal(always(Rc::new("foo"))); diff --git a/src/dom_operations.rs b/src/dom_operations.rs index 24ebfa1..68b7203 100644 --- a/src/dom_operations.rs +++ b/src/dom_operations.rs @@ -1,4 +1,4 @@ -use wasm_bindgen::UnwrapThrowExt; +use wasm_bindgen::{JsValue, UnwrapThrowExt}; use web_sys::{Node, HtmlElement, Element, DomTokenList}; @@ -9,75 +9,88 @@ pub(crate) fn get_at(parent: &Node, index: u32) -> Node { // TODO make this more efficient #[inline] -pub(crate) fn move_from_to(parent: &Node, old_index: u32, new_index: u32) { +pub(crate) fn move_from_to(parent: &Node, old_index: u32, new_index: u32) -> Result<(), JsValue> { let child = get_at(parent, old_index); - parent.remove_child(&child).unwrap_throw(); + parent.remove_child(&child)?; - insert_at(parent, new_index, &child); + insert_at(parent, new_index, &child)?; + + Ok(()) } // TODO make this more efficient #[inline] -pub(crate) fn insert_at(parent: &Node, index: u32, child: &Node) { - parent.insert_before(child, Some(&get_at(parent, index))).unwrap_throw(); +pub(crate) fn insert_at(parent: &Node, index: u32, child: &Node) -> Result<(), JsValue> { + parent.insert_before(child, Some(&get_at(parent, index)))?; + Ok(()) } // TODO make this more efficient #[inline] -pub(crate) fn update_at(parent: &Node, index: u32, child: &Node) { - parent.replace_child(child, &get_at(parent, index)).unwrap_throw(); +pub(crate) fn update_at(parent: &Node, index: u32, child: &Node) -> Result<(), JsValue> { + parent.replace_child(child, &get_at(parent, index))?; + Ok(()) } // TODO make this more efficient #[inline] -pub(crate) fn remove_at(parent: &Node, index: u32) { - parent.remove_child(&get_at(parent, index)).unwrap_throw(); +pub(crate) fn remove_at(parent: &Node, index: u32) -> Result<(), JsValue> { + parent.remove_child(&get_at(parent, index))?; + Ok(()) } #[inline] -pub(crate) fn set_focused(element: &HtmlElement, focused: bool) { +pub(crate) fn set_focused(element: &HtmlElement, focused: bool) -> Result<(), JsValue> { if focused { - element.focus().unwrap_throw(); + element.focus()?; } else { - element.blur().unwrap_throw(); + element.blur()?; } + + Ok(()) } #[inline] -pub(crate) fn add_class(list: &DomTokenList, name: &str) { - list.add_1(name).unwrap_throw(); +pub(crate) fn add_class(list: &DomTokenList, name: &str) -> Result<(), JsValue> { + list.add_1(name)?; + Ok(()) } #[inline] -pub(crate) fn remove_class(list: &DomTokenList, name: &str) { - list.remove_1(name).unwrap_throw(); +pub(crate) fn remove_class(list: &DomTokenList, name: &str) -> Result<(), JsValue> { + list.remove_1(name)?; + Ok(()) } // TODO check that the attribute *actually* was changed #[inline] -pub(crate) fn set_attribute(element: &Element, name: &str, value: &str) { - element.set_attribute(name, value).unwrap_throw(); +pub(crate) fn set_attribute(element: &Element, name: &str, value: &str) -> Result<(), JsValue> { + element.set_attribute(name, value)?; + Ok(()) } // TODO check that the attribute *actually* was changed #[inline] -pub(crate) fn set_attribute_ns(element: &Element, namespace: &str, name: &str, value: &str) { - element.set_attribute_ns(Some(namespace), name, value).unwrap_throw(); +pub(crate) fn set_attribute_ns(element: &Element, namespace: &str, name: &str, value: &str) -> Result<(), JsValue> { + element.set_attribute_ns(Some(namespace), name, value)?; + Ok(()) } #[inline] -pub(crate) fn remove_attribute_ns(element: &Element, namespace: &str, name: &str) { - element.remove_attribute_ns(Some(namespace), name).unwrap_throw(); +pub(crate) fn remove_attribute_ns(element: &Element, namespace: &str, name: &str) -> Result<(), JsValue> { + element.remove_attribute_ns(Some(namespace), name)?; + Ok(()) } #[inline] -pub(crate) fn remove_attribute(element: &Element, name: &str) { - element.remove_attribute(name).unwrap_throw(); +pub(crate) fn remove_attribute(element: &Element, name: &str) -> Result<(), JsValue> { + element.remove_attribute(name)?; + Ok(()) } diff --git a/src/operations.rs b/src/operations.rs index 89ddaaf..752a98b 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -8,7 +8,7 @@ use futures_signals::{cancelable_future, CancelableFutureHandle}; use futures_signals::signal::{Signal, SignalExt}; use futures_signals::signal_vec::{VecDiff, SignalVec, SignalVecExt}; use web_sys::Node; -use wasm_bindgen::UnwrapThrowExt; +use wasm_bindgen::{JsValue, UnwrapThrowExt}; use wasm_bindgen_futures::futures_0_3::spawn_local; use crate::dom_operations; @@ -82,14 +82,16 @@ pub fn insert_children_signal(element: &A, callbacks: &mut Callbacks, s }*/ #[inline] -pub(crate) fn insert_children_iter<'a, A: IntoIterator>(element: &Node, callbacks: &mut Callbacks, value: A) { +pub(crate) fn insert_children_iter<'a, A: IntoIterator>(element: &Node, callbacks: &mut Callbacks, value: A) -> Result<(), JsValue> { for dom in value.into_iter() { // TODO can this be made more efficient ? callbacks.after_insert.append(&mut dom.callbacks.after_insert); callbacks.after_remove.append(&mut dom.callbacks.after_remove); - element.append_child(&dom.element).unwrap_throw(); + element.append_child(&dom.element)?; } + + Ok(()) } @@ -124,15 +126,12 @@ pub(crate) fn insert_children_signal_vec(element: Node, callbacks: &mut Callb }); } - // TODO verify that this will drop `children` - callbacks.after_remove(for_each_vec(signal, move |change| { - let mut state = state.lock().unwrap_throw(); - + fn process_change(state: &mut State, element: &Node, change: VecDiff) -> Result<(), JsValue> { match change { VecDiff::Replace { values } => { // TODO is this correct ? if state.children.len() > 0 { - dom_operations::remove_all_children(&element); + dom_operations::remove_all_children(element); for dom in state.children.drain(..) { dom.callbacks.discard(); @@ -151,14 +150,14 @@ pub(crate) fn insert_children_signal_vec(element: Node, callbacks: &mut Callb for dom in state.children.iter_mut() { dom.callbacks.leak(); - fragment.append_child(&dom.element).unwrap_throw(); + fragment.append_child(&dom.element)?; if !dom.callbacks.after_insert.is_empty() { has_inserts = true; } } - element.append_child(&fragment).unwrap_throw(); + element.append_child(&fragment)?; if is_inserted && has_inserts { for dom in state.children.iter_mut() { @@ -169,7 +168,7 @@ pub(crate) fn insert_children_signal_vec(element: Node, callbacks: &mut Callb VecDiff::InsertAt { index, mut value } => { // TODO better usize -> u32 conversion - dom_operations::insert_at(&element, index as u32, &value.element); + dom_operations::insert_at(element, index as u32, &value.element)?; value.callbacks.leak(); @@ -182,7 +181,7 @@ pub(crate) fn insert_children_signal_vec(element: Node, callbacks: &mut Callb }, VecDiff::Push { mut value } => { - element.append_child(&value.element).unwrap_throw(); + element.append_child(&value.element)?; value.callbacks.leak(); @@ -196,7 +195,7 @@ pub(crate) fn insert_children_signal_vec(element: Node, callbacks: &mut Callb VecDiff::UpdateAt { index, mut value } => { // TODO better usize -> u32 conversion - dom_operations::update_at(&element, index as u32, &value.element); + dom_operations::update_at(element, index as u32, &value.element)?; value.callbacks.leak(); @@ -217,12 +216,12 @@ pub(crate) fn insert_children_signal_vec(element: Node, callbacks: &mut Callb state.children.insert(new_index, value); // TODO better usize -> u32 conversion - dom_operations::move_from_to(&element, old_index as u32, new_index as u32); + dom_operations::move_from_to(element, old_index as u32, new_index as u32)?; }, VecDiff::RemoveAt { index } => { // TODO better usize -> u32 conversion - dom_operations::remove_at(&element, index as u32); + dom_operations::remove_at(element, index as u32)?; state.children.remove(index).callbacks.discard(); }, @@ -232,7 +231,7 @@ pub(crate) fn insert_children_signal_vec(element: Node, callbacks: &mut Callb // TODO create remove_last_child function ? // TODO better usize -> u32 conversion - dom_operations::remove_at(&element, index as u32); + dom_operations::remove_at(element, index as u32)?; state.children.pop().unwrap_throw().callbacks.discard(); }, @@ -241,7 +240,7 @@ pub(crate) fn insert_children_signal_vec(element: Node, callbacks: &mut Callb // TODO is this correct ? // TODO is this needed, or is it guaranteed by VecDiff ? if state.children.len() > 0 { - dom_operations::remove_all_children(&element); + dom_operations::remove_all_children(element); for dom in state.children.drain(..) { dom.callbacks.discard(); @@ -249,5 +248,14 @@ pub(crate) fn insert_children_signal_vec(element: Node, callbacks: &mut Callb } }, } + + Ok(()) + } + + // TODO verify that this will drop `children` + callbacks.after_remove(for_each_vec(signal, move |change| { + let mut state = state.lock().unwrap_throw(); + + process_change(&mut state, &element, change).unwrap_throw(); })); } diff --git a/src/routing.rs b/src/routing.rs index 87704a1..1a0add9 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -42,22 +42,24 @@ impl State { }*/ -fn current_url_string() -> String { - window().unwrap_throw().location().href().unwrap_throw() +fn current_url_string() -> Result { + Ok(window().unwrap_throw().location().href()?) } // TODO inline ? -fn change_url(mutable: &Mutable) { +fn change_url(mutable: &Mutable) -> Result<(), JsValue> { let mut lock = mutable.lock_mut(); - let new_url = current_url_string(); + let new_url = current_url_string()?; // TODO test that this doesn't notify if the URLs are the same // TODO helper method for this // TODO can this be made more efficient ? if lock.href() != new_url { - *lock = Url::new(&new_url).unwrap_throw(); + *lock = Url::new(&new_url)?; } + + Ok(()) } @@ -67,25 +69,25 @@ struct CurrentUrl { } impl CurrentUrl { - fn new() -> Self { + fn new() -> Result { // TODO can this be made more efficient ? - let value = Mutable::new(Url::new(¤t_url_string()).unwrap_throw()); + let value = Mutable::new(Url::new(¤t_url_string()?)?); - Self { + Ok(Self { _listener: EventListener::new(&window().unwrap_throw(), "popstate", { let value = value.clone(); move |_| { - change_url(&value); + change_url(&value).unwrap_throw(); } }), value, - } + }) } } // TODO somehow share this safely between threads ? thread_local! { - static URL: CurrentUrl = CurrentUrl::new(); + static URL: CurrentUrl = CurrentUrl::new().unwrap_throw(); } @@ -113,7 +115,7 @@ pub fn go_to_url(new_url: &str) { .unwrap_throw(); URL.with(|url| { - change_url(&url.value); + change_url(&url.value).unwrap_throw(); }); }