2018-10-22 20:16:54 -04:00
|
|
|
use std::pin::Pin;
|
2020-04-27 01:36:48 -04:00
|
|
|
use std::borrow::BorrowMut;
|
2018-09-17 17:17:55 -04:00
|
|
|
use std::convert::AsRef;
|
2019-04-28 20:30:55 -04:00
|
|
|
use std::future::Future;
|
|
|
|
use std::task::{Context, Poll};
|
2019-06-11 09:29:46 -04:00
|
|
|
|
2021-05-28 09:18:14 -04:00
|
|
|
use once_cell::sync::Lazy;
|
2018-10-25 19:44:17 -04:00
|
|
|
use futures_signals::signal::{Signal, not};
|
2018-10-22 20:16:54 -04:00
|
|
|
use futures_signals::signal_vec::SignalVec;
|
|
|
|
use futures_util::FutureExt;
|
2018-07-04 07:34:13 -04:00
|
|
|
use futures_channel::oneshot;
|
2018-03-16 02:50:21 -04:00
|
|
|
use discard::{Discard, DiscardOnDrop};
|
2019-07-23 00:51:40 -04:00
|
|
|
use wasm_bindgen::{JsValue, UnwrapThrowExt, JsCast, intern};
|
2020-06-01 21:08:34 -04:00
|
|
|
use web_sys::{HtmlElement, Node, EventTarget, Element, CssStyleSheet, CssStyleDeclaration, ShadowRoot, ShadowRootMode, ShadowRootInit, Text};
|
2019-06-11 09:29:46 -04:00
|
|
|
|
2019-06-20 19:57:47 -04:00
|
|
|
use crate::bindings;
|
2019-06-11 09:29:46 -04:00
|
|
|
use crate::callbacks::Callbacks;
|
|
|
|
use crate::traits::*;
|
|
|
|
use crate::operations;
|
|
|
|
use crate::operations::{for_each, spawn_future};
|
2021-10-17 04:31:02 -04:00
|
|
|
use crate::utils::{EventListener, on, ValueDiscard, FnDiscard};
|
2018-02-25 06:58:20 -05:00
|
|
|
|
|
|
|
|
2020-01-19 22:40:39 -05:00
|
|
|
pub struct RefFn<A, B, C> where B: ?Sized, C: Fn(&A) -> &B {
|
2018-06-22 13:04:08 -04:00
|
|
|
value: A,
|
2018-09-17 17:17:55 -04:00
|
|
|
callback: C,
|
2018-06-22 13:04:08 -04:00
|
|
|
}
|
|
|
|
|
2018-09-17 17:17:55 -04:00
|
|
|
impl<A, B, C> RefFn<A, B, C> where B: ?Sized, C: Fn(&A) -> &B {
|
2018-06-22 13:04:08 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn new(value: A, callback: C) -> Self {
|
2018-09-17 17:17:55 -04:00
|
|
|
Self {
|
|
|
|
value,
|
|
|
|
callback,
|
|
|
|
}
|
2018-06-22 13:04:08 -04:00
|
|
|
}
|
2018-07-13 18:26:45 -04:00
|
|
|
|
2018-09-17 17:17:55 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn call_ref(&self) -> &B {
|
|
|
|
(self.callback)(&self.value)
|
|
|
|
}
|
|
|
|
|
|
|
|
/*pub fn map<D, E>(self, callback: E) -> RefFn<A, impl Fn(&A) -> &D>
|
2018-07-13 18:26:45 -04:00
|
|
|
where D: ?Sized,
|
|
|
|
E: Fn(&B) -> &D {
|
|
|
|
|
|
|
|
let old_callback = self.callback;
|
|
|
|
|
2018-09-17 17:17:55 -04:00
|
|
|
RefFn {
|
2018-07-13 18:26:45 -04:00
|
|
|
value: self.value,
|
|
|
|
callback: move |value| callback(old_callback(value)),
|
|
|
|
}
|
|
|
|
}*/
|
2018-06-22 13:04:08 -04:00
|
|
|
}
|
|
|
|
|
2018-09-17 17:17:55 -04:00
|
|
|
/*impl<A, B, C> Deref for RefFn<A, C> where B: ?Sized, C: Fn(&A) -> &B {
|
2018-06-22 13:04:08 -04:00
|
|
|
type Target = B;
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn deref(&self) -> &Self::Target {
|
2018-09-17 17:17:55 -04:00
|
|
|
self.call_ref()
|
2018-06-22 13:04:08 -04:00
|
|
|
}
|
2018-09-17 17:17:55 -04:00
|
|
|
}*/
|
|
|
|
|
|
|
|
/*impl<A, B, C> AsRef<B> for RefFn<A, C> where B: ?Sized, C: Fn(&A) -> &B {
|
|
|
|
#[inline]
|
|
|
|
fn as_ref(&self) -> &B {
|
|
|
|
self.call_ref()
|
|
|
|
}
|
|
|
|
}*/
|
2018-06-22 13:04:08 -04:00
|
|
|
|
|
|
|
|
2018-02-21 01:37:09 -05:00
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#Valid%20Namespace%20URIs
|
2019-08-04 22:07:50 -04:00
|
|
|
const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
|
2018-02-21 01:37:09 -05:00
|
|
|
|
2018-06-12 03:06:51 -04:00
|
|
|
// 32-bit signed int
|
|
|
|
pub const HIGHEST_ZINDEX: &str = "2147483647";
|
|
|
|
|
2018-02-21 01:37:09 -05:00
|
|
|
|
2021-05-28 09:18:14 -04:00
|
|
|
static HIDDEN_CLASS: Lazy<String> = Lazy::new(|| class! {
|
|
|
|
.style_important("display", "none")
|
|
|
|
});
|
2018-11-24 15:59:46 -05:00
|
|
|
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
// TODO should return HtmlBodyElement ?
|
2018-03-18 14:49:40 -04:00
|
|
|
pub fn body() -> HtmlElement {
|
2019-06-20 19:57:47 -04:00
|
|
|
bindings::body()
|
2018-03-18 14:49:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-22 22:45:31 -04:00
|
|
|
pub fn get_id(id: &str) -> Element {
|
2019-07-23 00:51:40 -04:00
|
|
|
// TODO intern ?
|
|
|
|
bindings::get_element_by_id(id)
|
2019-07-22 22:45:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
pub struct DomHandle {
|
|
|
|
parent: Node,
|
|
|
|
dom: Dom,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Discard for DomHandle {
|
|
|
|
#[inline]
|
|
|
|
fn discard(self) {
|
2019-06-20 19:57:47 -04:00
|
|
|
bindings::remove_child(&self.parent, &self.dom.element);
|
2018-03-15 06:54:18 -04:00
|
|
|
self.dom.callbacks.discard();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-25 06:58:20 -05:00
|
|
|
#[inline]
|
2019-06-11 12:20:15 -04:00
|
|
|
pub fn append_dom(parent: &Node, mut dom: Dom) -> DomHandle {
|
2019-06-20 19:57:47 -04:00
|
|
|
bindings::append_child(&parent, &dom.element);
|
2018-02-21 01:37:09 -05:00
|
|
|
|
2018-02-25 06:58:20 -05:00
|
|
|
dom.callbacks.trigger_after_insert();
|
2018-02-21 01:37:09 -05:00
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
// This prevents it from triggering after_remove
|
|
|
|
dom.callbacks.leak();
|
|
|
|
|
|
|
|
DomHandle {
|
2019-06-11 12:20:15 -04:00
|
|
|
parent: parent.clone(),
|
2019-06-11 09:29:46 -04:00
|
|
|
dom,
|
2018-07-04 07:34:13 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-15 05:23:38 -04:00
|
|
|
// TODO use must_use ?
|
2018-07-04 07:51:39 -04:00
|
|
|
enum IsWindowLoaded {
|
2018-07-04 07:34:13 -04:00
|
|
|
Initial {},
|
|
|
|
Pending {
|
2019-06-11 09:29:46 -04:00
|
|
|
receiver: oneshot::Receiver<Option<bool>>,
|
|
|
|
_event: EventListener,
|
2018-07-04 07:34:13 -04:00
|
|
|
},
|
|
|
|
Done {},
|
|
|
|
}
|
|
|
|
|
2018-07-04 07:51:39 -04:00
|
|
|
impl Signal for IsWindowLoaded {
|
2018-07-04 07:34:13 -04:00
|
|
|
type Item = bool;
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
|
|
|
let result = match *self {
|
2018-07-04 07:51:39 -04:00
|
|
|
IsWindowLoaded::Initial {} => {
|
2019-06-20 19:57:47 -04:00
|
|
|
let is_ready = bindings::ready_state() == "complete";
|
2018-07-04 07:34:13 -04:00
|
|
|
|
|
|
|
if is_ready {
|
2018-10-22 20:16:54 -04:00
|
|
|
Poll::Ready(Some(true))
|
2018-07-04 07:34:13 -04:00
|
|
|
|
|
|
|
} else {
|
|
|
|
let (sender, receiver) = oneshot::channel();
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
*self = IsWindowLoaded::Pending {
|
2018-07-04 07:34:13 -04:00
|
|
|
receiver,
|
2019-07-23 00:51:40 -04:00
|
|
|
_event: EventListener::once(bindings::window_event_target(), "load", move |_| {
|
2018-07-04 07:34:13 -04:00
|
|
|
// TODO test this
|
2019-06-11 09:29:46 -04:00
|
|
|
sender.send(Some(true)).unwrap_throw();
|
2018-07-04 07:34:13 -04:00
|
|
|
}),
|
|
|
|
};
|
|
|
|
|
2018-10-22 20:16:54 -04:00
|
|
|
Poll::Ready(Some(false))
|
2018-07-04 07:34:13 -04:00
|
|
|
}
|
|
|
|
},
|
2019-06-11 09:29:46 -04:00
|
|
|
IsWindowLoaded::Pending { ref mut receiver, .. } => {
|
|
|
|
receiver.poll_unpin(cx).map(|x| x.unwrap_throw())
|
2018-07-04 07:34:13 -04:00
|
|
|
},
|
2018-07-04 07:51:39 -04:00
|
|
|
IsWindowLoaded::Done {} => {
|
2018-10-22 20:16:54 -04:00
|
|
|
Poll::Ready(None)
|
2018-07-04 07:34:13 -04:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2018-10-22 20:16:54 -04:00
|
|
|
if let Poll::Ready(Some(true)) = result {
|
2019-06-11 09:29:46 -04:00
|
|
|
*self = IsWindowLoaded::Done {};
|
2018-07-04 07:34:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
// TODO this should be moved into gloo
|
2018-07-04 07:34:13 -04:00
|
|
|
#[inline]
|
2018-07-04 07:51:39 -04:00
|
|
|
pub fn is_window_loaded() -> impl Signal<Item = bool> {
|
|
|
|
IsWindowLoaded::Initial {}
|
2018-07-04 07:34:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-20 20:29:56 -04:00
|
|
|
// TODO should this intern ?
|
2018-02-25 06:58:20 -05:00
|
|
|
#[inline]
|
2018-04-25 19:01:52 -04:00
|
|
|
pub fn text(value: &str) -> Dom {
|
2019-07-23 00:51:40 -04:00
|
|
|
Dom::new(bindings::create_text_node(value).into())
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-01 21:08:34 -04:00
|
|
|
fn make_text_signal<A, B>(callbacks: &mut Callbacks, value: B) -> Text
|
2018-09-17 17:17:55 -04:00
|
|
|
where A: AsStr,
|
2018-10-22 20:16:54 -04:00
|
|
|
B: Signal<Item = A> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
|
2019-07-23 00:51:40 -04:00
|
|
|
let element = bindings::create_text_node(intern(""));
|
2018-04-25 19:01:52 -04:00
|
|
|
|
|
|
|
{
|
|
|
|
let element = element.clone();
|
|
|
|
|
2018-10-22 20:16:54 -04:00
|
|
|
callbacks.after_remove(for_each(value, move |value| {
|
2021-05-27 17:54:52 -04:00
|
|
|
value.with_str(|value| {
|
|
|
|
// TODO maybe this should intern ?
|
|
|
|
bindings::set_text(&element, value);
|
|
|
|
});
|
2018-04-25 19:01:52 -04:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2020-06-01 21:08:34 -04:00
|
|
|
element
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO should this inline ?
|
|
|
|
pub fn text_signal<A, B>(value: B) -> Dom
|
|
|
|
where A: AsStr,
|
|
|
|
B: Signal<Item = A> + 'static {
|
|
|
|
|
|
|
|
let mut callbacks = Callbacks::new();
|
|
|
|
|
|
|
|
let element = make_text_signal(&mut callbacks, value);
|
|
|
|
|
2018-04-25 19:01:52 -04:00
|
|
|
Dom {
|
|
|
|
element: element.into(),
|
|
|
|
callbacks: callbacks,
|
|
|
|
}
|
2018-02-21 01:37:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-15 05:23:38 -04:00
|
|
|
// TODO better warning message for must_use
|
|
|
|
#[must_use]
|
2018-02-21 03:08:36 -05:00
|
|
|
#[derive(Debug)]
|
2018-02-21 01:37:09 -05:00
|
|
|
pub struct Dom {
|
2018-02-25 06:58:20 -05:00
|
|
|
pub(crate) element: Node,
|
|
|
|
pub(crate) callbacks: Callbacks,
|
2018-02-21 01:37:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Dom {
|
|
|
|
#[inline]
|
2018-05-20 05:53:42 -04:00
|
|
|
pub fn new(element: Node) -> Self {
|
2018-02-21 01:37:09 -05:00
|
|
|
Self {
|
|
|
|
element,
|
|
|
|
callbacks: Callbacks::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn empty() -> Self {
|
2021-04-30 12:08:29 -04:00
|
|
|
Self::new(bindings::create_empty_node())
|
2018-02-21 01:37:09 -05:00
|
|
|
}
|
|
|
|
|
2021-04-29 19:13:37 -04:00
|
|
|
#[deprecated(since = "0.5.15", note = "Store the data explicitly in a component struct instead")]
|
2018-02-21 01:37:09 -05:00
|
|
|
#[inline]
|
|
|
|
pub fn with_state<A, F>(mut state: A, initializer: F) -> Dom
|
|
|
|
where A: 'static,
|
|
|
|
F: FnOnce(&mut A) -> Dom {
|
|
|
|
|
|
|
|
let mut dom = initializer(&mut state);
|
|
|
|
|
2018-04-25 19:01:52 -04:00
|
|
|
dom.callbacks.after_remove(ValueDiscard::new(state));
|
2018-02-21 01:37:09 -05:00
|
|
|
|
|
|
|
dom
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-15 19:59:22 -04:00
|
|
|
#[inline]
|
2019-08-04 22:07:50 -04:00
|
|
|
fn create_element<A>(name: &str) -> A where A: JsCast {
|
2021-05-27 18:17:42 -04:00
|
|
|
// TODO use unchecked_into in release mode ?
|
2019-07-23 00:51:40 -04:00
|
|
|
bindings::create_element(intern(name)).dyn_into().unwrap_throw()
|
2019-06-15 19:59:22 -04:00
|
|
|
}
|
|
|
|
|
2018-04-25 19:01:52 -04:00
|
|
|
#[inline]
|
2019-08-04 22:07:50 -04:00
|
|
|
fn create_element_ns<A>(name: &str, namespace: &str) -> A where A: JsCast {
|
2021-05-27 18:17:42 -04:00
|
|
|
// TODO use unchecked_into in release mode ?
|
2019-07-23 00:51:40 -04:00
|
|
|
bindings::create_element_ns(intern(namespace), intern(name)).dyn_into().unwrap_throw()
|
2018-02-21 01:37:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-27 01:36:48 -04:00
|
|
|
// TODO should this inline ?
|
2019-06-11 09:29:46 -04:00
|
|
|
fn set_option<A, B, C, D, F>(element: A, callbacks: &mut Callbacks, value: D, mut f: F)
|
|
|
|
where A: 'static,
|
2018-09-17 17:17:55 -04:00
|
|
|
C: OptionStr<Output = B>,
|
2018-10-22 20:16:54 -04:00
|
|
|
D: Signal<Item = C> + 'static,
|
2018-09-17 17:17:55 -04:00
|
|
|
F: FnMut(&A, Option<B>) + 'static {
|
2018-06-09 14:45:41 -04:00
|
|
|
|
|
|
|
let mut is_set = false;
|
|
|
|
|
2018-10-22 20:16:54 -04:00
|
|
|
callbacks.after_remove(for_each(value, move |value| {
|
2018-09-17 17:17:55 -04:00
|
|
|
let value = value.into_option();
|
2018-06-09 14:45:41 -04:00
|
|
|
|
|
|
|
if value.is_some() {
|
|
|
|
is_set = true;
|
|
|
|
|
|
|
|
} else if is_set {
|
|
|
|
is_set = false;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
f(&element, value);
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2020-04-27 01:36:48 -04:00
|
|
|
// TODO should this inline ?
|
2019-07-23 00:51:40 -04:00
|
|
|
fn set_style<A, B>(style: &CssStyleDeclaration, name: &A, value: B, important: bool)
|
2019-06-11 09:29:46 -04:00
|
|
|
where A: MultiStr,
|
|
|
|
B: MultiStr {
|
2018-09-17 17:17:55 -04:00
|
|
|
|
|
|
|
let mut names = vec![];
|
|
|
|
let mut values = vec![];
|
|
|
|
|
2019-08-04 21:39:40 -04:00
|
|
|
fn try_set_style(style: &CssStyleDeclaration, names: &mut Vec<String>, values: &mut Vec<String>, name: &str, value: &str, important: bool) -> Option<()> {
|
2019-07-23 00:51:40 -04:00
|
|
|
assert!(value != "");
|
2019-06-11 09:29:46 -04:00
|
|
|
|
2019-06-20 11:52:52 -04:00
|
|
|
// TODO handle browser prefixes ?
|
2019-07-23 00:51:40 -04:00
|
|
|
bindings::remove_style(style, name);
|
2019-06-11 09:29:46 -04:00
|
|
|
|
2019-07-23 00:51:40 -04:00
|
|
|
bindings::set_style(style, name, value, important);
|
2019-06-11 09:29:46 -04:00
|
|
|
|
2019-07-23 00:51:40 -04:00
|
|
|
let is_changed = bindings::get_style(style, name) != "";
|
2019-06-11 09:29:46 -04:00
|
|
|
|
2019-06-20 11:52:52 -04:00
|
|
|
if is_changed {
|
2019-08-04 21:39:40 -04:00
|
|
|
Some(())
|
2018-09-17 17:17:55 -04:00
|
|
|
|
2019-06-20 11:52:52 -04:00
|
|
|
} else {
|
2019-06-20 19:57:47 -04:00
|
|
|
names.push(String::from(name));
|
|
|
|
values.push(String::from(value));
|
2019-08-04 21:39:40 -04:00
|
|
|
None
|
2019-06-20 11:52:52 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-04 21:39:40 -04:00
|
|
|
let okay = name.find_map(|name| {
|
2019-07-23 00:51:40 -04:00
|
|
|
let name: &str = intern(name);
|
2019-06-20 19:57:47 -04:00
|
|
|
|
2019-08-04 21:39:40 -04:00
|
|
|
value.find_map(|value| {
|
2019-06-21 07:22:53 -04:00
|
|
|
// TODO should this intern ?
|
2019-07-23 00:51:40 -04:00
|
|
|
try_set_style(style, &mut names, &mut values, &name, &value, important)
|
2018-09-17 17:17:55 -04:00
|
|
|
})
|
|
|
|
});
|
|
|
|
|
2019-08-04 21:39:40 -04:00
|
|
|
if let None = okay {
|
2021-09-18 15:04:54 -04:00
|
|
|
if cfg!(debug_assertions) {
|
|
|
|
// TODO maybe make this configurable
|
|
|
|
panic!("style is incorrect:\n names: {}\n values: {}", names.join(", "), values.join(", "));
|
|
|
|
}
|
2018-09-17 17:17:55 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-27 01:36:48 -04:00
|
|
|
// TODO should this inline ?
|
2019-07-23 00:51:40 -04:00
|
|
|
fn set_style_signal<A, B, C, D>(style: CssStyleDeclaration, callbacks: &mut Callbacks, name: A, value: D, important: bool)
|
2019-06-11 09:29:46 -04:00
|
|
|
where A: MultiStr + 'static,
|
|
|
|
B: MultiStr,
|
|
|
|
C: OptionStr<Output = B>,
|
|
|
|
D: Signal<Item = C> + 'static {
|
2018-09-17 17:17:55 -04:00
|
|
|
|
2019-07-23 00:51:40 -04:00
|
|
|
set_option(style, callbacks, value, move |style, value| {
|
2018-09-17 17:17:55 -04:00
|
|
|
match value {
|
|
|
|
Some(value) => {
|
2019-06-20 20:29:56 -04:00
|
|
|
// TODO should this intern or not ?
|
2019-07-23 00:51:40 -04:00
|
|
|
set_style(style, &name, value, important);
|
2018-09-17 17:17:55 -04:00
|
|
|
},
|
|
|
|
None => {
|
|
|
|
name.each(|name| {
|
2019-06-11 09:29:46 -04:00
|
|
|
// TODO handle browser prefixes ?
|
2019-07-23 00:51:40 -04:00
|
|
|
bindings::remove_style(style, intern(name));
|
2018-09-17 17:17:55 -04:00
|
|
|
});
|
|
|
|
},
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-09-18 15:30:52 -04:00
|
|
|
// TODO should this inline ?
|
|
|
|
fn set_style_unchecked_signal<A, B, C, D>(style: CssStyleDeclaration, callbacks: &mut Callbacks, name: A, value: D, important: bool)
|
|
|
|
where A: AsStr + 'static,
|
|
|
|
B: AsStr,
|
|
|
|
C: OptionStr<Output = B>,
|
|
|
|
D: Signal<Item = C> + 'static {
|
|
|
|
|
|
|
|
set_option(style, callbacks, value, move |style, value| {
|
|
|
|
match value {
|
|
|
|
Some(value) => {
|
|
|
|
name.with_str(|name| {
|
|
|
|
let name: &str = intern(name);
|
|
|
|
|
|
|
|
value.with_str(|value| {
|
|
|
|
bindings::set_style(style, name, value, important);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
name.with_str(|name| {
|
|
|
|
bindings::remove_style(style, intern(name));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
// TODO check that the property *actually* was changed ?
|
2019-06-20 19:57:47 -04:00
|
|
|
// TODO maybe use AsRef<Object> ?
|
2020-04-27 01:36:48 -04:00
|
|
|
// TODO should this inline ?
|
2019-06-11 09:29:46 -04:00
|
|
|
fn set_property<A, B, C>(element: &A, name: &B, value: C) where A: AsRef<JsValue>, B: MultiStr, C: Into<JsValue> {
|
|
|
|
let element = element.as_ref();
|
|
|
|
let value = value.into();
|
|
|
|
|
|
|
|
name.each(|name| {
|
2019-07-23 00:51:40 -04:00
|
|
|
bindings::set_property(element, intern(name), &value);
|
2019-06-11 09:29:46 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-06-09 14:45:41 -04:00
|
|
|
|
2021-10-17 04:31:02 -04:00
|
|
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
|
|
|
pub struct EventOptions {
|
|
|
|
pub bubbles: bool,
|
|
|
|
pub preventable: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl EventOptions {
|
|
|
|
pub fn bubbles() -> Self {
|
|
|
|
Self {
|
|
|
|
bubbles: true,
|
|
|
|
preventable: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn preventable() -> Self {
|
|
|
|
Self {
|
|
|
|
bubbles: false,
|
|
|
|
preventable: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for EventOptions {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
bubbles: false,
|
|
|
|
preventable: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-15 05:23:38 -04:00
|
|
|
// TODO better warning message for must_use
|
|
|
|
#[must_use]
|
2018-04-25 19:01:52 -04:00
|
|
|
pub struct DomBuilder<A> {
|
|
|
|
element: A,
|
|
|
|
callbacks: Callbacks,
|
2018-03-15 06:54:18 -04:00
|
|
|
}
|
|
|
|
|
2019-07-31 14:52:04 -04:00
|
|
|
impl<A> DomBuilder<A> where A: JsCast {
|
|
|
|
#[inline]
|
|
|
|
pub fn new_html(name: &str) -> Self {
|
|
|
|
Self::new(create_element(name))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn new_svg(name: &str) -> Self {
|
|
|
|
Self::new(create_element_ns(name, SVG_NAMESPACE))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-25 19:01:52 -04:00
|
|
|
impl<A> DomBuilder<A> {
|
2020-05-04 17:28:09 -04:00
|
|
|
#[inline]
|
|
|
|
#[doc(hidden)]
|
|
|
|
pub fn __internal_transfer_callbacks<B>(mut self, mut shadow: DomBuilder<B>) -> Self {
|
|
|
|
self.callbacks.after_insert.append(&mut shadow.callbacks.after_insert);
|
|
|
|
self.callbacks.after_remove.append(&mut shadow.callbacks.after_remove);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
#[inline]
|
2018-04-25 19:01:52 -04:00
|
|
|
pub fn new(value: A) -> Self {
|
|
|
|
Self {
|
|
|
|
element: value,
|
|
|
|
callbacks: Callbacks::new(),
|
2018-03-15 06:54:18 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
#[inline]
|
2021-10-17 04:31:02 -04:00
|
|
|
fn _event<T, F>(&mut self, element: EventTarget, options: &EventOptions, listener: F)
|
2019-06-11 09:29:46 -04:00
|
|
|
where T: StaticEvent,
|
2018-02-21 01:37:09 -05:00
|
|
|
F: FnMut(T) + 'static {
|
2021-10-17 04:31:02 -04:00
|
|
|
self.callbacks.after_remove(on(element, options, listener));
|
2019-06-11 09:29:46 -04:00
|
|
|
}
|
2018-02-21 01:37:09 -05:00
|
|
|
|
2021-10-17 04:31:02 -04:00
|
|
|
// TODO add this to the StylesheetBuilder and ClassBuilder too
|
2019-06-11 09:29:46 -04:00
|
|
|
#[inline]
|
2021-10-17 04:31:02 -04:00
|
|
|
pub fn global_event_with_options<T, F>(mut self, options: &EventOptions, listener: F) -> Self
|
2019-06-11 09:29:46 -04:00
|
|
|
where T: StaticEvent,
|
|
|
|
F: FnMut(T) + 'static {
|
2021-10-17 04:31:02 -04:00
|
|
|
self._event(bindings::window_event_target(), options, listener);
|
|
|
|
self
|
2018-02-21 01:37:09 -05:00
|
|
|
}
|
|
|
|
|
2018-04-25 19:01:52 -04:00
|
|
|
// TODO add this to the StylesheetBuilder and ClassBuilder too
|
|
|
|
#[inline]
|
2021-10-17 04:49:43 -04:00
|
|
|
pub fn global_event<T, F>(self, listener: F) -> Self
|
2019-06-11 09:29:46 -04:00
|
|
|
where T: StaticEvent,
|
2018-04-25 19:01:52 -04:00
|
|
|
F: FnMut(T) + 'static {
|
2021-10-17 04:31:02 -04:00
|
|
|
self.global_event_with_options(&EventOptions::default(), listener)
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
|
|
|
|
2021-10-17 04:31:02 -04:00
|
|
|
#[deprecated(since = "0.5.21", note = "Use global_event_with_options instead")]
|
2020-01-19 22:40:39 -05:00
|
|
|
#[inline]
|
2021-10-17 04:49:43 -04:00
|
|
|
pub fn global_event_preventable<T, F>(self, listener: F) -> Self
|
2020-01-19 22:40:39 -05:00
|
|
|
where T: StaticEvent,
|
|
|
|
F: FnMut(T) + 'static {
|
2021-10-17 04:31:02 -04:00
|
|
|
self.global_event_with_options(&EventOptions::preventable(), listener)
|
2020-01-19 22:40:39 -05:00
|
|
|
}
|
|
|
|
|
2018-04-25 19:01:52 -04:00
|
|
|
#[inline]
|
2018-10-22 20:16:54 -04:00
|
|
|
pub fn future<F>(mut self, future: F) -> Self where F: Future<Output = ()> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
self.callbacks.after_remove(DiscardOnDrop::leak(spawn_future(future)));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-10-30 05:06:22 -04:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn apply<F>(self, f: F) -> Self where F: FnOnce(Self) -> Self {
|
2021-07-30 16:27:19 -04:00
|
|
|
f(self)
|
2018-10-30 05:06:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn apply_if<F>(self, test: bool, f: F) -> Self where F: FnOnce(Self) -> Self {
|
|
|
|
if test {
|
|
|
|
f(self)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
impl<A> DomBuilder<A> where A: Clone {
|
2019-07-30 17:26:58 -04:00
|
|
|
#[inline]
|
|
|
|
#[doc(hidden)]
|
|
|
|
pub fn __internal_element(&self) -> A {
|
|
|
|
self.element.clone()
|
|
|
|
}
|
|
|
|
|
2019-07-31 14:52:04 -04:00
|
|
|
#[deprecated(since = "0.5.1", note = "Use the with_node macro instead")]
|
2018-06-12 03:33:10 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn with_element<B, F>(self, f: F) -> B where F: FnOnce(Self, A) -> B {
|
|
|
|
let element = self.element.clone();
|
|
|
|
f(self, element)
|
|
|
|
}
|
|
|
|
|
2021-07-30 16:27:19 -04:00
|
|
|
#[deprecated(since = "0.5.20", note = "Use the with_node macro instead")]
|
2018-05-20 05:53:42 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn before_inserted<F>(self, f: F) -> Self where F: FnOnce(A) {
|
|
|
|
let element = self.element.clone();
|
|
|
|
f(element);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
impl<A> DomBuilder<A> where A: Clone + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn after_inserted<F>(mut self, f: F) -> Self where F: FnOnce(A) + 'static {
|
|
|
|
let element = self.element.clone();
|
|
|
|
self.callbacks.after_insert(move |_| f(element));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn after_removed<F>(mut self, f: F) -> Self where F: FnOnce(A) + 'static {
|
|
|
|
let element = self.element.clone();
|
|
|
|
self.callbacks.after_remove(FnDiscard::new(move || f(element)));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
impl<A> DomBuilder<A> where A: Into<Node> {
|
2018-04-25 19:01:52 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn into_dom(self) -> Dom {
|
|
|
|
Dom {
|
|
|
|
element: self.element.into(),
|
|
|
|
callbacks: self.callbacks,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
impl<A> DomBuilder<A> where A: AsRef<JsValue> {
|
2021-05-27 17:54:52 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn prop<B, C>(self, name: B, value: C) -> Self where B: MultiStr, C: Into<JsValue> {
|
2022-04-10 17:41:16 -04:00
|
|
|
set_property(&self.element, &name, value);
|
|
|
|
self
|
2021-05-27 17:54:52 -04:00
|
|
|
}
|
|
|
|
|
2022-04-10 17:41:16 -04:00
|
|
|
#[deprecated(since = "0.5.24", note = "Use the `prop` method instead")]
|
2018-04-25 19:01:52 -04:00
|
|
|
#[inline]
|
2019-06-11 09:29:46 -04:00
|
|
|
pub fn property<B, C>(self, name: B, value: C) -> Self where B: MultiStr, C: Into<JsValue> {
|
2022-04-10 17:41:16 -04:00
|
|
|
self.prop(name, value)
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
impl<A> DomBuilder<A> where A: AsRef<JsValue> {
|
2020-04-27 01:36:48 -04:00
|
|
|
// TODO should this inline ?
|
2018-09-17 17:17:55 -04:00
|
|
|
fn set_property_signal<B, C, D>(&mut self, name: B, value: D)
|
|
|
|
where B: MultiStr + 'static,
|
2019-06-11 09:29:46 -04:00
|
|
|
C: Into<JsValue>,
|
2018-10-22 20:16:54 -04:00
|
|
|
D: Signal<Item = C> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
let element = self.element.as_ref().clone();
|
2018-04-25 19:01:52 -04:00
|
|
|
|
2018-10-22 20:16:54 -04:00
|
|
|
self.callbacks.after_remove(for_each(value, move |value| {
|
2019-06-11 09:29:46 -04:00
|
|
|
set_property(&element, &name, value);
|
2018-04-25 19:01:52 -04:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2021-05-27 17:54:52 -04:00
|
|
|
#[inline]
|
2022-04-10 17:41:16 -04:00
|
|
|
pub fn prop_signal<B, C, D>(mut self, name: B, value: D) -> Self
|
2021-05-27 17:54:52 -04:00
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: Into<JsValue>,
|
|
|
|
D: Signal<Item = C> + 'static {
|
2022-04-10 17:41:16 -04:00
|
|
|
|
|
|
|
self.set_property_signal(name, value);
|
|
|
|
self
|
2021-05-27 17:54:52 -04:00
|
|
|
}
|
|
|
|
|
2022-04-10 17:41:16 -04:00
|
|
|
#[deprecated(since = "0.5.24", note = "Use the `prop_signal` method instead")]
|
2018-04-25 19:01:52 -04:00
|
|
|
#[inline]
|
2022-04-10 17:41:16 -04:00
|
|
|
pub fn property_signal<B, C, D>(self, name: B, value: D) -> Self
|
2018-09-17 17:17:55 -04:00
|
|
|
where B: MultiStr + 'static,
|
2019-06-11 09:29:46 -04:00
|
|
|
C: Into<JsValue>,
|
2018-10-22 20:16:54 -04:00
|
|
|
D: Signal<Item = C> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
|
2022-04-10 17:41:16 -04:00
|
|
|
self.prop_signal(name, value)
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
impl<A> DomBuilder<A> where A: AsRef<EventTarget> {
|
2018-02-21 01:37:09 -05:00
|
|
|
#[inline]
|
2021-10-17 04:31:02 -04:00
|
|
|
pub fn event_with_options<T, F>(mut self, options: &EventOptions, listener: F) -> Self
|
2019-06-11 09:29:46 -04:00
|
|
|
where T: StaticEvent,
|
2018-02-21 01:37:09 -05:00
|
|
|
F: FnMut(T) + 'static {
|
2019-06-11 09:29:46 -04:00
|
|
|
// TODO can this clone be avoided ?
|
2021-10-17 04:31:02 -04:00
|
|
|
self._event(self.element.as_ref().clone(), options, listener);
|
2019-06-11 09:29:46 -04:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2021-10-17 04:31:02 -04:00
|
|
|
pub fn event<T, F>(self, listener: F) -> Self
|
2019-06-11 09:29:46 -04:00
|
|
|
where T: StaticEvent,
|
|
|
|
F: FnMut(T) + 'static {
|
2021-10-17 04:31:02 -04:00
|
|
|
self.event_with_options(&EventOptions::default(), listener)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[deprecated(since = "0.5.21", note = "Use event_with_options instead")]
|
|
|
|
#[inline]
|
|
|
|
pub fn event_preventable<T, F>(self, listener: F) -> Self
|
|
|
|
where T: StaticEvent,
|
|
|
|
F: FnMut(T) + 'static {
|
|
|
|
self.event_with_options(&EventOptions::preventable(), listener)
|
2018-02-21 01:37:09 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
impl<A> DomBuilder<A> where A: AsRef<Node> {
|
2018-10-25 05:39:55 -04:00
|
|
|
#[inline]
|
2020-10-26 08:18:36 -04:00
|
|
|
pub fn text(self, value: &str) -> Self {
|
2019-06-20 20:29:56 -04:00
|
|
|
// TODO should this intern ?
|
2020-06-01 21:08:34 -04:00
|
|
|
bindings::append_child(self.element.as_ref(), &bindings::create_text_node(value));
|
2019-06-15 18:10:19 -04:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-10-25 05:39:55 -04:00
|
|
|
#[inline]
|
2019-06-15 18:12:31 -04:00
|
|
|
pub fn text_signal<B, C>(mut self, value: C) -> Self
|
2018-10-25 05:39:55 -04:00
|
|
|
where B: AsStr,
|
|
|
|
C: Signal<Item = B> + 'static {
|
2019-06-15 18:10:19 -04:00
|
|
|
|
2020-06-01 21:08:34 -04:00
|
|
|
let element = make_text_signal(&mut self.callbacks, value);
|
|
|
|
bindings::append_child(self.element.as_ref(), &element);
|
2019-06-15 18:10:19 -04:00
|
|
|
self
|
2018-10-25 05:39:55 -04:00
|
|
|
}
|
2018-02-21 01:37:09 -05:00
|
|
|
|
|
|
|
#[inline]
|
2021-05-27 17:54:52 -04:00
|
|
|
pub fn child<B: BorrowMut<Dom>>(mut self, mut child: B) -> Self {
|
|
|
|
operations::insert_children_one(self.element.as_ref(), &mut self.callbacks, child.borrow_mut());
|
2020-01-08 03:52:00 -05:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn child_signal<B>(mut self, child: B) -> Self
|
|
|
|
where B: Signal<Item = Option<Dom>> + 'static {
|
|
|
|
|
|
|
|
operations::insert_child_signal(self.element.as_ref().clone(), &mut self.callbacks, child);
|
2018-02-21 01:37:09 -05:00
|
|
|
self
|
|
|
|
}
|
2021-05-27 17:54:52 -04:00
|
|
|
|
|
|
|
// TODO figure out how to make this owned rather than &mut
|
|
|
|
#[inline]
|
|
|
|
pub fn children<B: BorrowMut<Dom>, C: IntoIterator<Item = B>>(mut self, children: C) -> Self {
|
|
|
|
operations::insert_children_iter(self.element.as_ref(), &mut self.callbacks, children);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn children_signal_vec<B>(mut self, children: B) -> Self
|
|
|
|
where B: SignalVec<Item = Dom> + 'static {
|
|
|
|
|
|
|
|
operations::insert_children_signal_vec(self.element.as_ref().clone(), &mut self.callbacks, children);
|
|
|
|
self
|
|
|
|
}
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
2018-02-21 01:37:09 -05:00
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
impl<A> DomBuilder<A> where A: AsRef<Element> {
|
2020-05-04 17:28:09 -04:00
|
|
|
#[inline]
|
|
|
|
#[doc(hidden)]
|
|
|
|
pub fn __internal_shadow_root(&self, mode: ShadowRootMode) -> DomBuilder<ShadowRoot> {
|
|
|
|
let shadow = self.element.as_ref().attach_shadow(&ShadowRootInit::new(mode)).unwrap_throw();
|
|
|
|
DomBuilder::new(shadow)
|
|
|
|
}
|
|
|
|
|
2021-05-27 17:54:52 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn attr<B>(self, name: B, value: &str) -> Self where B: MultiStr {
|
2019-06-20 19:57:47 -04:00
|
|
|
let element = self.element.as_ref();
|
2019-07-23 00:51:40 -04:00
|
|
|
// TODO should this intern the value ?
|
|
|
|
let value: &str = intern(value);
|
2019-06-20 19:57:47 -04:00
|
|
|
|
2018-09-17 17:17:55 -04:00
|
|
|
name.each(|name| {
|
2019-07-23 00:51:40 -04:00
|
|
|
bindings::set_attribute(element, intern(name), &value);
|
2018-09-17 17:17:55 -04:00
|
|
|
});
|
2019-06-20 19:57:47 -04:00
|
|
|
|
2018-02-21 01:37:09 -05:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2022-04-10 17:40:51 -04:00
|
|
|
#[deprecated(since = "0.5.24", note = "Use the `attr` method instead")]
|
2021-05-27 17:54:52 -04:00
|
|
|
#[inline]
|
2022-04-10 17:40:51 -04:00
|
|
|
pub fn attribute<B>(self, name: B, value: &str) -> Self where B: MultiStr {
|
|
|
|
self.attr(name, value)
|
2021-05-27 17:54:52 -04:00
|
|
|
}
|
|
|
|
|
2018-02-21 01:37:09 -05:00
|
|
|
#[inline]
|
2022-04-10 17:40:51 -04:00
|
|
|
pub fn attr_ns<B>(self, namespace: &str, name: B, value: &str) -> Self where B: MultiStr {
|
2019-06-20 19:57:47 -04:00
|
|
|
let element = self.element.as_ref();
|
2019-07-23 00:51:40 -04:00
|
|
|
let namespace: &str = intern(namespace);
|
|
|
|
// TODO should this intern the value ?
|
|
|
|
let value: &str = intern(value);
|
2019-06-20 19:57:47 -04:00
|
|
|
|
2018-09-17 17:17:55 -04:00
|
|
|
name.each(|name| {
|
2019-07-23 00:51:40 -04:00
|
|
|
bindings::set_attribute_ns(element, &namespace, intern(name), &value);
|
2018-09-17 17:17:55 -04:00
|
|
|
});
|
2019-06-20 19:57:47 -04:00
|
|
|
|
2018-04-25 19:01:52 -04:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2022-04-10 17:40:51 -04:00
|
|
|
#[deprecated(since = "0.5.24", note = "Use the `attr_ns` method instead")]
|
|
|
|
#[inline]
|
|
|
|
pub fn attribute_namespace<B>(self, namespace: &str, name: B, value: &str) -> Self where B: MultiStr {
|
|
|
|
self.attr_ns(namespace, name, value)
|
|
|
|
}
|
|
|
|
|
2018-04-25 19:01:52 -04:00
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn class<B>(self, name: B) -> Self where B: MultiStr {
|
2019-07-23 01:25:27 -04:00
|
|
|
let classes = self.element.as_ref().class_list();
|
2019-06-15 20:13:53 -04:00
|
|
|
|
2018-09-17 17:17:55 -04:00
|
|
|
name.each(|name| {
|
2019-07-23 01:25:27 -04:00
|
|
|
bindings::add_class(&classes, intern(name));
|
2018-09-17 17:17:55 -04:00
|
|
|
});
|
2019-06-15 20:13:53 -04:00
|
|
|
|
2018-02-21 01:37:09 -05:00
|
|
|
self
|
|
|
|
}
|
2019-06-11 09:29:46 -04:00
|
|
|
|
|
|
|
// TODO make this more efficient ?
|
|
|
|
#[inline]
|
|
|
|
pub fn visible(self, value: bool) -> Self {
|
|
|
|
if value {
|
|
|
|
// TODO remove the class somehow ?
|
|
|
|
self
|
|
|
|
|
|
|
|
} else {
|
|
|
|
self.class(&*HIDDEN_CLASS)
|
|
|
|
}
|
|
|
|
}
|
2018-02-21 01:37:09 -05:00
|
|
|
}
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
impl<A> DomBuilder<A> where A: AsRef<Element> {
|
2020-04-27 01:36:48 -04:00
|
|
|
// TODO should this inline ?
|
2018-09-17 17:17:55 -04:00
|
|
|
fn set_attribute_signal<B, C, D, E>(&mut self, name: B, value: E)
|
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: AsStr,
|
|
|
|
D: OptionStr<Output = C>,
|
2018-10-22 20:16:54 -04:00
|
|
|
E: Signal<Item = D> + 'static {
|
2018-09-17 17:17:55 -04:00
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
set_option(self.element.as_ref().clone(), &mut self.callbacks, value, move |element, value| {
|
2018-06-09 14:45:41 -04:00
|
|
|
match value {
|
2018-09-17 17:17:55 -04:00
|
|
|
Some(value) => {
|
2021-05-27 17:54:52 -04:00
|
|
|
value.with_str(|value| {
|
|
|
|
name.each(|name| {
|
|
|
|
// TODO should this intern the value ?
|
|
|
|
bindings::set_attribute(element, intern(name), &value);
|
|
|
|
});
|
2018-09-17 17:17:55 -04:00
|
|
|
});
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
name.each(|name| {
|
2019-07-23 00:51:40 -04:00
|
|
|
bindings::remove_attribute(element, intern(name));
|
2018-09-17 17:17:55 -04:00
|
|
|
});
|
|
|
|
},
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
2018-06-09 14:45:41 -04:00
|
|
|
});
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
|
|
|
|
2021-05-27 17:54:52 -04:00
|
|
|
#[inline]
|
2022-04-10 17:40:51 -04:00
|
|
|
pub fn attr_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
|
2021-05-27 17:54:52 -04:00
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: AsStr,
|
|
|
|
D: OptionStr<Output = C>,
|
|
|
|
E: Signal<Item = D> + 'static {
|
2022-04-10 17:40:51 -04:00
|
|
|
|
|
|
|
self.set_attribute_signal(name, value);
|
|
|
|
self
|
2021-05-27 17:54:52 -04:00
|
|
|
}
|
|
|
|
|
2022-04-10 17:40:51 -04:00
|
|
|
#[deprecated(since = "0.5.24", note = "Use the `attr_signal` method instead")]
|
2018-04-25 19:01:52 -04:00
|
|
|
#[inline]
|
2022-04-10 17:40:51 -04:00
|
|
|
pub fn attribute_signal<B, C, D, E>(self, name: B, value: E) -> Self
|
2018-09-17 17:17:55 -04:00
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: AsStr,
|
|
|
|
D: OptionStr<Output = C>,
|
2018-10-22 20:16:54 -04:00
|
|
|
E: Signal<Item = D> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
|
2022-04-10 17:40:51 -04:00
|
|
|
self.attr_signal(name, value)
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
|
|
|
|
2020-04-27 01:36:48 -04:00
|
|
|
|
|
|
|
// TODO should this inline ?
|
2018-09-17 17:17:55 -04:00
|
|
|
fn set_attribute_namespace_signal<B, C, D, E>(&mut self, namespace: &str, name: B, value: E)
|
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: AsStr,
|
|
|
|
D: OptionStr<Output = C>,
|
2018-10-22 20:16:54 -04:00
|
|
|
E: Signal<Item = D> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
|
2019-07-23 00:51:40 -04:00
|
|
|
// TODO avoid this to_owned by using Into<Cow<'static str>>
|
|
|
|
let namespace: String = intern(namespace).to_owned();
|
2018-04-25 19:01:52 -04:00
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
set_option(self.element.as_ref().clone(), &mut self.callbacks, value, move |element, value| {
|
2018-06-09 14:45:41 -04:00
|
|
|
match value {
|
2018-09-17 17:17:55 -04:00
|
|
|
Some(value) => {
|
2021-05-27 17:54:52 -04:00
|
|
|
value.with_str(|value| {
|
|
|
|
name.each(|name| {
|
|
|
|
// TODO should this intern the value ?
|
|
|
|
bindings::set_attribute_ns(element, &namespace, intern(name), &value);
|
|
|
|
});
|
2018-09-17 17:17:55 -04:00
|
|
|
});
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
name.each(|name| {
|
2019-07-23 00:51:40 -04:00
|
|
|
bindings::remove_attribute_ns(element, &namespace, intern(name));
|
2018-09-17 17:17:55 -04:00
|
|
|
});
|
|
|
|
},
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
2018-06-09 14:45:41 -04:00
|
|
|
});
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
|
|
|
|
2021-05-27 17:54:52 -04:00
|
|
|
#[inline]
|
2022-04-10 17:40:51 -04:00
|
|
|
pub fn attr_ns_signal<B, C, D, E>(mut self, namespace: &str, name: B, value: E) -> Self
|
2021-05-27 17:54:52 -04:00
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: AsStr,
|
|
|
|
D: OptionStr<Output = C>,
|
|
|
|
E: Signal<Item = D> + 'static {
|
2022-04-10 17:40:51 -04:00
|
|
|
|
|
|
|
self.set_attribute_namespace_signal(namespace, name, value);
|
|
|
|
self
|
2021-05-27 17:54:52 -04:00
|
|
|
}
|
|
|
|
|
2022-04-10 17:40:51 -04:00
|
|
|
#[deprecated(since = "0.5.24", note = "Use the `attr_ns_signal` method instead")]
|
2018-04-25 19:01:52 -04:00
|
|
|
#[inline]
|
2022-04-10 17:40:51 -04:00
|
|
|
pub fn attribute_namespace_signal<B, C, D, E>(self, namespace: &str, name: B, value: E) -> Self
|
2018-09-17 17:17:55 -04:00
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: AsStr,
|
|
|
|
D: OptionStr<Output = C>,
|
2018-10-22 20:16:54 -04:00
|
|
|
E: Signal<Item = D> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
|
2022-04-10 17:40:51 -04:00
|
|
|
self.attr_ns_signal(namespace, name, value)
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-27 01:36:48 -04:00
|
|
|
// TODO should this inline ?
|
2018-09-17 17:17:55 -04:00
|
|
|
fn set_class_signal<B, C>(&mut self, name: B, value: C)
|
|
|
|
where B: MultiStr + 'static,
|
2018-10-22 20:16:54 -04:00
|
|
|
C: Signal<Item = bool> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
|
2019-07-23 01:25:27 -04:00
|
|
|
let element = self.element.as_ref().class_list();
|
2018-04-25 19:01:52 -04:00
|
|
|
|
2018-06-09 14:45:41 -04:00
|
|
|
let mut is_set = false;
|
|
|
|
|
2018-10-22 20:16:54 -04:00
|
|
|
self.callbacks.after_remove(for_each(value, move |value| {
|
2018-04-25 19:01:52 -04:00
|
|
|
if value {
|
2018-06-09 14:45:41 -04:00
|
|
|
if !is_set {
|
|
|
|
is_set = true;
|
2018-09-17 17:17:55 -04:00
|
|
|
|
|
|
|
name.each(|name| {
|
2019-07-23 00:51:40 -04:00
|
|
|
bindings::add_class(&element, intern(name));
|
2018-09-17 17:17:55 -04:00
|
|
|
});
|
2018-06-09 14:45:41 -04:00
|
|
|
}
|
2018-04-25 19:01:52 -04:00
|
|
|
|
|
|
|
} else {
|
2018-06-09 14:45:41 -04:00
|
|
|
if is_set {
|
|
|
|
is_set = false;
|
2018-09-17 17:17:55 -04:00
|
|
|
|
|
|
|
name.each(|name| {
|
2019-07-23 00:51:40 -04:00
|
|
|
bindings::remove_class(&element, intern(name));
|
2018-09-17 17:17:55 -04:00
|
|
|
});
|
2018-06-09 14:45:41 -04:00
|
|
|
}
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2018-02-21 01:37:09 -05:00
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn class_signal<B, C>(mut self, name: B, value: C) -> Self
|
|
|
|
where B: MultiStr + 'static,
|
2018-10-22 20:16:54 -04:00
|
|
|
C: Signal<Item = bool> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
|
|
|
|
self.set_class_signal(name, value);
|
2018-02-21 01:37:09 -05:00
|
|
|
self
|
|
|
|
}
|
2018-07-03 23:44:50 -04:00
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
// TODO make this more efficient ?
|
|
|
|
#[inline]
|
|
|
|
pub fn visible_signal<B>(self, value: B) -> Self where B: Signal<Item = bool> + 'static {
|
|
|
|
self.class_signal(&*HIDDEN_CLASS, not(value))
|
|
|
|
}
|
|
|
|
|
2018-07-04 05:56:07 -04:00
|
|
|
|
2018-09-17 17:17:55 -04:00
|
|
|
// TODO use OptionStr ?
|
2020-04-27 01:36:48 -04:00
|
|
|
// TODO should this inline ?
|
2018-07-03 23:58:43 -04:00
|
|
|
fn set_scroll_signal<B, F>(&mut self, signal: B, mut f: F)
|
2019-06-11 09:29:46 -04:00
|
|
|
where B: Signal<Item = Option<i32>> + 'static,
|
|
|
|
F: FnMut(&Element, i32) + 'static {
|
2018-07-03 23:58:43 -04:00
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
let element: Element = self.element.as_ref().clone();
|
2018-07-03 23:44:50 -04:00
|
|
|
|
2018-07-03 23:58:43 -04:00
|
|
|
// This needs to use `after_insert` because scrolling an element before it is in the DOM has no effect
|
|
|
|
self.callbacks.after_insert(move |callbacks| {
|
|
|
|
callbacks.after_remove(for_each(signal, move |value| {
|
|
|
|
if let Some(value) = value {
|
|
|
|
f(&element, value);
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
}
|
2018-07-03 23:44:50 -04:00
|
|
|
|
2021-05-27 17:54:52 -04:00
|
|
|
// TODO rename to scroll_x_signal ?
|
2018-07-03 23:58:43 -04:00
|
|
|
#[inline]
|
2019-06-11 09:29:46 -04:00
|
|
|
pub fn scroll_left_signal<B>(mut self, signal: B) -> Self where B: Signal<Item = Option<i32>> + 'static {
|
2019-06-20 19:57:47 -04:00
|
|
|
// TODO bindings function for this ?
|
2019-06-11 09:29:46 -04:00
|
|
|
self.set_scroll_signal(signal, Element::set_scroll_left);
|
2018-07-03 23:44:50 -04:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-05-27 17:54:52 -04:00
|
|
|
// TODO rename to scroll_y_signal ?
|
2018-07-03 23:44:50 -04:00
|
|
|
#[inline]
|
2019-06-11 09:29:46 -04:00
|
|
|
pub fn scroll_top_signal<B>(mut self, signal: B) -> Self where B: Signal<Item = Option<i32>> + 'static {
|
2019-06-20 19:57:47 -04:00
|
|
|
// TODO bindings function for this ?
|
2019-06-11 09:29:46 -04:00
|
|
|
self.set_scroll_signal(signal, Element::set_scroll_top);
|
2018-07-03 23:44:50 -04:00
|
|
|
self
|
|
|
|
}
|
2018-04-25 19:01:52 -04:00
|
|
|
}
|
2018-02-21 01:37:09 -05:00
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
|
2018-02-21 01:37:09 -05:00
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn style<B, C>(self, name: B, value: C) -> Self
|
|
|
|
where B: MultiStr,
|
|
|
|
C: MultiStr {
|
2019-07-23 00:51:40 -04:00
|
|
|
set_style(&self.element.as_ref().style(), &name, value, false);
|
2018-02-21 01:37:09 -05:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn style_important<B, C>(self, name: B, value: C) -> Self
|
|
|
|
where B: MultiStr,
|
|
|
|
C: MultiStr {
|
2019-07-23 00:51:40 -04:00
|
|
|
set_style(&self.element.as_ref().style(), &name, value, true);
|
2018-02-21 01:37:09 -05:00
|
|
|
self
|
|
|
|
}
|
2021-09-18 15:30:52 -04:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn style_unchecked<B, C>(self, name: B, value: C) -> Self
|
|
|
|
where B: AsStr,
|
|
|
|
C: AsStr {
|
|
|
|
name.with_str(|name| {
|
|
|
|
value.with_str(|value| {
|
|
|
|
bindings::set_style(&self.element.as_ref().style(), intern(name), value, false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
self
|
|
|
|
}
|
2018-02-21 01:37:09 -05:00
|
|
|
}
|
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
|
2018-02-21 01:37:09 -05:00
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn style_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
|
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: MultiStr,
|
|
|
|
D: OptionStr<Output = C>,
|
2018-10-22 20:16:54 -04:00
|
|
|
E: Signal<Item = D> + 'static {
|
2018-09-17 17:17:55 -04:00
|
|
|
|
2019-07-23 00:51:40 -04:00
|
|
|
set_style_signal(self.element.as_ref().style(), &mut self.callbacks, name, value, false);
|
2018-04-25 19:01:52 -04:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn style_important_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
|
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: MultiStr,
|
|
|
|
D: OptionStr<Output = C>,
|
2018-10-22 20:16:54 -04:00
|
|
|
E: Signal<Item = D> + 'static {
|
2018-09-17 17:17:55 -04:00
|
|
|
|
2019-07-23 00:51:40 -04:00
|
|
|
set_style_signal(self.element.as_ref().style(), &mut self.callbacks, name, value, true);
|
2018-04-25 19:01:52 -04:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-09-18 15:30:52 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn style_unchecked_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
|
|
|
|
where B: AsStr + 'static,
|
|
|
|
C: AsStr,
|
|
|
|
D: OptionStr<Output = C>,
|
|
|
|
E: Signal<Item = D> + 'static {
|
|
|
|
|
|
|
|
set_style_unchecked_signal(self.element.as_ref().style(), &mut self.callbacks, name, value, false);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-04-25 19:01:52 -04:00
|
|
|
|
|
|
|
// TODO remove the `value` argument ?
|
|
|
|
#[inline]
|
|
|
|
pub fn focused(mut self, value: bool) -> Self {
|
2019-06-11 09:29:46 -04:00
|
|
|
let element = self.element.as_ref().clone();
|
2018-04-25 19:01:52 -04:00
|
|
|
|
|
|
|
// 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 |_| {
|
2018-06-09 14:45:41 -04:00
|
|
|
// TODO avoid updating if the focused state hasn't changed ?
|
2019-06-20 19:57:47 -04:00
|
|
|
if value {
|
|
|
|
bindings::focus(&element);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
bindings::blur(&element);
|
|
|
|
}
|
2018-04-25 19:01:52 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-27 01:36:48 -04:00
|
|
|
// TODO should this inline ?
|
2018-04-25 19:01:52 -04:00
|
|
|
fn set_focused_signal<B>(&mut self, value: B)
|
2018-10-22 20:16:54 -04:00
|
|
|
where B: Signal<Item = bool> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
let element = self.element.as_ref().clone();
|
2018-04-25 19:01:52 -04:00
|
|
|
|
|
|
|
// 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 |callbacks| {
|
|
|
|
// TODO verify that this is correct under all circumstances
|
2018-07-03 23:58:43 -04:00
|
|
|
callbacks.after_remove(for_each(value, move |value| {
|
2018-06-09 14:45:41 -04:00
|
|
|
// TODO avoid updating if the focused state hasn't changed ?
|
2019-06-20 19:57:47 -04:00
|
|
|
if value {
|
|
|
|
bindings::focus(&element);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
bindings::blur(&element);
|
|
|
|
}
|
2018-04-25 19:01:52 -04:00
|
|
|
}));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn focused_signal<B>(mut self, value: B) -> Self
|
2018-10-22 20:16:54 -04:00
|
|
|
where B: Signal<Item = bool> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
|
|
|
|
self.set_focused_signal(value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-02-25 06:58:20 -05:00
|
|
|
// TODO better warning message for must_use
|
|
|
|
#[must_use]
|
2018-02-21 01:37:09 -05:00
|
|
|
pub struct StylesheetBuilder {
|
2019-07-23 00:51:40 -04:00
|
|
|
element: CssStyleDeclaration,
|
2018-02-21 01:37:09 -05:00
|
|
|
callbacks: Callbacks,
|
|
|
|
}
|
|
|
|
|
2018-02-25 06:58:20 -05:00
|
|
|
// TODO remove the CssStyleRule when this is discarded
|
2018-02-21 01:37:09 -05:00
|
|
|
impl StylesheetBuilder {
|
2019-08-04 21:39:40 -04:00
|
|
|
// TODO should this inline ?
|
|
|
|
#[doc(hidden)]
|
2018-02-21 01:37:09 -05:00
|
|
|
#[inline]
|
2019-08-04 21:39:40 -04:00
|
|
|
pub fn __internal_new<A>(selector: A) -> Self where A: MultiStr {
|
2019-06-11 09:29:46 -04:00
|
|
|
// TODO can this be made faster ?
|
|
|
|
// TODO somehow share this safely between threads ?
|
|
|
|
thread_local! {
|
2019-06-20 19:57:47 -04:00
|
|
|
static STYLESHEET: CssStyleSheet = bindings::create_stylesheet();
|
2018-02-21 01:37:09 -05:00
|
|
|
}
|
|
|
|
|
2019-08-04 21:39:40 -04:00
|
|
|
fn try_make(stylesheet: &CssStyleSheet, selector: &str, selectors: &mut Vec<String>) -> Option<CssStyleDeclaration> {
|
2019-07-23 00:51:40 -04:00
|
|
|
// TODO maybe intern the selector ?
|
2019-08-04 21:39:40 -04:00
|
|
|
if let Ok(declaration) = bindings::make_style_rule(stylesheet, selector) {
|
|
|
|
Some(declaration.style())
|
|
|
|
|
|
|
|
} else {
|
|
|
|
selectors.push(String::from(selector));
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let element = STYLESHEET.with(move |stylesheet| {
|
|
|
|
let mut selectors = vec![];
|
|
|
|
|
|
|
|
let okay = selector.find_map(|selector| {
|
|
|
|
try_make(stylesheet, selector, &mut selectors)
|
|
|
|
});
|
|
|
|
|
|
|
|
if let Some(okay) = okay {
|
|
|
|
okay
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// TODO maybe make this configurable
|
|
|
|
panic!("selectors are incorrect:\n {}", selectors.join("\n "));
|
|
|
|
}
|
2019-06-11 09:29:46 -04:00
|
|
|
});
|
|
|
|
|
2018-02-21 01:37:09 -05:00
|
|
|
Self {
|
2019-06-11 09:29:46 -04:00
|
|
|
element,
|
2018-02-21 01:37:09 -05:00
|
|
|
callbacks: Callbacks::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn style<B, C>(self, name: B, value: C) -> Self
|
|
|
|
where B: MultiStr,
|
|
|
|
C: MultiStr {
|
2019-06-21 07:22:53 -04:00
|
|
|
set_style(&self.element, &name, value, false);
|
2018-04-25 19:01:52 -04:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn style_important<B, C>(self, name: B, value: C) -> Self
|
|
|
|
where B: MultiStr,
|
|
|
|
C: MultiStr {
|
2019-06-21 07:22:53 -04:00
|
|
|
set_style(&self.element, &name, value, true);
|
2018-04-25 19:01:52 -04:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-09-18 15:30:52 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn style_unchecked<B, C>(self, name: B, value: C) -> Self
|
|
|
|
where B: AsStr,
|
|
|
|
C: AsStr {
|
|
|
|
name.with_str(|name| {
|
|
|
|
value.with_str(|value| {
|
|
|
|
bindings::set_style(&self.element, intern(name), value, false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-04-25 19:01:52 -04:00
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn style_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
|
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: MultiStr,
|
|
|
|
D: OptionStr<Output = C>,
|
2018-10-22 20:16:54 -04:00
|
|
|
E: Signal<Item = D> + 'static {
|
2018-09-17 17:17:55 -04:00
|
|
|
|
2019-07-23 00:51:40 -04:00
|
|
|
set_style_signal(self.element.clone(), &mut self.callbacks, name, value, false);
|
2018-02-21 01:37:09 -05:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn style_important_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
|
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: MultiStr,
|
|
|
|
D: OptionStr<Output = C>,
|
2018-10-22 20:16:54 -04:00
|
|
|
E: Signal<Item = D> + 'static {
|
2018-09-17 17:17:55 -04:00
|
|
|
|
2019-07-23 00:51:40 -04:00
|
|
|
set_style_signal(self.element.clone(), &mut self.callbacks, name, value, true);
|
2018-02-21 01:37:09 -05:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-09-18 15:30:52 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn style_unchecked_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
|
|
|
|
where B: AsStr + 'static,
|
|
|
|
C: AsStr,
|
|
|
|
D: OptionStr<Output = C>,
|
|
|
|
E: Signal<Item = D> + 'static {
|
|
|
|
|
|
|
|
set_style_unchecked_signal(self.element.clone(), &mut self.callbacks, name, value, false);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-02-21 01:37:09 -05:00
|
|
|
// TODO return a Handle
|
|
|
|
#[inline]
|
2019-08-04 21:39:40 -04:00
|
|
|
#[doc(hidden)]
|
|
|
|
pub fn __internal_done(mut self) {
|
2018-02-21 01:37:09 -05:00
|
|
|
self.callbacks.trigger_after_insert();
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
// This prevents it from triggering after_remove
|
|
|
|
self.callbacks.leak();
|
2018-02-21 01:37:09 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-02-25 06:58:20 -05:00
|
|
|
// TODO better warning message for must_use
|
|
|
|
#[must_use]
|
2018-02-21 01:37:09 -05:00
|
|
|
pub struct ClassBuilder {
|
|
|
|
stylesheet: StylesheetBuilder,
|
|
|
|
class_name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ClassBuilder {
|
2019-08-04 21:39:40 -04:00
|
|
|
#[doc(hidden)]
|
2018-02-21 01:37:09 -05:00
|
|
|
#[inline]
|
2019-08-04 21:39:40 -04:00
|
|
|
pub fn __internal_new() -> Self {
|
|
|
|
let class_name = __internal::make_class_id();
|
2018-02-21 01:37:09 -05:00
|
|
|
|
|
|
|
Self {
|
|
|
|
// TODO make this more efficient ?
|
2019-08-04 21:39:40 -04:00
|
|
|
stylesheet: StylesheetBuilder::__internal_new(&format!(".{}", class_name)),
|
2018-02-21 01:37:09 -05:00
|
|
|
class_name,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-04 21:39:40 -04:00
|
|
|
#[doc(hidden)]
|
|
|
|
#[inline]
|
|
|
|
pub fn __internal_class_name(&self) -> &str {
|
|
|
|
&self.class_name
|
|
|
|
}
|
|
|
|
|
2018-02-21 01:37:09 -05:00
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn style<B, C>(mut self, name: B, value: C) -> Self
|
|
|
|
where B: MultiStr,
|
|
|
|
C: MultiStr {
|
2018-02-21 01:37:09 -05:00
|
|
|
self.stylesheet = self.stylesheet.style(name, value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn style_important<B, C>(mut self, name: B, value: C) -> Self
|
|
|
|
where B: MultiStr,
|
|
|
|
C: MultiStr {
|
2018-02-21 01:37:09 -05:00
|
|
|
self.stylesheet = self.stylesheet.style_important(name, value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-09-18 15:30:52 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn style_unchecked<B, C>(mut self, name: B, value: C) -> Self
|
|
|
|
where B: AsStr,
|
|
|
|
C: AsStr {
|
|
|
|
self.stylesheet = self.stylesheet.style_unchecked(name, value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-04-25 19:01:52 -04:00
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn style_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
|
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: MultiStr,
|
|
|
|
D: OptionStr<Output = C>,
|
2018-10-22 20:16:54 -04:00
|
|
|
E: Signal<Item = D> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
|
|
|
|
self.stylesheet = self.stylesheet.style_signal(name, value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2018-09-17 17:17:55 -04:00
|
|
|
pub fn style_important_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
|
|
|
|
where B: MultiStr + 'static,
|
|
|
|
C: MultiStr,
|
|
|
|
D: OptionStr<Output = C>,
|
2018-10-22 20:16:54 -04:00
|
|
|
E: Signal<Item = D> + 'static {
|
2018-04-25 19:01:52 -04:00
|
|
|
|
|
|
|
self.stylesheet = self.stylesheet.style_important_signal(name, value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-09-18 15:30:52 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn style_unchecked_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
|
|
|
|
where B: AsStr + 'static,
|
|
|
|
C: AsStr,
|
|
|
|
D: OptionStr<Output = C>,
|
|
|
|
E: Signal<Item = D> + 'static {
|
|
|
|
|
|
|
|
self.stylesheet = self.stylesheet.style_unchecked_signal(name, value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-02-21 01:37:09 -05:00
|
|
|
// TODO return a Handle ?
|
2019-08-04 21:39:40 -04:00
|
|
|
#[doc(hidden)]
|
2018-02-21 01:37:09 -05:00
|
|
|
#[inline]
|
2019-08-04 21:39:40 -04:00
|
|
|
pub fn __internal_done(self) -> String {
|
|
|
|
self.stylesheet.__internal_done();
|
2018-02-21 01:37:09 -05:00
|
|
|
self.class_name
|
|
|
|
}
|
|
|
|
}
|
2018-06-13 19:48:41 -04:00
|
|
|
|
|
|
|
|
2019-08-04 21:39:40 -04:00
|
|
|
#[doc(hidden)]
|
|
|
|
pub mod __internal {
|
|
|
|
use std::sync::atomic::{AtomicU32, Ordering};
|
|
|
|
use crate::traits::MultiStr;
|
|
|
|
|
|
|
|
|
2019-08-04 22:07:50 -04:00
|
|
|
pub use web_sys::HtmlElement;
|
|
|
|
pub use web_sys::SvgElement;
|
|
|
|
|
|
|
|
|
2019-08-04 21:39:40 -04:00
|
|
|
pub fn make_class_id() -> String {
|
|
|
|
// TODO replace this with a global counter in JavaScript ?
|
|
|
|
// TODO can this be made more efficient ?
|
|
|
|
static CLASS_ID: AtomicU32 = AtomicU32::new(0);
|
|
|
|
|
|
|
|
// TODO check for overflow ?
|
|
|
|
// TODO should this be SeqCst ?
|
|
|
|
let id = CLASS_ID.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
|
|
|
// TODO make this more efficient ?
|
|
|
|
format!("__class_{}__", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub struct Pseudo<'a, A> {
|
|
|
|
class_name: &'a str,
|
|
|
|
pseudos: A,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, A> Pseudo<'a, A> where A: MultiStr {
|
|
|
|
#[inline]
|
|
|
|
pub fn new(class_name: &'a str, pseudos: A) -> Self {
|
|
|
|
Self { class_name, pseudos }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, A> MultiStr for Pseudo<'a, A> where A: MultiStr {
|
|
|
|
#[inline]
|
|
|
|
fn find_map<B, F>(&self, mut f: F) -> Option<B> where F: FnMut(&str) -> Option<B> {
|
|
|
|
self.pseudos.find_map(|x| {
|
|
|
|
f(&format!(".{}{}", self.class_name, x))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-13 19:48:41 -04:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-03-11 08:29:29 -04:00
|
|
|
use super::{DomBuilder, text_signal, RefFn};
|
2020-05-05 09:32:20 -04:00
|
|
|
use crate::{html, shadow_root, ShadowRootMode, with_cfg};
|
2018-06-22 13:04:08 -04:00
|
|
|
use futures_signals::signal::{always, SignalExt};
|
2021-05-28 09:18:14 -04:00
|
|
|
use once_cell::sync::Lazy;
|
2019-06-11 09:29:46 -04:00
|
|
|
use web_sys::HtmlElement;
|
2018-06-13 19:48:41 -04:00
|
|
|
|
|
|
|
#[test]
|
2019-03-15 06:07:14 -04:00
|
|
|
fn apply() {
|
2020-03-11 08:29:29 -04:00
|
|
|
let a: DomBuilder<HtmlElement> = DomBuilder::new_html("div");
|
2018-06-13 19:48:41 -04:00
|
|
|
|
2019-06-11 09:29:46 -04:00
|
|
|
fn my_mixin<A: AsRef<HtmlElement>>(builder: DomBuilder<A>) -> DomBuilder<A> {
|
2018-06-13 19:48:41 -04:00
|
|
|
builder.style("foo", "bar")
|
|
|
|
}
|
|
|
|
|
2019-06-20 11:52:52 -04:00
|
|
|
let _ = a.apply(my_mixin);
|
2018-06-13 19:48:41 -04:00
|
|
|
}
|
|
|
|
|
2020-04-27 01:36:48 -04:00
|
|
|
#[test]
|
|
|
|
fn children_mut() {
|
|
|
|
let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
|
|
|
|
.children(&mut [
|
|
|
|
DomBuilder::<HtmlElement>::new_html("div").into_dom(),
|
|
|
|
DomBuilder::<HtmlElement>::new_html("div").into_dom(),
|
|
|
|
DomBuilder::<HtmlElement>::new_html("div").into_dom(),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn children_value() {
|
|
|
|
let v: Vec<u32> = vec![];
|
|
|
|
|
|
|
|
let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
|
|
|
|
.children(v.iter().map(|_| {
|
|
|
|
DomBuilder::<HtmlElement>::new_html("div").into_dom()
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2018-06-13 19:48:41 -04:00
|
|
|
#[test]
|
|
|
|
fn text_signal_types() {
|
2019-06-20 11:52:52 -04:00
|
|
|
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())));
|
2018-06-22 13:04:08 -04:00
|
|
|
//text_signal(always(Arc::new("foo")));
|
|
|
|
//text_signal(always(Arc::new("foo".to_owned())));
|
|
|
|
//text_signal(always(Rc::new("foo")));
|
|
|
|
//text_signal(always(Rc::new("foo".to_owned())));
|
|
|
|
//text_signal(always(Box::new("foo")));
|
|
|
|
//text_signal(always(Box::new("foo".to_owned())));
|
|
|
|
//text_signal(always(Cow::Borrowed(&"foo")));
|
|
|
|
//text_signal(always(Cow::Owned::<String>("foo".to_owned())));
|
2018-06-13 19:48:41 -04:00
|
|
|
}
|
|
|
|
|
2018-09-17 17:17:55 -04:00
|
|
|
#[test]
|
|
|
|
fn property_signal_types() {
|
2020-03-11 08:29:29 -04:00
|
|
|
let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
|
2022-04-10 17:57:51 -04:00
|
|
|
.prop("foo", "hi")
|
|
|
|
.prop("foo", 5)
|
|
|
|
.prop(["foo", "-webkit-foo", "-ms-foo"], "hi")
|
2018-09-17 17:17:55 -04:00
|
|
|
|
2022-04-10 17:57:51 -04:00
|
|
|
.prop_signal("foo", always("hi"))
|
|
|
|
.prop_signal("foo", always(5))
|
|
|
|
.prop_signal("foo", always(Some("hi")))
|
2018-09-17 17:17:55 -04:00
|
|
|
|
2022-04-10 17:57:51 -04:00
|
|
|
.prop_signal(["foo", "-webkit-foo", "-ms-foo"], always("hi"))
|
|
|
|
.prop_signal(["foo", "-webkit-foo", "-ms-foo"], always(5))
|
|
|
|
.prop_signal(["foo", "-webkit-foo", "-ms-foo"], always(Some("hi")))
|
2018-09-17 17:17:55 -04:00
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn attribute_signal_types() {
|
2020-03-11 08:29:29 -04:00
|
|
|
let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
|
2022-04-10 17:57:51 -04:00
|
|
|
.attr("foo", "hi")
|
|
|
|
.attr(["foo", "-webkit-foo", "-ms-foo"], "hi")
|
2018-09-17 17:17:55 -04:00
|
|
|
|
2022-04-10 17:57:51 -04:00
|
|
|
.attr_signal("foo", always("hi"))
|
|
|
|
.attr_signal("foo", always(Some("hi")))
|
2018-09-17 17:17:55 -04:00
|
|
|
|
2022-04-10 17:57:51 -04:00
|
|
|
.attr_signal(["foo", "-webkit-foo", "-ms-foo"], always("hi"))
|
|
|
|
.attr_signal(["foo", "-webkit-foo", "-ms-foo"], always(Some("hi")))
|
2018-09-17 17:17:55 -04:00
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn class_signal_types() {
|
2020-03-11 08:29:29 -04:00
|
|
|
let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
|
2018-09-17 17:17:55 -04:00
|
|
|
.class("foo")
|
|
|
|
.class(["foo", "-webkit-foo", "-ms-foo"])
|
|
|
|
|
|
|
|
.class_signal("foo", always(true))
|
|
|
|
.class_signal(["foo", "-webkit-foo", "-ms-foo"], always(true))
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2018-06-13 19:48:41 -04:00
|
|
|
#[test]
|
|
|
|
fn style_signal_types() {
|
2021-05-28 09:18:14 -04:00
|
|
|
static FOO: Lazy<String> = Lazy::new(|| "foo".to_owned());
|
2018-09-17 17:58:05 -04:00
|
|
|
|
2020-03-11 08:29:29 -04:00
|
|
|
let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
|
2018-06-13 19:48:41 -04:00
|
|
|
.style_signal("foo", always("bar"))
|
|
|
|
.style_signal("foo", always("bar".to_owned()))
|
2018-09-17 17:17:55 -04:00
|
|
|
.style_signal("foo", always("bar".to_owned()).map(|x| RefFn::new(x, |x| x.as_str())))
|
|
|
|
|
2018-09-17 17:58:05 -04:00
|
|
|
.style("foo".to_owned(), "bar".to_owned())
|
|
|
|
.style_signal("foo".to_owned(), always("bar".to_owned()))
|
|
|
|
|
|
|
|
.style(&"foo".to_owned(), &"bar".to_owned())
|
2018-09-18 17:04:03 -04:00
|
|
|
//.style(Box::new("foo".to_owned()), Box::new("bar".to_owned()))
|
|
|
|
//.style_signal(Box::new("foo".to_owned()), always(Box::new("bar".to_owned())))
|
2018-09-17 17:58:05 -04:00
|
|
|
|
|
|
|
.style_signal(&*FOO, always(&*FOO))
|
|
|
|
|
2018-09-18 17:04:03 -04:00
|
|
|
//.style(vec!["-moz-foo", "-webkit-foo", "foo"].as_slice(), vec!["bar"].as_slice())
|
2018-09-18 06:50:07 -04:00
|
|
|
.style_signal(RefFn::new(vec!["-moz-foo", "-webkit-foo", "foo"], |x| x.as_slice()), always(RefFn::new(vec!["bar"], |x| x.as_slice())))
|
2018-06-28 13:02:35 -04:00
|
|
|
|
|
|
|
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar"))
|
|
|
|
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar".to_owned()))
|
2018-09-17 17:17:55 -04:00
|
|
|
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar".to_owned()).map(|x| RefFn::new(x, |x| x.as_str())))
|
|
|
|
|
|
|
|
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always(["bar", "qux"]))
|
|
|
|
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always(["bar".to_owned(), "qux".to_owned()]))
|
2018-09-18 06:50:07 -04:00
|
|
|
|
|
|
|
//.style_signal(["-moz-foo", "-webkit-foo", "foo"], always(AsSlice::new(["foo", "bar"])))
|
|
|
|
//.style_signal(["-moz-foo", "-webkit-foo", "foo"], always(("bar".to_owned(), "qux".to_owned())).map(|x| RefFn::new(x, |x| AsSlice::new([x.0.as_str(), x.1.as_str()]))))
|
2018-06-13 19:48:41 -04:00
|
|
|
|
|
|
|
.style_signal("foo", always(Some("bar")))
|
|
|
|
.style_signal("foo", always(Some("bar".to_owned())))
|
2018-09-17 17:17:55 -04:00
|
|
|
.style_signal("foo", always("bar".to_owned()).map(|x| Some(RefFn::new(x, |x| x.as_str()))))
|
2018-06-28 13:02:35 -04:00
|
|
|
|
|
|
|
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always(Some("bar")))
|
|
|
|
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always(Some("bar".to_owned())))
|
2018-09-17 17:17:55 -04:00
|
|
|
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar".to_owned()).map(|x| Some(RefFn::new(x, |x| x.as_str()))))
|
2018-06-13 19:48:41 -04:00
|
|
|
;
|
|
|
|
}
|
2020-05-04 17:28:09 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shadow_root() {
|
|
|
|
let _a = html!("div", {
|
|
|
|
.shadow_root!(ShadowRootMode::Closed => {
|
|
|
|
.children(&mut [
|
|
|
|
html!("span")
|
|
|
|
])
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|
2020-05-05 09:32:20 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn with_cfg() {
|
|
|
|
let _a = html!("div", {
|
|
|
|
.with_cfg!(target_arch = "wasm32", {
|
2022-04-10 17:57:51 -04:00
|
|
|
.attr("foo", "bar")
|
2020-05-05 09:32:20 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
.with_cfg!(all(not(foo), bar = "test", feature = "hi"), {
|
2022-04-10 17:57:51 -04:00
|
|
|
.attr("foo", "bar")
|
2020-05-05 09:32:20 -04:00
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|
2018-06-13 19:48:41 -04:00
|
|
|
}
|