1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
use ;
use ;
use crate::;
/// Derives id, signal, form, and component-prop helpers for a component struct.
///
/// This derive does **not** implement `Render`. A type becomes
///
/// usable as a component in `html!` by implementing `Render`; `#[derive(Cheers)]` only
/// adds the supporting APIs around that type.
///
/// # Generated APIs
///
/// Depending on which attributes you use, the derive generates:
///
/// - DOM id associated functions from struct-level `#[id]`, field-level `#[id]`, and
/// `#[id("namespace")]`, plus an `ids()` method that returns all id bindings for the
/// current component instance.
/// - signal associated functions from `#[signal]` and `#[signal(name: Type)]`, plus a
/// `signals()` method that returns all signal bindings for the current component instance.
/// Signals are Datastar-local by default; use `#[signal(global)]` or
/// `#[signal(global, name: Type)]` to include them in Datastar JSON payload
/// deserialization types.
/// - form field-name bindings and a deserializable `...Form` type from `#[form]` and
/// `#[form_derive(...)]`, plus a `form_names()` method that returns all form-name
/// bindings for the current component instance.
/// - support for `#[prop(default(...))]`
///
/// Form names are component-local and are not meant to be referenced from outside the
/// component. Ids and signals can be referenced from outside through the generated associated
/// functions.
///
/// # Supported attributes
///
/// - `#[id]` on the struct opts into a static base id for that component type.
/// - `#[id]` on at most one field marks the component instance id.
/// - `#[id("namespace")]` on the struct opts into the base id and generates an additional
/// namespaced id.
/// - `#[signal]` on a field generates a Datastar-local signal accessor for that field.
/// - `#[signal(global)]` on a field generates a payload-sent signal accessor for that field.
/// - `#[signal(nested)]` / `#[signal(global, nested)]` on a field nests another component's
/// signal scope.
/// - `#[signal(name: Type)]` on the struct generates an extra Datastar-local signal that is
/// not backed by a field.
/// - `#[signal(global, name: Type)]` on the struct generates an extra payload-sent signal that
/// is not backed by a field.
/// - `#[form]` on a field includes that field in the generated form type.
/// - `#[form(...)]` on a field forwards additional field attributes, such as serde
/// attributes, to the generated form field.
/// - `#[form(flatten)]` on a field composes another `#[derive(Cheers)]` component's generated
/// form type with `serde(flatten)` and exposes its form-name bindings as a nested group.
/// - `#[form(name: Type)]` on the struct adds an extra field to the generated form type.
/// - `#[form_derive(...)]` adds derives to the generated `...Form` type.
/// - `#[prop(default(...))]` marks a component field as optional when used from `html!`.
/// Optional props are provided through a trailing `(prop=value)` group; use `()` to opt into
/// defaults without overriding any optional props. This syntax cannot be combined with `..`
/// defaults syntax.
///
/// # Example
///
/// ```ignore
/// use cheers::prelude::*;
///
/// #[derive(Cheers)]
/// #[id("input")]
/// struct TodoItem {
/// #[id]
/// id: u64,
/// #[signal]
/// done: bool,
/// #[form]
/// title: String,
/// }
///
/// impl Render for TodoItem {
/// fn render_to(&self, buffer: &mut Buffer<Element>) {
/// let TodoItemIds { id, id_input } = self.ids();
/// let TodoItemSignals { signal_done } = self.signals();
/// let TodoItemFormNames { form_title } = self.form_names();
///
/// html! {
/// label for=id_input {
/// input id=id type="checkbox" !bind(signal_done) name=(form_title);
/// }
/// }
/// .render_to(buffer);
/// }
/// }
///
/// let rendered = TodoItem {
/// id: 1,
/// done: false,
/// title: String::from("Write docs"),
/// }
/// .render()
/// .into_inner();
///
/// assert!(rendered.contains("id=\"todo_item-1\""));
/// assert!(rendered.contains("for=\"todo_item-1-input\""));
/// assert!(rendered.contains("data-bind=\"_todo_item['1']['done']\""));
/// assert!(rendered.contains("name=\"title\""));
/// ```
/// Builds a lazily rendered HTML fragment.
///
/// Use `html!` for normal Cheers markup. It can render elements, text, components, control flow,
/// and interpolated values with `(expr)`.
///
/// `html!` captures interpolated values by value. Use `(@&expr)` in expression positions when
/// you want to borrow instead and keep using the original value after the macro invocation.
///
/// # Example
///
/// ```ignore
/// use cheers::prelude::*;
///
/// let name = String::from("Ferris");
/// let page = html! {
/// section {
/// h1 { "Hello" }
/// p { (name) }
/// }
/// };
///
/// assert_eq!(page.render().into_inner(), "<section><h1>Hello</h1><p>Ferris</p></section>");
/// ```
/// Builds a lazily rendered SVG fragment or document.
///
/// Use `svg!` when generating standalone SVG, such as sprite bundles served as
/// `image/svg+xml`. Unlike [`html!`], this macro starts in SVG/XML mode from
/// the root, validates elements against the SVG validation table, and renders
/// childless SVG elements with XML-style self-closing syntax (`/>`).
///
/// To embed inline SVG inside HTML, prefer [`html!`] and a normal `<svg>`
/// element. Cheers will switch into SVG validation automatically inside that
/// subtree.
///
/// # Example
///
/// ```ignore
/// use cheers::prelude::*;
///
/// let sprite = svg! {
/// svg viewBox="0 0 16 16" xml:lang="en" {
/// defs {
/// symbol id="icon-check" viewBox="0 0 16 16" {
/// path d="M6.5 11.2 3.3 8l-1.1 1.1 4.3 4.3L14 5.9l-1.1-1.1z";
/// }
/// }
/// }
/// };
///
/// assert!(sprite.render().into_inner().contains("<symbol id=\"icon-check\""));
/// ```
/// Builds an attribute value from literal and interpolated fragments.
///
/// `attribute!` captures interpolated values by value. Use `(@&expr)` to borrow an
/// interpolated value instead.
///
/// # Example
///
/// ```ignore
/// use cheers::macros::attribute;
/// use cheers::prelude::*;
///
/// let kind = String::from("primary");
/// let class = attribute! { "btn btn-" (kind) };
/// let page = html! {
/// button class=class { "Save" }
/// };
///
/// assert_eq!(page.render().into_inner(), r#"<button class="btn btn-primary">Save</button>"#);
/// ```
/// Builds a JavaScript source fragment for Datastar attributes.
///
/// # Example
///
/// ```ignore
/// use cheers::prelude::*;
///
/// let onclick = datastar_source! {
/// "console.log("
/// (signal_name)
/// ")"
/// };
///
/// html! {
/// button !on:click(onclick) { "Log" }
/// };
/// ```
/// Builds JavaScript source for a `<script>` body.
///
/// Unlike `datastar_source!`, this macro does not HTML-attribute-escape JavaScript operators such as
/// `<`. Interpolated string-like values are emitted as JavaScript string literals and escaped so
/// they cannot terminate the surrounding `<script>` element.
///
/// # Example
///
/// ```ignore
/// use cheers::prelude::*;
///
/// let url = "/profile";
/// let script = JsScript::new(js_script! {
/// "window.location.assign("
/// url
/// ");"
/// });
/// ```
/// Creates a component-local signal whose name is scoped to the current component instance and call site.
///
/// Use this when defining local state inside a component method, not when referencing a
/// component signal from outside. Scoped signals are intentionally internal to the component
/// that creates them.
///
/// This is useful for component-local UI state such as loading spinners.
///
/// The type hint is optional.
///
/// ```ignore
/// use cheers::prelude::*;
///
/// #[derive(Cheers)]
/// struct Projects {
/// #[id]
/// id: u64,
/// }
///
/// impl Render for Projects {
/// fn render_to(&self, buffer: &mut Buffer<Element>) {
/// scoped_signal!(signal_fetching: bool);
/// scoped_signal!(signal_busy);
///
/// html! {
/// button !on:click("@get('/items')") !indicator(signal_fetching) { "Refresh" }
/// div !show(signal_fetching) { "Loading..." }
/// p !signals(signal_busy: true) {}
/// }
/// .render_to(buffer);
/// }
/// }
/// ```
/// Generates a renderable action reference from an async handler function.
///
/// Applying `#[action(METHOD)]` generates a companion `...Action` type that renders to the
/// client-side action string used by Cheers attributes such as `!on:click`.
///
/// Path parameters are taken from `Path<_>` arguments. Form submission is enabled when the
/// handler takes a `Form<_>` argument or an argument marked with `#[form]`.
/// Register the generated route explicitly with `Router::action::<...Action>()`.
///
/// # Example
///
/// ```ignore
/// use cheers::prelude::*;
/// use axum::extract::Path;
///
/// #[action(POST)]
/// async fn save_user(Path(id): Path<u64>) {}
///
/// let action = SaveUserAction { id: 7 };
/// assert_eq!(action.render().into_inner(), "@post('/cheers/actions/save_user/7')");
/// ```