1use std::borrow::BorrowMut;
2use std::convert::AsRef;
3use std::future::Future;
4use std::pin::Pin;
5use std::task::{Context, Poll};
6
7use discard::{Discard, DiscardOnDrop};
8use futures_channel::oneshot;
9use futures_signals::signal::{not, Signal};
10use futures_signals::signal_vec::SignalVec;
11use futures_util::FutureExt;
12use once_cell::sync::Lazy;
13use wasm_bindgen::{intern, JsCast, JsValue, UnwrapThrowExt};
14use web_sys::{
15 CssStyleDeclaration, CssStyleSheet, Element, EventTarget, HtmlElement, Node, ShadowRoot,
16 ShadowRootInit, ShadowRootMode, Text,
17};
18
19use crate::bindings;
20use crate::callbacks::Callbacks;
21use crate::operations;
22use crate::operations::{for_each, spawn_future};
23use crate::traits::*;
24use crate::utils::{on, EventListener, FnDiscard, ValueDiscard};
25
26pub struct RefFn<A, B, C>
27where
28 B: ?Sized,
29 C: Fn(&A) -> &B,
30{
31 value: A,
32 callback: C,
33}
34
35impl<A, B, C> RefFn<A, B, C>
36where
37 B: ?Sized,
38 C: Fn(&A) -> &B,
39{
40 #[inline]
41 pub fn new(value: A, callback: C) -> Self {
42 Self { value, callback }
43 }
44
45 #[inline]
46 pub fn call_ref(&self) -> &B {
47 (self.callback)(&self.value)
48 }
49
50 }
62
63const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
81
82pub const HIGHEST_ZINDEX: &str = "2147483647";
84
85static HIDDEN_CLASS: Lazy<String> = Lazy::new(|| {
86 class! {
87 .style_important("display", "none")
88 }
89});
90
91pub fn body() -> HtmlElement {
93 bindings::body()
94}
95
96pub fn get_id(id: &str) -> Element {
97 bindings::get_element_by_id(id)
99}
100
101pub struct DomHandle {
102 parent: Node,
103 dom: Dom,
104}
105
106impl Discard for DomHandle {
107 #[inline]
108 fn discard(self) {
109 bindings::remove_child(&self.parent, &self.dom.element);
110 self.dom.callbacks.discard();
111 }
112}
113
114#[inline]
115pub fn append_dom(parent: &Node, mut dom: Dom) -> DomHandle {
116 bindings::append_child(&parent, &dom.element);
117
118 dom.callbacks.trigger_after_insert();
119
120 dom.callbacks.leak();
122
123 DomHandle {
124 parent: parent.clone(),
125 dom,
126 }
127}
128
129enum IsWindowLoaded {
131 Initial {},
132 Pending {
133 receiver: oneshot::Receiver<Option<bool>>,
134 _event: EventListener,
135 },
136 Done {},
137}
138
139impl Signal for IsWindowLoaded {
140 type Item = bool;
141
142 fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
143 let result = match *self {
144 IsWindowLoaded::Initial {} => {
145 let is_ready = bindings::ready_state() == "complete";
146
147 if is_ready {
148 Poll::Ready(Some(true))
149 } else {
150 let (sender, receiver) = oneshot::channel();
151
152 *self = IsWindowLoaded::Pending {
153 receiver,
154 _event: EventListener::once(
155 bindings::window_event_target(),
156 "load",
157 move |_| {
158 sender.send(Some(true)).unwrap_throw();
160 },
161 ),
162 };
163
164 Poll::Ready(Some(false))
165 }
166 }
167 IsWindowLoaded::Pending {
168 ref mut receiver, ..
169 } => receiver.poll_unpin(cx).map(|x| x.unwrap_throw()),
170 IsWindowLoaded::Done {} => Poll::Ready(None),
171 };
172
173 if let Poll::Ready(Some(true)) = result {
174 *self = IsWindowLoaded::Done {};
175 }
176
177 result
178 }
179}
180
181#[inline]
183pub fn is_window_loaded() -> impl Signal<Item = bool> {
184 IsWindowLoaded::Initial {}
185}
186
187#[inline]
189pub fn text(value: &str) -> Dom {
190 Dom::new(bindings::create_text_node(value).into())
191}
192
193fn make_text_signal<A, B>(callbacks: &mut Callbacks, value: B) -> Text
194where
195 A: AsStr,
196 B: Signal<Item = A> + 'static,
197{
198 let element = bindings::create_text_node(intern(""));
199
200 {
201 let element = element.clone();
202
203 callbacks.after_remove(for_each(value, move |value| {
204 value.with_str(|value| {
205 bindings::set_text(&element, value);
207 });
208 }));
209 }
210
211 element
212}
213
214pub fn text_signal<A, B>(value: B) -> Dom
216where
217 A: AsStr,
218 B: Signal<Item = A> + 'static,
219{
220 let mut callbacks = Callbacks::new();
221
222 let element = make_text_signal(&mut callbacks, value);
223
224 Dom {
225 element: element.into(),
226 callbacks: callbacks,
227 }
228}
229
230#[must_use]
232#[derive(Debug)]
233pub struct Dom {
234 pub(crate) element: Node,
235 pub(crate) callbacks: Callbacks,
236}
237
238impl Dom {
239 #[inline]
240 pub fn new(element: Node) -> Self {
241 Self {
242 element,
243 callbacks: Callbacks::new(),
244 }
245 }
246
247 #[inline]
248 pub fn empty() -> Self {
249 Self::new(bindings::create_empty_node())
250 }
251
252 #[deprecated(
253 since = "0.5.15",
254 note = "Store the data explicitly in a component struct instead"
255 )]
256 #[inline]
257 pub fn with_state<A, F>(mut state: A, initializer: F) -> Dom
258 where
259 A: 'static,
260 F: FnOnce(&mut A) -> Dom,
261 {
262 let mut dom = initializer(&mut state);
263
264 dom.callbacks.after_remove(ValueDiscard::new(state));
265
266 dom
267 }
268}
269
270#[inline]
271fn create_element<A>(name: &str) -> A
272where
273 A: JsCast,
274{
275 bindings::create_element(intern(name))
277 .dyn_into()
278 .unwrap_throw()
279}
280
281#[inline]
282fn create_element_ns<A>(name: &str, namespace: &str) -> A
283where
284 A: JsCast,
285{
286 bindings::create_element_ns(intern(namespace), intern(name))
288 .dyn_into()
289 .unwrap_throw()
290}
291
292fn set_option<A, B, C, D, F>(element: A, callbacks: &mut Callbacks, value: D, mut f: F)
294where
295 A: 'static,
296 C: OptionStr<Output = B>,
297 D: Signal<Item = C> + 'static,
298 F: FnMut(&A, Option<B>) + 'static,
299{
300 let mut is_set = false;
301
302 callbacks.after_remove(for_each(value, move |value| {
303 let value = value.into_option();
304
305 if value.is_some() {
306 is_set = true;
307 } else if is_set {
308 is_set = false;
309 } else {
310 return;
311 }
312
313 f(&element, value);
314 }));
315}
316
317fn set_style<A, B>(style: &CssStyleDeclaration, name: &A, value: B, important: bool)
319where
320 A: MultiStr,
321 B: MultiStr,
322{
323 let mut names = vec![];
324 let mut values = vec![];
325
326 fn try_set_style(
327 style: &CssStyleDeclaration,
328 names: &mut Vec<String>,
329 values: &mut Vec<String>,
330 name: &str,
331 value: &str,
332 important: bool,
333 ) -> Option<()> {
334 assert!(value != "");
335
336 bindings::remove_style(style, name);
338
339 bindings::set_style(style, name, value, important);
340
341 let is_changed = bindings::get_style(style, name) != "";
342
343 if is_changed {
344 Some(())
345 } else {
346 names.push(String::from(name));
347 values.push(String::from(value));
348 None
349 }
350 }
351
352 let okay = name.find_map(|name| {
353 let name: &str = intern(name);
354
355 value.find_map(|value| {
356 try_set_style(style, &mut names, &mut values, &name, &value, important)
358 })
359 });
360
361 if let None = okay {
362 if cfg!(debug_assertions) {
363 panic!(
365 "style is incorrect:\n names: {}\n values: {}",
366 names.join(", "),
367 values.join(", ")
368 );
369 }
370 }
371}
372
373fn set_style_signal<A, B, C, D>(
375 style: CssStyleDeclaration,
376 callbacks: &mut Callbacks,
377 name: A,
378 value: D,
379 important: bool,
380) where
381 A: MultiStr + 'static,
382 B: MultiStr,
383 C: OptionStr<Output = B>,
384 D: Signal<Item = C> + 'static,
385{
386 set_option(style, callbacks, value, move |style, value| {
387 match value {
388 Some(value) => {
389 set_style(style, &name, value, important);
391 }
392 None => {
393 name.each(|name| {
394 bindings::remove_style(style, intern(name));
396 });
397 }
398 }
399 });
400}
401
402fn set_style_unchecked_signal<A, B, C, D>(
404 style: CssStyleDeclaration,
405 callbacks: &mut Callbacks,
406 name: A,
407 value: D,
408 important: bool,
409) where
410 A: AsStr + 'static,
411 B: AsStr,
412 C: OptionStr<Output = B>,
413 D: Signal<Item = C> + 'static,
414{
415 set_option(style, callbacks, value, move |style, value| match value {
416 Some(value) => {
417 name.with_str(|name| {
418 let name: &str = intern(name);
419
420 value.with_str(|value| {
421 bindings::set_style(style, name, value, important);
422 });
423 });
424 }
425 None => {
426 name.with_str(|name| {
427 bindings::remove_style(style, intern(name));
428 });
429 }
430 });
431}
432
433fn set_property<A, B, C>(element: &A, name: &B, value: C)
437where
438 A: AsRef<JsValue>,
439 B: MultiStr,
440 C: Into<JsValue>,
441{
442 let element = element.as_ref();
443 let value = value.into();
444
445 name.each(|name| {
446 bindings::set_property(element, intern(name), &value);
447 });
448}
449
450#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
451pub struct EventOptions {
452 pub bubbles: bool,
453 pub preventable: bool,
454}
455
456impl EventOptions {
457 pub fn bubbles() -> Self {
458 Self {
459 bubbles: true,
460 preventable: false,
461 }
462 }
463
464 pub fn preventable() -> Self {
465 Self {
466 bubbles: false,
467 preventable: true,
468 }
469 }
470}
471
472impl Default for EventOptions {
473 fn default() -> Self {
474 Self {
475 bubbles: false,
476 preventable: false,
477 }
478 }
479}
480
481#[must_use]
483pub struct DomBuilder<A> {
484 element: A,
485 callbacks: Callbacks,
486}
487
488impl<A> DomBuilder<A>
489where
490 A: JsCast,
491{
492 #[inline]
493 pub fn new_html(name: &str) -> Self {
494 Self::new(create_element(name))
495 }
496
497 #[inline]
498 pub fn new_svg(name: &str) -> Self {
499 Self::new(create_element_ns(name, SVG_NAMESPACE))
500 }
501}
502
503impl<A> DomBuilder<A> {
504 #[inline]
505 #[doc(hidden)]
506 pub fn __internal_transfer_callbacks<B>(mut self, mut shadow: DomBuilder<B>) -> Self {
507 self.callbacks
508 .after_insert
509 .append(&mut shadow.callbacks.after_insert);
510 self.callbacks
511 .after_remove
512 .append(&mut shadow.callbacks.after_remove);
513 self
514 }
515
516 #[inline]
517 pub fn new(value: A) -> Self {
518 Self {
519 element: value,
520 callbacks: Callbacks::new(),
521 }
522 }
523
524 #[inline]
525 fn _event<T, F>(&mut self, element: EventTarget, options: &EventOptions, listener: F)
526 where
527 T: StaticEvent,
528 F: FnMut(T) + 'static,
529 {
530 self.callbacks.after_remove(on(element, options, listener));
531 }
532
533 #[inline]
535 pub fn global_event_with_options<T, F>(mut self, options: &EventOptions, listener: F) -> Self
536 where
537 T: StaticEvent,
538 F: FnMut(T) + 'static,
539 {
540 self._event(bindings::window_event_target(), options, listener);
541 self
542 }
543
544 #[inline]
546 pub fn global_event<T, F>(self, listener: F) -> Self
547 where
548 T: StaticEvent,
549 F: FnMut(T) + 'static,
550 {
551 self.global_event_with_options(&EventOptions::default(), listener)
552 }
553
554 #[deprecated(since = "0.5.21", note = "Use global_event_with_options instead")]
556 #[inline]
557 pub fn global_event_preventable<T, F>(self, listener: F) -> Self
558 where
559 T: StaticEvent,
560 F: FnMut(T) + 'static,
561 {
562 self.global_event_with_options(&EventOptions::preventable(), listener)
563 }
564
565 #[inline]
566 pub fn future<F>(mut self, future: F) -> Self
567 where
568 F: Future<Output = ()> + 'static,
569 {
570 self.callbacks
571 .after_remove(DiscardOnDrop::leak(spawn_future(future)));
572 self
573 }
574
575 #[inline]
576 pub fn apply<F>(self, f: F) -> Self
577 where
578 F: FnOnce(Self) -> Self,
579 {
580 f(self)
581 }
582
583 #[inline]
584 pub fn apply_if<F>(self, test: bool, f: F) -> Self
585 where
586 F: FnOnce(Self) -> Self,
587 {
588 if test {
589 f(self)
590 } else {
591 self
592 }
593 }
594}
595
596impl<A> DomBuilder<A>
597where
598 A: Clone,
599{
600 #[inline]
601 #[doc(hidden)]
602 pub fn __internal_element(&self) -> A {
603 self.element.clone()
604 }
605
606 #[deprecated(since = "0.5.1", note = "Use the with_node macro instead")]
607 #[inline]
608 pub fn with_element<B, F>(self, f: F) -> B
609 where
610 F: FnOnce(Self, A) -> B,
611 {
612 let element = self.element.clone();
613 f(self, element)
614 }
615
616 #[deprecated(since = "0.5.20", note = "Use the with_node macro instead")]
617 #[inline]
618 pub fn before_inserted<F>(self, f: F) -> Self
619 where
620 F: FnOnce(A),
621 {
622 let element = self.element.clone();
623 f(element);
624 self
625 }
626}
627
628impl<A> DomBuilder<A>
629where
630 A: Clone + 'static,
631{
632 #[inline]
633 pub fn after_inserted<F>(mut self, f: F) -> Self
634 where
635 F: FnOnce(A) + 'static,
636 {
637 let element = self.element.clone();
638 self.callbacks.after_insert(move |_| f(element));
639 self
640 }
641
642 #[inline]
643 pub fn after_removed<F>(mut self, f: F) -> Self
644 where
645 F: FnOnce(A) + 'static,
646 {
647 let element = self.element.clone();
648 self.callbacks
649 .after_remove(FnDiscard::new(move || f(element)));
650 self
651 }
652}
653
654impl<A> DomBuilder<A>
655where
656 A: Into<Node>,
657{
658 #[inline]
659 pub fn into_dom(self) -> Dom {
660 Dom {
661 element: self.element.into(),
662 callbacks: self.callbacks,
663 }
664 }
665}
666
667impl<A> DomBuilder<A>
668where
669 A: AsRef<JsValue>,
670{
671 #[inline]
672 pub fn prop<B, C>(self, name: B, value: C) -> Self
673 where
674 B: MultiStr,
675 C: Into<JsValue>,
676 {
677 self.property(name, value)
678 }
679
680 #[inline]
682 pub fn property<B, C>(self, name: B, value: C) -> Self
683 where
684 B: MultiStr,
685 C: Into<JsValue>,
686 {
687 set_property(&self.element, &name, value);
688 self
689 }
690}
691
692impl<A> DomBuilder<A>
693where
694 A: AsRef<JsValue>,
695{
696 fn set_property_signal<B, C, D>(&mut self, name: B, value: D)
698 where
699 B: MultiStr + 'static,
700 C: Into<JsValue>,
701 D: Signal<Item = C> + 'static,
702 {
703 let element = self.element.as_ref().clone();
704
705 self.callbacks.after_remove(for_each(value, move |value| {
706 set_property(&element, &name, value);
707 }));
708 }
709
710 #[inline]
711 pub fn prop_signal<B, C, D>(self, name: B, value: D) -> Self
712 where
713 B: MultiStr + 'static,
714 C: Into<JsValue>,
715 D: Signal<Item = C> + 'static,
716 {
717 self.property_signal(name, value)
718 }
719
720 #[inline]
722 pub fn property_signal<B, C, D>(mut self, name: B, value: D) -> Self
723 where
724 B: MultiStr + 'static,
725 C: Into<JsValue>,
726 D: Signal<Item = C> + 'static,
727 {
728 self.set_property_signal(name, value);
729 self
730 }
731}
732
733impl<A> DomBuilder<A>
734where
735 A: AsRef<EventTarget>,
736{
737 #[inline]
738 pub fn event_with_options<T, F>(mut self, options: &EventOptions, listener: F) -> Self
739 where
740 T: StaticEvent,
741 F: FnMut(T) + 'static,
742 {
743 self._event(self.element.as_ref().clone(), options, listener);
745 self
746 }
747
748 #[inline]
749 pub fn event<T, F>(self, listener: F) -> Self
750 where
751 T: StaticEvent,
752 F: FnMut(T) + 'static,
753 {
754 self.event_with_options(&EventOptions::default(), listener)
755 }
756
757 #[deprecated(since = "0.5.21", note = "Use event_with_options instead")]
758 #[inline]
759 pub fn event_preventable<T, F>(self, listener: F) -> Self
760 where
761 T: StaticEvent,
762 F: FnMut(T) + 'static,
763 {
764 self.event_with_options(&EventOptions::preventable(), listener)
765 }
766}
767
768impl<A> DomBuilder<A>
769where
770 A: AsRef<Node>,
771{
772 #[inline]
773 pub fn text(self, value: &str) -> Self {
774 bindings::append_child(self.element.as_ref(), &bindings::create_text_node(value));
776 self
777 }
778
779 #[inline]
780 pub fn text_signal<B, C>(mut self, value: C) -> Self
781 where
782 B: AsStr,
783 C: Signal<Item = B> + 'static,
784 {
785 let element = make_text_signal(&mut self.callbacks, value);
786 bindings::append_child(self.element.as_ref(), &element);
787 self
788 }
789
790 #[inline]
791 pub fn child<B: BorrowMut<Dom>>(mut self, mut child: B) -> Self {
792 operations::insert_children_one(
793 self.element.as_ref(),
794 &mut self.callbacks,
795 child.borrow_mut(),
796 );
797 self
798 }
799
800 #[inline]
801 pub fn child_signal<B>(mut self, child: B) -> Self
802 where
803 B: Signal<Item = Option<Dom>> + 'static,
804 {
805 operations::insert_child_signal(self.element.as_ref().clone(), &mut self.callbacks, child);
806 self
807 }
808
809 #[inline]
811 pub fn children<B: BorrowMut<Dom>, C: IntoIterator<Item = B>>(mut self, children: C) -> Self {
812 operations::insert_children_iter(self.element.as_ref(), &mut self.callbacks, children);
813 self
814 }
815
816 #[inline]
817 pub fn children_signal_vec<B>(mut self, children: B) -> Self
818 where
819 B: SignalVec<Item = Dom> + 'static,
820 {
821 operations::insert_children_signal_vec(
822 self.element.as_ref().clone(),
823 &mut self.callbacks,
824 children,
825 );
826 self
827 }
828}
829
830impl<A> DomBuilder<A>
831where
832 A: AsRef<Element>,
833{
834 #[inline]
835 #[doc(hidden)]
836 pub fn __internal_shadow_root(&self, mode: ShadowRootMode) -> DomBuilder<ShadowRoot> {
837 let shadow = self
838 .element
839 .as_ref()
840 .attach_shadow(&ShadowRootInit::new(mode))
841 .unwrap_throw();
842 DomBuilder::new(shadow)
843 }
844
845 #[inline]
846 pub fn attr<B>(self, name: B, value: &str) -> Self
847 where
848 B: MultiStr,
849 {
850 self.attribute(name, value)
851 }
852
853 #[inline]
855 pub fn attribute<B>(self, name: B, value: &str) -> Self
856 where
857 B: MultiStr,
858 {
859 let element = self.element.as_ref();
860 let value: &str = intern(value);
862
863 name.each(|name| {
864 bindings::set_attribute(element, intern(name), &value);
865 });
866
867 self
868 }
869
870 #[inline]
871 pub fn attr_ns<B>(self, namespace: &str, name: B, value: &str) -> Self
872 where
873 B: MultiStr,
874 {
875 self.attribute_namespace(namespace, name, value)
876 }
877
878 #[inline]
880 pub fn attribute_namespace<B>(self, namespace: &str, name: B, value: &str) -> Self
881 where
882 B: MultiStr,
883 {
884 let element = self.element.as_ref();
885 let namespace: &str = intern(namespace);
886 let value: &str = intern(value);
888
889 name.each(|name| {
890 bindings::set_attribute_ns(element, &namespace, intern(name), &value);
891 });
892
893 self
894 }
895
896 #[inline]
897 pub fn class<B>(self, name: B) -> Self
898 where
899 B: MultiStr,
900 {
901 let classes = self.element.as_ref().class_list();
902
903 name.each(|name| {
904 bindings::add_class(&classes, intern(name));
905 });
906
907 self
908 }
909
910 #[inline]
912 pub fn visible(self, value: bool) -> Self {
913 if value {
914 self
916 } else {
917 self.class(&*HIDDEN_CLASS)
918 }
919 }
920}
921
922impl<A> DomBuilder<A>
923where
924 A: AsRef<Element>,
925{
926 fn set_attribute_signal<B, C, D, E>(&mut self, name: B, value: E)
928 where
929 B: MultiStr + 'static,
930 C: AsStr,
931 D: OptionStr<Output = C>,
932 E: Signal<Item = D> + 'static,
933 {
934 set_option(
935 self.element.as_ref().clone(),
936 &mut self.callbacks,
937 value,
938 move |element, value| {
939 match value {
940 Some(value) => {
941 value.with_str(|value| {
942 name.each(|name| {
943 bindings::set_attribute(element, intern(name), &value);
945 });
946 });
947 }
948 None => {
949 name.each(|name| {
950 bindings::remove_attribute(element, intern(name));
951 });
952 }
953 }
954 },
955 );
956 }
957
958 #[inline]
959 pub fn attr_signal<B, C, D, E>(self, name: B, value: E) -> Self
960 where
961 B: MultiStr + 'static,
962 C: AsStr,
963 D: OptionStr<Output = C>,
964 E: Signal<Item = D> + 'static,
965 {
966 self.attribute_signal(name, value)
967 }
968
969 #[inline]
971 pub fn attribute_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
972 where
973 B: MultiStr + 'static,
974 C: AsStr,
975 D: OptionStr<Output = C>,
976 E: Signal<Item = D> + 'static,
977 {
978 self.set_attribute_signal(name, value);
979 self
980 }
981
982 fn set_attribute_namespace_signal<B, C, D, E>(&mut self, namespace: &str, name: B, value: E)
984 where
985 B: MultiStr + 'static,
986 C: AsStr,
987 D: OptionStr<Output = C>,
988 E: Signal<Item = D> + 'static,
989 {
990 let namespace: String = intern(namespace).to_owned();
992
993 set_option(
994 self.element.as_ref().clone(),
995 &mut self.callbacks,
996 value,
997 move |element, value| {
998 match value {
999 Some(value) => {
1000 value.with_str(|value| {
1001 name.each(|name| {
1002 bindings::set_attribute_ns(
1004 element,
1005 &namespace,
1006 intern(name),
1007 &value,
1008 );
1009 });
1010 });
1011 }
1012 None => {
1013 name.each(|name| {
1014 bindings::remove_attribute_ns(element, &namespace, intern(name));
1015 });
1016 }
1017 }
1018 },
1019 );
1020 }
1021
1022 #[inline]
1023 pub fn attr_ns_signal<B, C, D, E>(self, namespace: &str, name: B, value: E) -> Self
1024 where
1025 B: MultiStr + 'static,
1026 C: AsStr,
1027 D: OptionStr<Output = C>,
1028 E: Signal<Item = D> + 'static,
1029 {
1030 self.attribute_namespace_signal(namespace, name, value)
1031 }
1032
1033 #[inline]
1035 pub fn attribute_namespace_signal<B, C, D, E>(
1036 mut self,
1037 namespace: &str,
1038 name: B,
1039 value: E,
1040 ) -> Self
1041 where
1042 B: MultiStr + 'static,
1043 C: AsStr,
1044 D: OptionStr<Output = C>,
1045 E: Signal<Item = D> + 'static,
1046 {
1047 self.set_attribute_namespace_signal(namespace, name, value);
1048 self
1049 }
1050
1051 fn set_class_signal<B, C>(&mut self, name: B, value: C)
1053 where
1054 B: MultiStr + 'static,
1055 C: Signal<Item = bool> + 'static,
1056 {
1057 let element = self.element.as_ref().class_list();
1058
1059 let mut is_set = false;
1060
1061 self.callbacks.after_remove(for_each(value, move |value| {
1062 if value {
1063 if !is_set {
1064 is_set = true;
1065
1066 name.each(|name| {
1067 bindings::add_class(&element, intern(name));
1068 });
1069 }
1070 } else {
1071 if is_set {
1072 is_set = false;
1073
1074 name.each(|name| {
1075 bindings::remove_class(&element, intern(name));
1076 });
1077 }
1078 }
1079 }));
1080 }
1081
1082 #[inline]
1083 pub fn class_signal<B, C>(mut self, name: B, value: C) -> Self
1084 where
1085 B: MultiStr + 'static,
1086 C: Signal<Item = bool> + 'static,
1087 {
1088 self.set_class_signal(name, value);
1089 self
1090 }
1091
1092 #[inline]
1094 pub fn visible_signal<B>(self, value: B) -> Self
1095 where
1096 B: Signal<Item = bool> + 'static,
1097 {
1098 self.class_signal(&*HIDDEN_CLASS, not(value))
1099 }
1100
1101 fn set_scroll_signal<B, F>(&mut self, signal: B, mut f: F)
1104 where
1105 B: Signal<Item = Option<i32>> + 'static,
1106 F: FnMut(&Element, i32) + 'static,
1107 {
1108 let element: Element = self.element.as_ref().clone();
1109
1110 self.callbacks.after_insert(move |callbacks| {
1112 callbacks.after_remove(for_each(signal, move |value| {
1113 if let Some(value) = value {
1114 f(&element, value);
1115 }
1116 }));
1117 });
1118 }
1119
1120 #[inline]
1122 pub fn scroll_left_signal<B>(mut self, signal: B) -> Self
1123 where
1124 B: Signal<Item = Option<i32>> + 'static,
1125 {
1126 self.set_scroll_signal(signal, Element::set_scroll_left);
1128 self
1129 }
1130
1131 #[inline]
1133 pub fn scroll_top_signal<B>(mut self, signal: B) -> Self
1134 where
1135 B: Signal<Item = Option<i32>> + 'static,
1136 {
1137 self.set_scroll_signal(signal, Element::set_scroll_top);
1139 self
1140 }
1141}
1142
1143impl<A> DomBuilder<A>
1144where
1145 A: AsRef<HtmlElement>,
1146{
1147 #[inline]
1148 pub fn style<B, C>(self, name: B, value: C) -> Self
1149 where
1150 B: MultiStr,
1151 C: MultiStr,
1152 {
1153 set_style(&self.element.as_ref().style(), &name, value, false);
1154 self
1155 }
1156
1157 #[inline]
1158 pub fn style_important<B, C>(self, name: B, value: C) -> Self
1159 where
1160 B: MultiStr,
1161 C: MultiStr,
1162 {
1163 set_style(&self.element.as_ref().style(), &name, value, true);
1164 self
1165 }
1166
1167 #[inline]
1168 pub fn style_unchecked<B, C>(self, name: B, value: C) -> Self
1169 where
1170 B: AsStr,
1171 C: AsStr,
1172 {
1173 name.with_str(|name| {
1174 value.with_str(|value| {
1175 bindings::set_style(&self.element.as_ref().style(), intern(name), value, false);
1176 });
1177 });
1178 self
1179 }
1180}
1181
1182impl<A> DomBuilder<A>
1183where
1184 A: AsRef<HtmlElement>,
1185{
1186 #[inline]
1187 pub fn style_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1188 where
1189 B: MultiStr + 'static,
1190 C: MultiStr,
1191 D: OptionStr<Output = C>,
1192 E: Signal<Item = D> + 'static,
1193 {
1194 set_style_signal(
1195 self.element.as_ref().style(),
1196 &mut self.callbacks,
1197 name,
1198 value,
1199 false,
1200 );
1201 self
1202 }
1203
1204 #[inline]
1205 pub fn style_important_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1206 where
1207 B: MultiStr + 'static,
1208 C: MultiStr,
1209 D: OptionStr<Output = C>,
1210 E: Signal<Item = D> + 'static,
1211 {
1212 set_style_signal(
1213 self.element.as_ref().style(),
1214 &mut self.callbacks,
1215 name,
1216 value,
1217 true,
1218 );
1219 self
1220 }
1221
1222 #[inline]
1223 pub fn style_unchecked_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1224 where
1225 B: AsStr + 'static,
1226 C: AsStr,
1227 D: OptionStr<Output = C>,
1228 E: Signal<Item = D> + 'static,
1229 {
1230 set_style_unchecked_signal(
1231 self.element.as_ref().style(),
1232 &mut self.callbacks,
1233 name,
1234 value,
1235 false,
1236 );
1237 self
1238 }
1239
1240 #[inline]
1242 pub fn focused(mut self, value: bool) -> Self {
1243 let element = self.element.as_ref().clone();
1244
1245 self.callbacks.after_insert(move |_| {
1247 if value {
1249 bindings::focus(&element);
1250 } else {
1251 bindings::blur(&element);
1252 }
1253 });
1254
1255 self
1256 }
1257
1258 fn set_focused_signal<B>(&mut self, value: B)
1260 where
1261 B: Signal<Item = bool> + 'static,
1262 {
1263 let element = self.element.as_ref().clone();
1264
1265 self.callbacks.after_insert(move |callbacks| {
1267 callbacks.after_remove(for_each(value, move |value| {
1269 if value {
1271 bindings::focus(&element);
1272 } else {
1273 bindings::blur(&element);
1274 }
1275 }));
1276 });
1277 }
1278
1279 #[inline]
1280 pub fn focused_signal<B>(mut self, value: B) -> Self
1281 where
1282 B: Signal<Item = bool> + 'static,
1283 {
1284 self.set_focused_signal(value);
1285 self
1286 }
1287}
1288
1289#[must_use]
1291pub struct StylesheetBuilder {
1292 element: CssStyleDeclaration,
1293 callbacks: Callbacks,
1294}
1295
1296impl StylesheetBuilder {
1298 #[doc(hidden)]
1300 #[inline]
1301 pub fn __internal_new<A>(selector: A) -> Self
1302 where
1303 A: MultiStr,
1304 {
1305 thread_local! {
1308 static STYLESHEET: CssStyleSheet = bindings::create_stylesheet();
1309 }
1310
1311 fn try_make(
1312 stylesheet: &CssStyleSheet,
1313 selector: &str,
1314 selectors: &mut Vec<String>,
1315 ) -> Option<CssStyleDeclaration> {
1316 if let Ok(declaration) = bindings::make_style_rule(stylesheet, selector) {
1318 Some(declaration.style())
1319 } else {
1320 selectors.push(String::from(selector));
1321 None
1322 }
1323 }
1324
1325 let element = STYLESHEET.with(move |stylesheet| {
1326 let mut selectors = vec![];
1327
1328 let okay = selector.find_map(|selector| try_make(stylesheet, selector, &mut selectors));
1329
1330 if let Some(okay) = okay {
1331 okay
1332 } else {
1333 panic!("selectors are incorrect:\n {}", selectors.join("\n "));
1335 }
1336 });
1337
1338 Self {
1339 element,
1340 callbacks: Callbacks::new(),
1341 }
1342 }
1343
1344 #[inline]
1345 pub fn style<B, C>(self, name: B, value: C) -> Self
1346 where
1347 B: MultiStr,
1348 C: MultiStr,
1349 {
1350 set_style(&self.element, &name, value, false);
1351 self
1352 }
1353
1354 #[inline]
1355 pub fn style_important<B, C>(self, name: B, value: C) -> Self
1356 where
1357 B: MultiStr,
1358 C: MultiStr,
1359 {
1360 set_style(&self.element, &name, value, true);
1361 self
1362 }
1363
1364 #[inline]
1365 pub fn style_unchecked<B, C>(self, name: B, value: C) -> Self
1366 where
1367 B: AsStr,
1368 C: AsStr,
1369 {
1370 name.with_str(|name| {
1371 value.with_str(|value| {
1372 bindings::set_style(&self.element, intern(name), value, false);
1373 });
1374 });
1375 self
1376 }
1377
1378 #[inline]
1379 pub fn style_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1380 where
1381 B: MultiStr + 'static,
1382 C: MultiStr,
1383 D: OptionStr<Output = C>,
1384 E: Signal<Item = D> + 'static,
1385 {
1386 set_style_signal(
1387 self.element.clone(),
1388 &mut self.callbacks,
1389 name,
1390 value,
1391 false,
1392 );
1393 self
1394 }
1395
1396 #[inline]
1397 pub fn style_important_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1398 where
1399 B: MultiStr + 'static,
1400 C: MultiStr,
1401 D: OptionStr<Output = C>,
1402 E: Signal<Item = D> + 'static,
1403 {
1404 set_style_signal(self.element.clone(), &mut self.callbacks, name, value, true);
1405 self
1406 }
1407
1408 #[inline]
1409 pub fn style_unchecked_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1410 where
1411 B: AsStr + 'static,
1412 C: AsStr,
1413 D: OptionStr<Output = C>,
1414 E: Signal<Item = D> + 'static,
1415 {
1416 set_style_unchecked_signal(
1417 self.element.clone(),
1418 &mut self.callbacks,
1419 name,
1420 value,
1421 false,
1422 );
1423 self
1424 }
1425
1426 #[inline]
1428 #[doc(hidden)]
1429 pub fn __internal_done(mut self) {
1430 self.callbacks.trigger_after_insert();
1431
1432 self.callbacks.leak();
1434 }
1435}
1436
1437#[must_use]
1439pub struct ClassBuilder {
1440 stylesheet: StylesheetBuilder,
1441 class_name: String,
1442}
1443
1444impl ClassBuilder {
1445 #[doc(hidden)]
1446 #[inline]
1447 pub fn __internal_new() -> Self {
1448 let class_name = __internal::make_class_id();
1449
1450 Self {
1451 stylesheet: StylesheetBuilder::__internal_new(&format!(".{}", class_name)),
1453 class_name,
1454 }
1455 }
1456
1457 #[doc(hidden)]
1458 #[inline]
1459 pub fn __internal_class_name(&self) -> &str {
1460 &self.class_name
1461 }
1462
1463 #[inline]
1464 pub fn style<B, C>(mut self, name: B, value: C) -> Self
1465 where
1466 B: MultiStr,
1467 C: MultiStr,
1468 {
1469 self.stylesheet = self.stylesheet.style(name, value);
1470 self
1471 }
1472
1473 #[inline]
1474 pub fn style_important<B, C>(mut self, name: B, value: C) -> Self
1475 where
1476 B: MultiStr,
1477 C: MultiStr,
1478 {
1479 self.stylesheet = self.stylesheet.style_important(name, value);
1480 self
1481 }
1482
1483 #[inline]
1484 pub fn style_unchecked<B, C>(mut self, name: B, value: C) -> Self
1485 where
1486 B: AsStr,
1487 C: AsStr,
1488 {
1489 self.stylesheet = self.stylesheet.style_unchecked(name, value);
1490 self
1491 }
1492
1493 #[inline]
1494 pub fn style_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1495 where
1496 B: MultiStr + 'static,
1497 C: MultiStr,
1498 D: OptionStr<Output = C>,
1499 E: Signal<Item = D> + 'static,
1500 {
1501 self.stylesheet = self.stylesheet.style_signal(name, value);
1502 self
1503 }
1504
1505 #[inline]
1506 pub fn style_important_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1507 where
1508 B: MultiStr + 'static,
1509 C: MultiStr,
1510 D: OptionStr<Output = C>,
1511 E: Signal<Item = D> + 'static,
1512 {
1513 self.stylesheet = self.stylesheet.style_important_signal(name, value);
1514 self
1515 }
1516
1517 #[inline]
1518 pub fn style_unchecked_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1519 where
1520 B: AsStr + 'static,
1521 C: AsStr,
1522 D: OptionStr<Output = C>,
1523 E: Signal<Item = D> + 'static,
1524 {
1525 self.stylesheet = self.stylesheet.style_unchecked_signal(name, value);
1526 self
1527 }
1528
1529 #[doc(hidden)]
1531 #[inline]
1532 pub fn __internal_done(self) -> String {
1533 self.stylesheet.__internal_done();
1534 self.class_name
1535 }
1536}
1537
1538#[doc(hidden)]
1539pub mod __internal {
1540 use crate::traits::MultiStr;
1541 use std::sync::atomic::{AtomicU32, Ordering};
1542
1543 pub use web_sys::HtmlElement;
1544 pub use web_sys::SvgElement;
1545
1546 pub fn make_class_id() -> String {
1547 static CLASS_ID: AtomicU32 = AtomicU32::new(0);
1550
1551 let id = CLASS_ID.fetch_add(1, Ordering::Relaxed);
1554
1555 format!("__class_{}__", id)
1557 }
1558
1559 pub struct Pseudo<'a, A> {
1560 class_name: &'a str,
1561 pseudos: A,
1562 }
1563
1564 impl<'a, A> Pseudo<'a, A>
1565 where
1566 A: MultiStr,
1567 {
1568 #[inline]
1569 pub fn new(class_name: &'a str, pseudos: A) -> Self {
1570 Self {
1571 class_name,
1572 pseudos,
1573 }
1574 }
1575 }
1576
1577 impl<'a, A> MultiStr for Pseudo<'a, A>
1578 where
1579 A: MultiStr,
1580 {
1581 #[inline]
1582 fn find_map<B, F>(&self, mut f: F) -> Option<B>
1583 where
1584 F: FnMut(&str) -> Option<B>,
1585 {
1586 self.pseudos
1587 .find_map(|x| f(&format!(".{}{}", self.class_name, x)))
1588 }
1589 }
1590}
1591
1592#[cfg(test)]
1593mod tests {
1594 use super::{text_signal, DomBuilder, RefFn};
1595 use crate::{html, shadow_root, with_cfg, ShadowRootMode};
1596 use futures_signals::signal::{always, SignalExt};
1597 use once_cell::sync::Lazy;
1598 use web_sys::HtmlElement;
1599
1600 #[test]
1601 fn apply() {
1602 let a: DomBuilder<HtmlElement> = DomBuilder::new_html("div");
1603
1604 fn my_mixin<A: AsRef<HtmlElement>>(builder: DomBuilder<A>) -> DomBuilder<A> {
1605 builder.style("foo", "bar")
1606 }
1607
1608 let _ = a.apply(my_mixin);
1609 }
1610
1611 #[test]
1612 fn children_mut() {
1613 let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div").children(&mut [
1614 DomBuilder::<HtmlElement>::new_html("div").into_dom(),
1615 DomBuilder::<HtmlElement>::new_html("div").into_dom(),
1616 DomBuilder::<HtmlElement>::new_html("div").into_dom(),
1617 ]);
1618 }
1619
1620 #[test]
1621 fn children_value() {
1622 let v: Vec<u32> = vec![];
1623
1624 let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div").children(
1625 v.iter()
1626 .map(|_| DomBuilder::<HtmlElement>::new_html("div").into_dom()),
1627 );
1628 }
1629
1630 #[test]
1631 fn text_signal_types() {
1632 let _ = text_signal(always("foo"));
1633 let _ = text_signal(always("foo".to_owned()));
1634 let _ = text_signal(always("foo".to_owned()).map(|x| RefFn::new(x, |x| x.as_str())));
1635 }
1644
1645 #[test]
1646 fn property_signal_types() {
1647 let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
1648 .property("foo", "hi")
1649 .property("foo", 5)
1650 .property(["foo", "-webkit-foo", "-ms-foo"], "hi")
1651 .property_signal("foo", always("hi"))
1652 .property_signal("foo", always(5))
1653 .property_signal("foo", always(Some("hi")))
1654 .property_signal(["foo", "-webkit-foo", "-ms-foo"], always("hi"))
1655 .property_signal(["foo", "-webkit-foo", "-ms-foo"], always(5))
1656 .property_signal(["foo", "-webkit-foo", "-ms-foo"], always(Some("hi")));
1657 }
1658
1659 #[test]
1660 fn attribute_signal_types() {
1661 let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
1662 .attribute("foo", "hi")
1663 .attribute(["foo", "-webkit-foo", "-ms-foo"], "hi")
1664 .attribute_signal("foo", always("hi"))
1665 .attribute_signal("foo", always(Some("hi")))
1666 .attribute_signal(["foo", "-webkit-foo", "-ms-foo"], always("hi"))
1667 .attribute_signal(["foo", "-webkit-foo", "-ms-foo"], always(Some("hi")));
1668 }
1669
1670 #[test]
1671 fn class_signal_types() {
1672 let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
1673 .class("foo")
1674 .class(["foo", "-webkit-foo", "-ms-foo"])
1675 .class_signal("foo", always(true))
1676 .class_signal(["foo", "-webkit-foo", "-ms-foo"], always(true));
1677 }
1678
1679 #[test]
1680 fn style_signal_types() {
1681 static FOO: Lazy<String> = Lazy::new(|| "foo".to_owned());
1682
1683 let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
1684 .style_signal("foo", always("bar"))
1685 .style_signal("foo", always("bar".to_owned()))
1686 .style_signal(
1687 "foo",
1688 always("bar".to_owned()).map(|x| RefFn::new(x, |x| x.as_str())),
1689 )
1690 .style("foo".to_owned(), "bar".to_owned())
1691 .style_signal("foo".to_owned(), always("bar".to_owned()))
1692 .style(&"foo".to_owned(), &"bar".to_owned())
1693 .style_signal(&*FOO, always(&*FOO))
1696 .style_signal(
1698 RefFn::new(vec!["-moz-foo", "-webkit-foo", "foo"], |x| x.as_slice()),
1699 always(RefFn::new(vec!["bar"], |x| x.as_slice())),
1700 )
1701 .style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar"))
1702 .style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar".to_owned()))
1703 .style_signal(
1704 ["-moz-foo", "-webkit-foo", "foo"],
1705 always("bar".to_owned()).map(|x| RefFn::new(x, |x| x.as_str())),
1706 )
1707 .style_signal(["-moz-foo", "-webkit-foo", "foo"], always(["bar", "qux"]))
1708 .style_signal(
1709 ["-moz-foo", "-webkit-foo", "foo"],
1710 always(["bar".to_owned(), "qux".to_owned()]),
1711 )
1712 .style_signal("foo", always(Some("bar")))
1715 .style_signal("foo", always(Some("bar".to_owned())))
1716 .style_signal(
1717 "foo",
1718 always("bar".to_owned()).map(|x| Some(RefFn::new(x, |x| x.as_str()))),
1719 )
1720 .style_signal(["-moz-foo", "-webkit-foo", "foo"], always(Some("bar")))
1721 .style_signal(
1722 ["-moz-foo", "-webkit-foo", "foo"],
1723 always(Some("bar".to_owned())),
1724 )
1725 .style_signal(
1726 ["-moz-foo", "-webkit-foo", "foo"],
1727 always("bar".to_owned()).map(|x| Some(RefFn::new(x, |x| x.as_str()))),
1728 );
1729 }
1730
1731 #[test]
1732 fn shadow_root() {
1733 let _a = html!("div", {
1734 .shadow_root!(ShadowRootMode::Closed => {
1735 .children(&mut [
1736 html!("span")
1737 ])
1738 })
1739 });
1740 }
1741
1742 #[test]
1743 fn with_cfg() {
1744 let _a = html!("div", {
1745 .with_cfg!(target_arch = "wasm32", {
1746 .attribute("foo", "bar")
1747 })
1748
1749 .with_cfg!(all(not(foo), bar = "test", feature = "hi"), {
1750 .attribute("foo", "bar")
1751 })
1752 });
1753 }
1754}