renox_core/components/
plugin.rs1use super::attrs::Kind;
5use super::contracts::{Contract, Module, Prop as Compiled, Render, Slot};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9#[non_exhaustive]
10pub struct Prop {
11 name: String,
12 kind: Kind,
13 required: bool,
14 values: Vec<String>,
15 doc: String,
16}
17
18impl Prop {
19 fn new(name: &str, kind: Kind) -> Self {
20 Self {
21 name: name.to_owned(),
22 kind,
23 required: false,
24 values: Vec::new(),
25 doc: String::new(),
26 }
27 }
28
29 pub fn text(name: &str) -> Self {
31 Self::new(name, Kind::Text)
32 }
33
34 pub fn bool(name: &str) -> Self {
36 Self::new(name, Kind::Bool)
37 }
38
39 pub fn number(name: &str) -> Self {
41 Self::new(name, Kind::Number)
42 }
43
44 pub fn data(name: &str) -> Self {
46 Self::new(name, Kind::Data)
47 }
48
49 pub fn choice(name: &str, values: &[&str]) -> Self {
51 let mut prop = Self::new(name, Kind::Enum);
52 prop.values = values.iter().map(|v| (*v).to_owned()).collect();
53 prop
54 }
55
56 pub fn required(mut self) -> Self {
58 self.required = true;
59 self
60 }
61
62 pub fn doc(mut self, doc: &str) -> Self {
64 self.doc = doc.to_owned();
65 self
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
81#[non_exhaustive]
82pub struct Component {
83 tag: String,
84 template: String,
85 macro_name: String,
86 doc: String,
87 props: Vec<Prop>,
88 content: bool,
89 attrs: bool,
90}
91
92impl Component {
93 pub fn macro_call(tag: &str, template: &str, macro_name: &str) -> Self {
97 Self {
98 tag: tag.to_owned(),
99 template: template.to_owned(),
100 macro_name: macro_name.to_owned(),
101 doc: String::new(),
102 props: Vec::new(),
103 content: true,
104 attrs: false,
105 }
106 }
107
108 pub fn doc(mut self, doc: &str) -> Self {
110 self.doc = doc.to_owned();
111 self
112 }
113
114 pub fn prop(mut self, prop: Prop) -> Self {
116 self.props.push(prop);
117 self
118 }
119
120 pub fn content(mut self, takes: bool) -> Self {
122 self.content = takes;
123 self
124 }
125
126 pub fn attrs(mut self, pass: bool) -> Self {
129 self.attrs = pass;
130 self
131 }
132
133 pub fn tag(&self) -> &str {
135 &self.tag
136 }
137
138 pub(crate) fn problem(&self) -> Option<String> {
140 if !self.tag.starts_with("rx-") || self.tag.len() <= 3 {
141 return Some(format!("the tag \"{}\" must start with \"rx-\"", self.tag));
142 }
143 if super::contracts::BUILTIN.iter().any(|c| c.tag == self.tag) {
144 return Some(format!("<{}> is a built-in component", self.tag));
145 }
146 None
147 }
148
149 pub(crate) fn contract(&self) -> Contract {
152 fn leak(s: &str) -> &'static str {
153 Box::leak(s.to_owned().into_boxed_str())
154 }
155 let props: Vec<Compiled> = self
156 .props
157 .iter()
158 .map(|p| Compiled {
159 name: leak(&p.name),
160 kind: p.kind,
161 required: p.required,
162 values: Box::leak(
163 p.values
164 .iter()
165 .map(|v| leak(v))
166 .collect::<Vec<_>>()
167 .into_boxed_slice(),
168 ),
169 doc: leak(&p.doc),
170 })
171 .collect();
172 let alias: String = self
173 .template
174 .chars()
175 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
176 .collect();
177 let slots: &'static [Slot] = if self.content {
178 Box::leak(Box::new([Slot {
179 name: "",
180 into: None,
181 args: &[],
182 optional: true,
183 doc: "The content.",
184 }]))
185 } else {
186 &[]
187 };
188 Contract {
189 tag: leak(&self.tag),
190 doc: leak(&self.doc),
191 render: Render::Macro {
192 module: Module::Custom {
193 path: leak(&self.template),
194 alias: leak(&format!("__rx_p_{alias}")),
195 },
196 name: leak(&self.macro_name),
197 },
198 props: Box::leak(props.into_boxed_slice()),
199 slots,
200 events: &[],
201 route_prop: None,
202 attrs: self.attrs,
203 parent: None,
204 }
205 }
206}