use std::pin::Pin;
use std::borrow::BorrowMut;
use std::convert::AsRef;
use std::future::Future;
use std::task::{Context, Poll};
use once_cell::sync::Lazy;
use futures_signals::signal::{Signal, Mutable, MutableSignal, not};
use futures_signals::signal_vec::SignalVec;
use futures_util::FutureExt;
use futures_channel::oneshot;
use discard::{Discard, DiscardOnDrop};
use wasm_bindgen::{JsValue, UnwrapThrowExt, JsCast, intern};
use web_sys::{HtmlElement, Node, EventTarget, Element, CssRule, CssStyleRule, CssStyleSheet, CssStyleDeclaration, ShadowRoot, ShadowRootMode, ShadowRootInit, Text};
use crate::bindings;
use crate::bindings::WINDOW;
use crate::callbacks::Callbacks;
use crate::traits::*;
use crate::fragment::{Fragment, FragmentBuilder};
use crate::operations;
use crate::operations::{for_each, spawn_future};
use crate::utils::{EventListener, on, RefCounter, MutableListener, UnwrapJsExt, ValueDiscard, FnDiscard};
#[cfg(doc)]
use crate::fragment;
pub struct RefFn<A, B, C> where B: ?Sized, C: Fn(&A) -> &B {
value: A,
callback: C,
}
impl<A, B, C> RefFn<A, B, C> where B: ?Sized, C: Fn(&A) -> &B {
#[inline]
pub fn new(value: A, callback: C) -> Self {
Self {
value,
callback,
}
}
#[inline]
pub fn call_ref(&self) -> &B {
(self.callback)(&self.value)
}
}
const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
pub const HIGHEST_ZINDEX: &str = "2147483647";
static HIDDEN_CLASS: Lazy<String> = Lazy::new(|| class! {
.style_important("display", "none")
});
pub fn body() -> HtmlElement {
bindings::body()
}
pub fn get_id(id: &str) -> Element {
bindings::get_element_by_id(id)
}
pub struct DomHandle {
parent: Node,
dom: Dom,
}
impl DomHandle {
#[inline]
pub(crate) fn new(parent: &Node, mut dom: Dom) -> Self {
dom.callbacks.trigger_after_insert();
dom.callbacks.leak();
Self {
parent: parent.clone(),
dom,
}
}
}
impl Discard for DomHandle {
#[inline]
#[track_caller]
fn discard(self) {
bindings::remove_child(&self.parent, &self.dom.element);
self.dom.callbacks.discard();
}
}
#[inline]
#[track_caller]
pub fn append_dom(parent: &Node, dom: Dom) -> DomHandle {
bindings::append_child(&parent, &dom.element);
DomHandle::new(parent, dom)
}
#[inline]
#[track_caller]
pub fn replace_dom(parent: &Node, old_node: &Node, dom: Dom) -> DomHandle {
bindings::replace_child(&parent, &dom.element, old_node);
DomHandle::new(parent, dom)
}
#[must_use = "Signals do nothing unless polled"]
enum IsWindowLoaded {
Initial {},
Pending {
receiver: oneshot::Receiver<Option<bool>>,
_event: DiscardOnDrop<EventListener>,
},
Done {},
}
impl Signal for IsWindowLoaded {
type Item = bool;
fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
let result = match *self {
IsWindowLoaded::Initial {} => {
let is_ready = bindings::ready_state() == "complete";
if is_ready {
Poll::Ready(Some(true))
} else {
let (sender, receiver) = oneshot::channel();
*self = IsWindowLoaded::Pending {
receiver,
_event: DiscardOnDrop::new(WINDOW.with(|window| {
EventListener::once(window, "load", move |_| {
crate::__unwrap!(
sender.send(Some(true)),
_e => panic!("Invalid is_window_loaded() state"),
)
})
})),
};
Poll::Ready(Some(false))
}
},
IsWindowLoaded::Pending { ref mut receiver, .. } => {
receiver.poll_unpin(cx).map(|x| x.unwrap_throw())
},
IsWindowLoaded::Done {} => {
Poll::Ready(None)
},
};
if let Poll::Ready(Some(true)) = result {
*self = IsWindowLoaded::Done {};
}
result
}
}
#[inline]
pub fn is_window_loaded() -> impl Signal<Item = bool> {
IsWindowLoaded::Initial {}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WindowSize {
pub width: f64,
pub height: f64,
}
impl WindowSize {
fn new() -> Self {
WINDOW.with(|window| {
let width = window.inner_width().unwrap_throw().as_f64().unwrap_throw();
let height = window.inner_height().unwrap_throw().as_f64().unwrap_throw();
Self { width, height }
})
}
}
thread_local! {
static WINDOW_SIZE: RefCounter<MutableListener<WindowSize>> = RefCounter::new();
}
#[derive(Debug)]
#[must_use = "Signals do nothing unless polled"]
struct WindowSizeSignal {
signal: MutableSignal<WindowSize>,
}
impl Signal for WindowSizeSignal {
type Item = WindowSize;
#[inline]
fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.signal).poll_change(cx)
}
}
impl Drop for WindowSizeSignal {
fn drop(&mut self) {
WINDOW_SIZE.with(|size| {
size.decrement();
});
}
}
pub fn window_size() -> impl Signal<Item = WindowSize> {
let signal = WINDOW_SIZE.with(|size| {
let size = size.increment(|| {
let size = Mutable::new(WindowSize::new());
let listener = {
let size = size.clone();
WINDOW.with(move |window| {
on(window, &EventOptions::default(), move |_: crate::events::Resize| {
size.set_neq(WindowSize::new());
})
})
};
MutableListener::new(size, listener)
});
size.as_mutable().signal()
});
WindowSizeSignal { signal }
}
fn media_query_raw<A, F>(query: &str, mut f: F) -> (Mutable<A>, EventListener)
where A: PartialEq + 'static,
F: FnMut(bool) -> A + 'static {
let query = WINDOW.with(|window| window.match_media(query).unwrap().unwrap());
let mutable = Mutable::new(f(query.matches()));
let listener = on(&query, &EventOptions::default(), {
let mutable = mutable.clone();
let query = query.clone();
move |_: crate::events::Change| {
mutable.set_neq(f(query.matches()));
}
});
(mutable, listener)
}
#[derive(Debug)]
#[must_use = "Signals do nothing unless polled"]
struct MediaQuerySignal {
signal: MutableSignal<bool>,
_listener: EventListener,
}
impl Signal for MediaQuerySignal {
type Item = bool;
#[inline]
fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.signal).poll_change(cx)
}
}
pub fn media_query(query: &str) -> impl Signal<Item = bool> {
let (mutable, listener) = media_query_raw(query, |value| value);
MediaQuerySignal {
signal: mutable.signal(),
_listener: listener,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorScheme {
Light,
Dark,
}
impl ColorScheme {
#[inline]
fn from_bool(matches: bool) -> Self {
if matches {
Self::Dark
} else {
Self::Light
}
}
#[inline]
pub fn is_light(self) -> bool {
matches!(self, Self::Light)
}
#[inline]
pub fn is_dark(self) -> bool {
matches!(self, Self::Dark)
}
#[inline]
pub fn choose<A>(self, light: A, dark: A) -> A {
match self {
Self::Light => light,
Self::Dark => dark,
}
}
}
thread_local! {
static COLOR_SCHEME: RefCounter<MutableListener<ColorScheme>> = RefCounter::new();
}
#[derive(Debug)]
#[must_use = "Signals do nothing unless polled"]
struct ColorSchemeSignal {
signal: MutableSignal<ColorScheme>,
}
impl Signal for ColorSchemeSignal {
type Item = ColorScheme;
#[inline]
fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.signal).poll_change(cx)
}
}
impl Drop for ColorSchemeSignal {
fn drop(&mut self) {
COLOR_SCHEME.with(|x| x.decrement());
}
}
pub fn color_scheme() -> impl Signal<Item = ColorScheme> {
let signal = COLOR_SCHEME.with(|counter| {
let counter = counter.increment(|| {
let (mutable, listener) = media_query_raw("(prefers-color-scheme: dark)", ColorScheme::from_bool);
MutableListener::new(mutable, listener)
});
counter.as_mutable().signal()
});
ColorSchemeSignal { signal }
}
#[inline]
pub fn text(value: &str) -> Dom {
Dom::new(bindings::create_text_node(value).into())
}
fn make_text_signal<A, B>(callbacks: &mut Callbacks, value: B) -> Text
where A: AsStr,
B: Signal<Item = A> + 'static {
let element = bindings::create_text_node(intern(""));
{
let element = element.clone();
callbacks.after_remove(for_each(value, move |value| {
value.with_str(|value| {
bindings::set_text(&element, value);
});
}));
}
element
}
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);
Dom {
element: element.into(),
callbacks: callbacks,
}
}
#[must_use]
#[derive(Debug)]
pub struct Dom {
pub(crate) element: Node,
pub(crate) callbacks: Callbacks,
}
impl Dom {
#[inline]
pub fn new(element: Node) -> Self {
Self {
element,
callbacks: Callbacks::new(),
}
}
#[inline]
#[track_caller]
pub fn empty() -> Self {
Self::new(bindings::create_empty_node())
}
#[deprecated(since = "0.5.15", note = "Store the data explicitly in a component struct instead")]
#[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);
dom.callbacks.after_remove(ValueDiscard::new(state));
dom
}
}
#[inline]
#[track_caller]
fn create_element<A>(name: &str) -> A where A: JsCast {
crate::__unwrap!(
bindings::create_element(intern(name)).dyn_into(),
e => panic!("Invalid DOM type: \"{}\" => {:?}", name, JsValue::as_ref(&e)),
)
}
#[inline]
#[track_caller]
fn create_element_ns<A>(name: &str, namespace: &str) -> A where A: JsCast {
crate::__unwrap!(
bindings::create_element_ns(intern(namespace), intern(name)).dyn_into(),
e => panic!("Invalid DOM type: \"{}\" => {:?}", name, JsValue::as_ref(&e)),
)
}
fn set_option<A, B, C, D, F>(element: A, callbacks: &mut Callbacks, value: D, mut f: F)
where A: 'static,
C: OptionStr<Output = B>,
D: Signal<Item = C> + 'static,
F: FnMut(&A, Option<B>) + 'static {
let mut is_set = false;
callbacks.after_remove(for_each(value, move |value| {
let value = value.into_option();
if value.is_some() {
is_set = true;
} else if is_set {
is_set = false;
} else {
return;
}
f(&element, value);
}));
}
fn set_style<A, B>(style: &CssStyleDeclaration, name: &A, value: B, important: bool)
where A: MultiStr,
B: MultiStr {
let mut names = vec![];
let mut values = vec![];
fn try_set_style(style: &CssStyleDeclaration, names: &mut Vec<String>, values: &mut Vec<String>, name: &str, value: &str, important: bool) -> Option<()> {
assert!(value != "");
bindings::remove_style(style, name);
bindings::set_style(style, name, value, important);
let is_changed = bindings::get_style(style, name) != "";
if is_changed {
Some(())
} else {
names.push(String::from(name));
values.push(String::from(value));
None
}
}
let okay = name.find_map(|name| {
let name: &str = intern(name);
value.find_map(|value| {
try_set_style(style, &mut names, &mut values, &name, &value, important)
})
});
if let None = okay {
if cfg!(debug_assertions) {
panic!("style is incorrect:\n names: {}\n values: {}", names.join(", "), values.join(", "));
}
}
}
fn set_style_signal<A, B, C, D>(style: CssStyleDeclaration, callbacks: &mut Callbacks, name: A, value: D, important: bool)
where A: MultiStr + 'static,
B: MultiStr,
C: OptionStr<Output = B>,
D: Signal<Item = C> + 'static {
set_option(style, callbacks, value, move |style, value| {
match value {
Some(value) => {
set_style(style, &name, value, important);
},
None => {
name.each(|name| {
bindings::remove_style(style, intern(name));
});
},
}
});
}
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));
});
},
}
});
}
#[track_caller]
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| {
bindings::set_property(element, intern(name), &value);
});
}
#[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,
}
}
pub(crate) fn into_gloo(self) -> gloo_events::EventListenerOptions {
gloo_events::EventListenerOptions {
phase: if self.bubbles {
gloo_events::EventListenerPhase::Bubble
} else {
gloo_events::EventListenerPhase::Capture
},
passive: !self.preventable,
}
}
}
impl Default for EventOptions {
fn default() -> Self {
Self {
bubbles: false,
preventable: false,
}
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum ScrollBehavior {
Auto,
Instant,
Smooth,
}
impl ScrollBehavior {
fn into_js(self) -> web_sys::ScrollBehavior {
match self {
Self::Auto => web_sys::ScrollBehavior::Auto,
Self::Instant => web_sys::ScrollBehavior::Instant,
Self::Smooth => web_sys::ScrollBehavior::Smooth,
}
}
}
impl Default for ScrollBehavior {
fn default() -> Self {
Self::Auto
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum ScrollAlign {
Start,
Center,
End,
Nearest,
}
impl ScrollAlign {
fn into_js(self) -> web_sys::ScrollLogicalPosition {
match self {
Self::Start => web_sys::ScrollLogicalPosition::Start,
Self::Center => web_sys::ScrollLogicalPosition::Center,
Self::End => web_sys::ScrollLogicalPosition::End,
Self::Nearest => web_sys::ScrollLogicalPosition::Nearest,
}
}
}
#[derive(Debug, Clone, Hash, PartialEq)]
pub struct ScrollIntoView {
pub behavior: ScrollBehavior,
pub align_x: ScrollAlign,
pub align_y: ScrollAlign,
}
impl ScrollIntoView {
pub fn smooth_center() -> Self {
Self {
behavior: ScrollBehavior::Smooth,
align_x: ScrollAlign::Center,
align_y: ScrollAlign::Center,
}
}
pub fn smooth_nearest() -> Self {
Self {
behavior: ScrollBehavior::Smooth,
align_x: ScrollAlign::Nearest,
align_y: ScrollAlign::Nearest,
}
}
fn into_js(&self) -> web_sys::ScrollIntoViewOptions {
let output = web_sys::ScrollIntoViewOptions::new();
output.set_inline(self.align_x.into_js());
output.set_block(self.align_y.into_js());
output.set_behavior(self.behavior.into_js());
output
}
}
impl Default for ScrollIntoView {
fn default() -> Self {
Self {
behavior: ScrollBehavior::default(),
align_x: ScrollAlign::Start,
align_y: ScrollAlign::Nearest,
}
}
}
#[must_use]
#[derive(Debug)]
pub struct DomBuilder<A> {
element: A,
callbacks: Callbacks,
}
impl<A> DomBuilder<A> where A: JsCast {
#[track_caller]
#[inline]
pub fn new_html(name: &str) -> Self {
Self::new(create_element(name))
}
#[track_caller]
#[inline]
pub fn new_svg(name: &str) -> Self {
Self::new(create_element_ns(name, SVG_NAMESPACE))
}
}
impl<A> DomBuilder<A> {
#[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
}
#[inline]
pub fn new(value: A) -> Self {
Self {
element: value,
callbacks: Callbacks::new(),
}
}
#[inline]
#[track_caller]
fn _event<T, F>(callbacks: &mut Callbacks, element: &EventTarget, options: &EventOptions, listener: F)
where T: StaticEvent,
F: FnMut(T) + 'static {
callbacks.after_remove(on(element, options, listener));
}
#[inline]
#[track_caller]
pub fn global_event_with_options<T, F>(mut self, options: &EventOptions, listener: F) -> Self
where T: StaticEvent,
F: FnMut(T) + 'static {
WINDOW.with(|window| {
Self::_event(&mut self.callbacks, window, options, listener);
});
self
}
#[inline]
#[track_caller]
pub fn global_event<T, F>(self, listener: F) -> Self
where T: StaticEvent,
F: FnMut(T) + 'static {
self.global_event_with_options(&T::default_options(false), listener)
}
#[deprecated(since = "0.5.21", note = "Use global_event_with_options instead")]
#[inline]
#[track_caller]
pub fn global_event_preventable<T, F>(self, listener: F) -> Self
where T: StaticEvent,
F: FnMut(T) + 'static {
self.global_event_with_options(&T::default_options(true), listener)
}
#[inline]
pub fn future<F>(mut self, future: F) -> Self where F: Future<Output = ()> + 'static {
self.callbacks.after_remove(DiscardOnDrop::leak(spawn_future(future)));
self
}
#[inline]
pub fn apply<F>(self, f: F) -> Self where F: FnOnce(Self) -> Self {
f(self)
}
#[inline]
pub fn apply_if<F>(self, test: bool, f: F) -> Self where F: FnOnce(Self) -> Self {
if test {
f(self)
} else {
self
}
}
}
impl<A> DomBuilder<A> where A: Clone {
#[inline]
#[doc(hidden)]
pub fn __internal_element(&self) -> A {
self.element.clone()
}
#[deprecated(since = "0.5.1", note = "Use the with_node macro instead")]
#[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)
}
#[deprecated(since = "0.5.20", note = "Use the with_node macro instead")]
#[inline]
pub fn before_inserted<F>(self, f: F) -> Self where F: FnOnce(A) {
let element = self.element.clone();
f(element);
self
}
}
impl<A> DomBuilder<A> where A: Clone + 'static {
#[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
}
}
impl<A> DomBuilder<A> where A: Into<Node> {
#[inline]
pub fn into_dom(self) -> Dom {
Dom {
element: self.element.into(),
callbacks: self.callbacks,
}
}
}
impl<A> DomBuilder<A> where A: AsRef<JsValue> {
#[inline]
#[track_caller]
pub fn prop<B, C>(self, name: B, value: C) -> Self where B: MultiStr, C: Into<JsValue> {
set_property(&self.element, &name, value);
self
}
#[deprecated(since = "0.5.24", note = "Use the `prop` method instead")]
#[inline]
#[track_caller]
pub fn property<B, C>(self, name: B, value: C) -> Self where B: MultiStr, C: Into<JsValue> {
self.prop(name, value)
}
}
impl<A> DomBuilder<A> where A: AsRef<JsValue> {
fn set_property_signal<B, C, D>(&mut self, name: B, value: D)
where B: MultiStr + 'static,
C: Into<JsValue>,
D: Signal<Item = C> + 'static {
let element = self.element.as_ref().clone();
self.callbacks.after_remove(for_each(value, move |value| {
set_property(&element, &name, value);
}));
}
#[inline]
#[track_caller]
pub fn prop_signal<B, C, D>(mut self, name: B, value: D) -> Self
where B: MultiStr + 'static,
C: Into<JsValue>,
D: Signal<Item = C> + 'static {
self.set_property_signal(name, value);
self
}
#[deprecated(since = "0.5.24", note = "Use the `prop_signal` method instead")]
#[inline]
#[track_caller]
pub fn property_signal<B, C, D>(self, name: B, value: D) -> Self
where B: MultiStr + 'static,
C: Into<JsValue>,
D: Signal<Item = C> + 'static {
self.prop_signal(name, value)
}
}
impl<A> DomBuilder<A> where A: AsRef<EventTarget> {
#[inline]
#[track_caller]
pub fn event_with_options<T, F>(mut self, options: &EventOptions, listener: F) -> Self
where T: StaticEvent,
F: FnMut(T) + 'static {
Self::_event(&mut self.callbacks, &self.element.as_ref(), options, listener);
self
}
#[inline]
#[track_caller]
pub fn event<T, F>(self, listener: F) -> Self
where T: StaticEvent,
F: FnMut(T) + 'static {
self.event_with_options(&T::default_options(false), listener)
}
#[deprecated(since = "0.5.21", note = "Use event_with_options instead")]
#[inline]
#[track_caller]
pub fn event_preventable<T, F>(self, listener: F) -> Self
where T: StaticEvent,
F: FnMut(T) + 'static {
self.event_with_options(&T::default_options(true), listener)
}
}
impl<A> DomBuilder<A> where A: AsRef<Node> {
#[inline]
#[track_caller]
pub fn fragment<F>(self, fragment: &F) -> Self where F: Fragment {
let FragmentBuilder(DomBuilder { callbacks, .. }) = {
let element: &Node = self.element.as_ref();
fragment.apply(FragmentBuilder(DomBuilder {
element,
callbacks: self.callbacks,
}))
};
Self {
element: self.element,
callbacks,
}
}
#[inline]
#[track_caller]
pub fn text(self, value: &str) -> Self {
bindings::append_child(self.element.as_ref(), &bindings::create_text_node(value));
self
}
#[inline]
#[track_caller]
pub fn text_signal<B, C>(mut self, value: C) -> Self
where B: AsStr,
C: Signal<Item = B> + 'static {
let element = make_text_signal(&mut self.callbacks, value);
bindings::append_child(self.element.as_ref(), &element);
self
}
#[inline]
#[track_caller]
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());
self
}
#[inline]
#[track_caller]
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);
self
}
#[inline]
#[track_caller]
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]
#[track_caller]
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
}
}
impl<A> DomBuilder<A> where A: AsRef<Element> {
#[inline]
#[doc(hidden)]
#[track_caller]
pub fn __internal_shadow_root(&self, mode: ShadowRootMode) -> DomBuilder<ShadowRoot> {
let shadow = self.element.as_ref().attach_shadow(&ShadowRootInit::new(mode)).unwrap_js();
DomBuilder::new(shadow)
}
#[inline]
#[track_caller]
pub fn attr<B>(self, name: B, value: &str) -> Self where B: MultiStr {
let element = self.element.as_ref();
name.each(|name| {
bindings::set_attribute(element, intern(name), &value);
});
self
}
#[deprecated(since = "0.5.24", note = "Use the `attr` method instead")]
#[inline]
#[track_caller]
pub fn attribute<B>(self, name: B, value: &str) -> Self where B: MultiStr {
self.attr(name, value)
}
#[inline]
#[track_caller]
pub fn attr_ns<B>(self, namespace: &str, name: B, value: &str) -> Self where B: MultiStr {
let element = self.element.as_ref();
let namespace: &str = intern(namespace);
name.each(|name| {
bindings::set_attribute_ns(element, &namespace, intern(name), &value);
});
self
}
#[deprecated(since = "0.5.24", note = "Use the `attr_ns` method instead")]
#[inline]
#[track_caller]
pub fn attribute_namespace<B>(self, namespace: &str, name: B, value: &str) -> Self where B: MultiStr {
self.attr_ns(namespace, name, value)
}
#[inline]
#[track_caller]
pub fn class<B>(self, name: B) -> Self where B: MultiStr {
let classes = self.element.as_ref().class_list();
name.each(|name| {
bindings::add_class(&classes, intern(name));
});
self
}
#[inline]
#[track_caller]
pub fn visible(self, value: bool) -> Self {
if value {
self
} else {
self.class(&*HIDDEN_CLASS)
}
}
}
impl<A> DomBuilder<A> where A: AsRef<Element> {
fn set_attribute_signal<B, C, D, E>(&mut self, name: B, value: E)
where B: MultiStr + 'static,
C: AsStr,
D: OptionStr<Output = C>,
E: Signal<Item = D> + 'static {
set_option(self.element.as_ref().clone(), &mut self.callbacks, value, move |element, value| {
match value {
Some(value) => {
value.with_str(|value| {
name.each(|name| {
bindings::set_attribute(element, intern(name), &value);
});
});
},
None => {
name.each(|name| {
bindings::remove_attribute(element, intern(name));
});
},
}
});
}
#[inline]
#[track_caller]
pub fn attr_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
where B: MultiStr + 'static,
C: AsStr,
D: OptionStr<Output = C>,
E: Signal<Item = D> + 'static {
self.set_attribute_signal(name, value);
self
}
#[deprecated(since = "0.5.24", note = "Use the `attr_signal` method instead")]
#[inline]
#[track_caller]
pub fn attribute_signal<B, C, D, E>(self, name: B, value: E) -> Self
where B: MultiStr + 'static,
C: AsStr,
D: OptionStr<Output = C>,
E: Signal<Item = D> + 'static {
self.attr_signal(name, value)
}
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>,
E: Signal<Item = D> + 'static {
let namespace: String = intern(namespace).to_owned();
set_option(self.element.as_ref().clone(), &mut self.callbacks, value, move |element, value| {
match value {
Some(value) => {
value.with_str(|value| {
name.each(|name| {
bindings::set_attribute_ns(element, &namespace, intern(name), &value);
});
});
},
None => {
name.each(|name| {
bindings::remove_attribute_ns(element, &namespace, intern(name));
});
},
}
});
}
#[inline]
#[track_caller]
pub fn attr_ns_signal<B, C, D, E>(mut self, namespace: &str, name: B, value: E) -> Self
where B: MultiStr + 'static,
C: AsStr,
D: OptionStr<Output = C>,
E: Signal<Item = D> + 'static {
self.set_attribute_namespace_signal(namespace, name, value);
self
}
#[deprecated(since = "0.5.24", note = "Use the `attr_ns_signal` method instead")]
#[inline]
#[track_caller]
pub fn attribute_namespace_signal<B, C, D, E>(self, namespace: &str, name: B, value: E) -> Self
where B: MultiStr + 'static,
C: AsStr,
D: OptionStr<Output = C>,
E: Signal<Item = D> + 'static {
self.attr_ns_signal(namespace, name, value)
}
fn set_class_signal<B, C>(&mut self, name: B, value: C)
where B: MultiStr + 'static,
C: Signal<Item = bool> + 'static {
let element = self.element.as_ref().class_list();
let mut is_set = false;
self.callbacks.after_remove(for_each(value, move |value| {
if value {
if !is_set {
is_set = true;
name.each(|name| {
bindings::add_class(&element, intern(name));
});
}
} else {
if is_set {
is_set = false;
name.each(|name| {
bindings::remove_class(&element, intern(name));
});
}
}
}));
}
#[inline]
#[track_caller]
pub fn class_signal<B, C>(mut self, name: B, value: C) -> Self
where B: MultiStr + 'static,
C: Signal<Item = bool> + 'static {
self.set_class_signal(name, value);
self
}
#[inline]
#[track_caller]
pub fn visible_signal<B>(self, value: B) -> Self where B: Signal<Item = bool> + 'static {
self.class_signal(&*HIDDEN_CLASS, not(value))
}
fn set_scroll_signal<B, F>(&mut self, signal: B, mut f: F)
where B: Signal<Item = Option<i32>> + 'static,
F: FnMut(&Element, i32) + 'static {
let element: Element = self.element.as_ref().clone();
self.callbacks.after_insert(move |callbacks| {
callbacks.after_remove(for_each(signal, move |value| {
if let Some(value) = value {
f(&element, value);
}
}));
});
}
#[inline]
#[track_caller]
pub fn scroll_left_signal<B>(mut self, signal: B) -> Self where B: Signal<Item = Option<i32>> + 'static {
self.set_scroll_signal(signal, Element::set_scroll_left);
self
}
#[inline]
#[track_caller]
pub fn scroll_top_signal<B>(mut self, signal: B) -> Self where B: Signal<Item = Option<i32>> + 'static {
self.set_scroll_signal(signal, Element::set_scroll_top);
self
}
fn set_scroll_into_view_signal<B>(&mut self, signal: B)
where B: Signal<Item = Option<ScrollIntoView>> + 'static {
let element: Element = self.element.as_ref().clone();
self.callbacks.after_insert(move |callbacks| {
callbacks.after_remove(for_each(signal, move |options| {
if let Some(options) = options {
element.scroll_into_view_with_scroll_into_view_options(&options.into_js());
}
}));
});
}
#[inline]
#[track_caller]
pub fn scroll_into_view_signal<B>(mut self, signal: B) -> Self where B: Signal<Item = Option<ScrollIntoView>> + 'static {
self.set_scroll_into_view_signal(signal);
self
}
}
impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
#[inline]
#[track_caller]
pub fn style<B, C>(self, name: B, value: C) -> Self
where B: MultiStr,
C: MultiStr {
set_style(&self.element.as_ref().style(), &name, value, false);
self
}
#[inline]
#[track_caller]
pub fn style_important<B, C>(self, name: B, value: C) -> Self
where B: MultiStr,
C: MultiStr {
set_style(&self.element.as_ref().style(), &name, value, true);
self
}
#[inline]
#[track_caller]
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
}
}
impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
#[inline]
#[track_caller]
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>,
E: Signal<Item = D> + 'static {
set_style_signal(self.element.as_ref().style(), &mut self.callbacks, name, value, false);
self
}
#[inline]
#[track_caller]
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>,
E: Signal<Item = D> + 'static {
set_style_signal(self.element.as_ref().style(), &mut self.callbacks, name, value, true);
self
}
#[inline]
#[track_caller]
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
}
#[inline]
#[track_caller]
pub fn focused(mut self, value: bool) -> Self {
let element = self.element.as_ref().clone();
self.callbacks.after_insert(move |_| {
if value {
bindings::focus(&element);
} else {
bindings::blur(&element);
}
});
self
}
fn set_focused_signal<B>(&mut self, value: B)
where B: Signal<Item = bool> + 'static {
let element = self.element.as_ref().clone();
self.callbacks.after_insert(move |callbacks| {
callbacks.after_remove(for_each(value, move |value| {
if value {
bindings::focus(&element);
} else {
bindings::blur(&element);
}
}));
});
}
#[inline]
#[track_caller]
pub fn focused_signal<B>(mut self, value: B) -> Self
where B: Signal<Item = bool> + 'static {
self.set_focused_signal(value);
self
}
}
#[inline]
pub fn stylesheet_raw<A>(css: A) where A: AsStr {
css.with_str(|css| {
bindings::create_stylesheet(Some(css));
});
}
#[must_use]
pub struct StylesheetBuilder {
element: CssStyleDeclaration,
callbacks: Callbacks,
}
impl StylesheetBuilder {
fn __internal_rules<A>(rules: &A) -> CssRule where A: MultiStr {
thread_local! {
static STYLESHEET: CssStyleSheet = bindings::create_stylesheet(None);
}
STYLESHEET.with(move |stylesheet| {
let mut failed = vec![];
let okay = rules.find_map(|rule| {
if let Ok(declaration) = bindings::make_rule(stylesheet, rule) {
Some(declaration)
} else {
failed.push(String::from(rule));
None
}
});
if let Some(okay) = okay {
okay
} else {
panic!("selectors are incorrect:\n {}", failed.join("\n "));
}
})
}
#[doc(hidden)]
#[inline]
pub fn __internal_stylesheet<A>(rules: A) -> Self where A: MultiStr {
let element = Self::__internal_rules(&rules).unchecked_into::<CssStyleRule>();
Self {
element: element.style(),
callbacks: Callbacks::new(),
}
}
#[doc(hidden)]
#[inline]
pub fn __internal_new<A>(rules: A) -> Self where A: MultiStr {
Self::__internal_stylesheet(MapMultiStr::new(rules, |rule| format!("{} {{}}", rule)))
}
#[inline]
#[track_caller]
pub fn style<B, C>(self, name: B, value: C) -> Self
where B: MultiStr,
C: MultiStr {
set_style(&self.element, &name, value, false);
self
}
#[inline]
#[track_caller]
pub fn style_important<B, C>(self, name: B, value: C) -> Self
where B: MultiStr,
C: MultiStr {
set_style(&self.element, &name, value, true);
self
}
#[inline]
#[track_caller]
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
}
#[inline]
#[track_caller]
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>,
E: Signal<Item = D> + 'static {
set_style_signal(self.element.clone(), &mut self.callbacks, name, value, false);
self
}
#[inline]
#[track_caller]
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>,
E: Signal<Item = D> + 'static {
set_style_signal(self.element.clone(), &mut self.callbacks, name, value, true);
self
}
#[inline]
#[track_caller]
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
}
#[inline]
#[track_caller]
pub fn raw<B>(self, css: B) -> Self where B: AsStr {
css.with_str(|css| {
bindings::append_raw(&self.element, css);
});
self
}
#[inline]
#[track_caller]
#[doc(hidden)]
pub fn __internal_done(mut self) {
self.callbacks.trigger_after_insert();
self.callbacks.leak();
}
}
#[must_use]
pub struct ClassBuilder {
stylesheet: StylesheetBuilder,
class_name: String,
}
impl ClassBuilder {
#[doc(hidden)]
#[inline]
#[track_caller]
pub fn __internal_new(name: Option<&str>) -> Self {
let class_name = __internal::make_class_id(name);
Self {
stylesheet: StylesheetBuilder::__internal_stylesheet(&format!(".{} {{}}", class_name)),
class_name,
}
}
#[doc(hidden)]
#[inline]
#[track_caller]
pub fn __internal_class_name(&self) -> &str {
&self.class_name
}
#[inline]
#[track_caller]
pub fn style<B, C>(mut self, name: B, value: C) -> Self
where B: MultiStr,
C: MultiStr {
self.stylesheet = self.stylesheet.style(name, value);
self
}
#[inline]
#[track_caller]
pub fn style_important<B, C>(mut self, name: B, value: C) -> Self
where B: MultiStr,
C: MultiStr {
self.stylesheet = self.stylesheet.style_important(name, value);
self
}
#[inline]
#[track_caller]
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
}
#[inline]
#[track_caller]
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>,
E: Signal<Item = D> + 'static {
self.stylesheet = self.stylesheet.style_signal(name, value);
self
}
#[inline]
#[track_caller]
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>,
E: Signal<Item = D> + 'static {
self.stylesheet = self.stylesheet.style_important_signal(name, value);
self
}
#[inline]
#[track_caller]
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
}
#[inline]
#[track_caller]
pub fn raw<B>(mut self, css: B) -> Self where B: AsStr {
self.stylesheet = self.stylesheet.raw(css);
self
}
#[doc(hidden)]
#[inline]
#[track_caller]
pub fn __internal_done(self) -> String {
self.stylesheet.__internal_done();
self.class_name
}
}
#[doc(hidden)]
pub mod __internal {
use std::sync::atomic::{AtomicU32, Ordering};
use crate::fragment::{Fragment, FragmentBuilder, BoxFragment};
use crate::traits::MultiStr;
pub use web_sys::HtmlElement;
pub use web_sys::SvgElement;
pub fn make_class_id(name: Option<&str>) -> String {
static CLASS_ID: AtomicU32 = AtomicU32::new(0);
let id = CLASS_ID.fetch_add(1, Ordering::Relaxed);
let name = name.unwrap_or("__class_");
format!("{}_{}", name, 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))
})
}
}
#[derive(Debug)]
struct FnFragment<F>(F);
impl<F> Fragment for FnFragment<F> where F: Fn(FragmentBuilder<'_>) -> FragmentBuilder<'_> {
#[inline]
fn apply<'a>(&self, dom: FragmentBuilder<'a>) -> FragmentBuilder<'a> {
(self.0)(dom)
}
}
#[inline]
pub fn fragment<F>(f: F) -> impl Fragment where F: Fn(FragmentBuilder<'_>) -> FragmentBuilder<'_> {
FnFragment(f)
}
#[inline]
pub fn box_fragment<F>(f: F) -> BoxFragment where F: Fn(FragmentBuilder<'_>) -> FragmentBuilder<'_> + Send + Sync + 'static {
Box::new(FnFragment(f))
}
}
#[cfg(test)]
mod tests {
use super::{DomBuilder, text_signal, RefFn};
use crate::{html, shadow_root, ShadowRootMode, with_cfg};
use futures_signals::signal::{always, SignalExt};
use once_cell::sync::Lazy;
use web_sys::HtmlElement;
#[test]
fn apply() {
let a: DomBuilder<HtmlElement> = DomBuilder::new_html("div");
fn my_mixin<A: AsRef<HtmlElement>>(builder: DomBuilder<A>) -> DomBuilder<A> {
builder.style("foo", "bar")
}
let _ = a.apply(my_mixin);
}
#[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()
}));
}
#[test]
fn text_signal_types() {
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())));
}
#[test]
fn property_signal_types() {
let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
.prop("foo", "hi")
.prop("foo", 5)
.prop(["foo", "-webkit-foo", "-ms-foo"], "hi")
.prop_signal("foo", always("hi"))
.prop_signal("foo", always(5))
.prop_signal("foo", always(Some("hi")))
.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")))
;
}
#[test]
fn attribute_signal_types() {
let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
.attr("foo", "hi")
.attr(["foo", "-webkit-foo", "-ms-foo"], "hi")
.attr_signal("foo", always("hi"))
.attr_signal("foo", always(Some("hi")))
.attr_signal(["foo", "-webkit-foo", "-ms-foo"], always("hi"))
.attr_signal(["foo", "-webkit-foo", "-ms-foo"], always(Some("hi")))
;
}
#[test]
fn class_signal_types() {
let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
.class("foo")
.class(["foo", "-webkit-foo", "-ms-foo"])
.class_signal("foo", always(true))
.class_signal(["foo", "-webkit-foo", "-ms-foo"], always(true))
;
}
#[test]
fn style_signal_types() {
static FOO: Lazy<String> = Lazy::new(|| "foo".to_owned());
let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
.style_signal("foo", always("bar"))
.style_signal("foo", always("bar".to_owned()))
.style_signal("foo", always("bar".to_owned()).map(|x| RefFn::new(x, |x| x.as_str())))
.style("foo".to_owned(), "bar".to_owned())
.style_signal("foo".to_owned(), always("bar".to_owned()))
.style(&"foo".to_owned(), &"bar".to_owned())
.style_signal(&*FOO, always(&*FOO))
.style_signal(RefFn::new(vec!["-moz-foo", "-webkit-foo", "foo"], |x| x.as_slice()), always(RefFn::new(vec!["bar"], |x| x.as_slice())))
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar"))
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar".to_owned()))
.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()]))
.style_signal("foo", always(Some("bar")))
.style_signal("foo", always(Some("bar".to_owned())))
.style_signal("foo", always("bar".to_owned()).map(|x| Some(RefFn::new(x, |x| x.as_str()))))
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always(Some("bar")))
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always(Some("bar".to_owned())))
.style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar".to_owned()).map(|x| Some(RefFn::new(x, |x| x.as_str()))))
;
}
#[test]
fn shadow_root() {
let _a = html!("div", {
.shadow_root!(ShadowRootMode::Closed => {
.children(&mut [
html!("span")
])
})
});
}
#[test]
fn with_cfg() {
let _a = html!("div", {
.with_cfg!(target_arch = "wasm32", {
.attr("foo", "bar")
})
.with_cfg!(all(not(foo), bar = "test", feature = "hi"), {
.attr("foo", "bar")
})
});
}
}