1use std::borrow::Cow;
2
3use serde::Serialize;
4use topcoat_core::context::Cx;
5use topcoat_view::{AttributeValueViewParts, PartsWriter};
6
7#[derive(Debug, Clone)]
16pub struct Js {
17 parts: Vec<JsPart>,
18}
19
20#[derive(Debug, Clone)]
21enum JsPart {
22 Source(Cow<'static, str>),
24 Raw(&'static str),
27 Surrogate(String),
29}
30
31impl Js {
32 #[must_use]
34 pub fn source(js: impl Into<Cow<'static, str>>) -> Self {
35 Self {
36 parts: vec![JsPart::Source(js.into())],
37 }
38 }
39
40 #[must_use]
42 pub fn builder() -> JsBuilder {
43 JsBuilder { parts: Vec::new() }
44 }
45
46 pub(crate) fn write(&self, parts: &mut PartsWriter<'_>) {
49 for part in &self.parts {
50 match part {
51 JsPart::Source(Cow::Borrowed(js)) => {
52 parts.push_static_str(js);
53 }
54 JsPart::Source(Cow::Owned(js)) => {
55 parts.push_str(js);
56 }
57 JsPart::Raw(js) => {
58 parts.push_static_str_unescaped(js);
59 }
60 JsPart::Surrogate(json) => {
61 parts
62 .push_promoted_str_unescaped(&"cx.hydrate(")
63 .push_str(json)
64 .push_promoted_str_unescaped(&")");
65 }
66 }
67 }
68 }
69}
70
71impl AttributeValueViewParts for Js {
74 #[inline]
75 fn attribute_present(&self) -> bool {
76 true
77 }
78
79 #[inline]
80 fn into_view_parts(self, _cx: &Cx, parts: &mut PartsWriter<'_>) {
81 self.write(parts);
82 }
83}
84
85#[derive(Debug)]
87pub struct JsBuilder {
88 parts: Vec<JsPart>,
89}
90
91impl JsBuilder {
92 #[must_use]
94 pub fn source(mut self, js: impl Into<Cow<'static, str>>) -> Self {
95 self.parts.push(JsPart::Source(js.into()));
96 self
97 }
98
99 #[must_use]
101 pub fn raw(mut self, js: &'static str) -> Self {
102 self.parts.push(JsPart::Raw(js));
103 self
104 }
105
106 #[must_use]
113 pub fn surrogate(mut self, value: &(impl Serialize + ?Sized)) -> Self {
114 let json = serde_json::to_string(value).expect("failed to serialize surrogate value");
115 self.parts.push(JsPart::Surrogate(json));
116 self
117 }
118
119 #[must_use]
121 pub fn build(self) -> Js {
122 Js { parts: self.parts }
123 }
124}