1use std::{
2 borrow::Cow,
3 fmt,
4 ops::{Deref, DerefMut},
5};
6
7use crate::{format::*, *};
8
9use paste::paste;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
18pub struct GlobalAttributes<'a>(Option<Box<GlobalAttributesInner<'a>>>);
19
20impl<'a> GlobalAttributes<'a> {
21 pub const EMPTY: Self = Self(None);
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
29pub struct GlobalAttributesInner<'a> {
30 pub id: Cow<'a, str>,
32 pub class: Cow<'a, str>,
34 pub style: Cow<'a, str>,
36 pub title: Cow<'a, str>,
38 pub autofocus: bool,
40 pub itemscope: bool,
42}
43
44pub(crate) static DEFAULT_GLOBAL_ATTRIBUTES_INNER: GlobalAttributesInner<'static> =
45 GlobalAttributesInner {
46 id: Cow::Borrowed(""),
47 class: Cow::Borrowed(""),
48 style: Cow::Borrowed(""),
49 title: Cow::Borrowed(""),
50 autofocus: false,
51 itemscope: false,
52 };
53
54impl<'a> Deref for GlobalAttributes<'a> {
55 type Target = GlobalAttributesInner<'a>;
56 fn deref(&self) -> &Self::Target {
57 self.0
58 .as_deref()
59 .unwrap_or(&DEFAULT_GLOBAL_ATTRIBUTES_INNER)
60 }
61}
62
63impl<'a> DerefMut for GlobalAttributes<'a> {
64 fn deref_mut(&mut self) -> &mut Self::Target {
65 self.0.get_or_insert_with(std::default::Default::default)
66 }
67}
68
69impl<'a> IndentFormat for GlobalAttributes<'a> {
70 fn indent_fmt(&self, f: &mut IndentFormatter) -> fmt::Result {
71 id_write(&self.id, f.f)?;
72 class_write(&self.class, f.f)?;
73 style_write(&self.style, f.f)?;
74 title_write(&self.title, f.f)?;
75 autofocus_write(&self.autofocus, f.f)?;
76 itemscope_write(&self.itemscope, f.f)?;
77 Ok(())
78 }
79}
80
81macro_rules! attribute_struct {
82 ($name:tt[bool]) => {
83 paste! {
84 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
85 #[doc = "The `"]
86 #[doc = stringify!($name)]
87 #[doc = "` attribute"]
88 pub struct [<$name:camel>];
89 #[allow(non_camel_case_types)]
90 pub(crate) type [<$name _t>]<'a> = bool;
91 #[allow(non_camel_case_types)]
92 pub(crate) type [<$name _ref_t>] = bool;
93 #[allow(non_snake_case)]
94 pub(crate) fn [<$name _take_ref>](val: &[<$name _t>]) -> [<$name _ref_t>] {
95 *val
96 }
97 #[allow(non_snake_case)]
98 pub(crate) fn [<$name _write>](b: &bool, f: &mut fmt::Formatter) -> fmt::Result {
99 if *b {
100 write!(f, " {}", stringify!($name).trim_end_matches('_'))
101 } else {
102 Ok(())
103 }
104 }
105 impl [<$name:camel>] {
106 fn take(self) -> bool {
107 true
108 }
109 }
110 }
111 };
112 ($name:tt) => {
113 paste! {
114 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
115 #[allow(non_camel_case_types)]
116 #[doc = "The `"]
117 #[doc = stringify!($name)]
118 #[doc = "` attribute"]
119 pub struct [<$name:camel>]<T = String>(pub T);
120 #[allow(non_camel_case_types)]
121 pub(crate) type [<$name _t>]<'a> = Cow<'a, str>;
122 #[allow(non_camel_case_types)]
123 pub(crate) type [<$name _ref_t>]<'a> = &'a str;
124 #[allow(non_snake_case)]
125 pub(crate) fn [<$name _take_ref>]<'a>(val: &'a [<$name _t>]) -> [<$name _ref_t>]<'a> {
126 val
127 }
128 #[allow(non_snake_case)]
129 pub(crate) fn [<$name _write>](s: &str, f: &mut fmt::Formatter) -> fmt::Result {
130 if s.is_empty() {
131 Ok(())
132 } else {
133 write!(f, " {}=\"{}\"", stringify!($name).trim_end_matches('_'), s)
134 }
135 }
136 impl<T> [<$name:camel>]<T> {
137 fn take(self) -> T {
138 self.0
139 }
140 }
141 }
142 };
143}
144
145macro_rules! attribute_trait {
146 ($name:tt [bool]) => {
147 paste! {
148 impl<'a, E> ElementData<E> for [<$name:camel>]
149 where
150 E: [<Has $name:camel>]<'a>
151 {
152 fn add_to(self, element: &mut E) {
153 element.[<set_ $name>](self.take());
154 }
155 }
156 }
157 };
158 ($name:tt) => {
159 paste! {
160 impl<'a, E, T> ElementData<E> for [<$name:camel>]<T>
161 where
162 E: [<Has $name:camel>]<'a>,
163 T: Into<Cow<'a, str>>,
164 {
165 fn add_to(self, element: &mut E) {
166 element.[<set_ $name>](self.take());
167 }
168 }
169 }
170 };
171}
172
173macro_rules! attributes {
174 ($($name:tt $([$ty:ident])?),* $(,)?) => {
175 $(attribute_struct!($name $([$ty])*);)*
176 pub mod attribute_traits {
177 use super::*;
179 $(
180 paste! {
181 #[doc = "Trait for elements that have the `"]
182 #[doc = stringify!($name)]
183 #[doc = "` attribute"]
184 #[allow(non_camel_case_types)]
185 pub trait [<Has $name:camel>]<'a> {
186 #[doc = "Get the value of the `"]
187 #[doc = stringify!($name)]
188 #[doc = "` attribute"]
189 fn [<get_ $name>](&self) -> [<$name _ref_t>];
190 #[doc = "Set the value of the `"]
191 #[doc = stringify!($name)]
192 #[doc = "` attribute"]
193 fn [<set_ $name>](&mut self, value: impl Into<[<$name _t>]<'a>>);
194 }
195 }
196 attribute_trait!($name $([$ty])*);
197 )*
198 }
199 };
200}
201
202attributes!(
203 accept,
204 action,
205 align,
206 allow,
207 alt,
208 autocomplete,
209 autofocus[bool],
210 autoplay[bool],
211 charset,
212 checked[bool],
213 cite,
214 class,
215 clear,
216 color,
217 cols,
218 colspan,
219 command,
220 content,
221 controls[bool],
222 coords,
223 crossorigin,
224 data,
225 datetime,
226 decoding,
227 default[bool],
228 defer[bool],
229 dir,
230 dirname,
231 disabled[bool],
232 download,
233 enctype,
234 form,
235 formaction,
236 formenctype,
237 formmethod,
238 formnovalidate[bool],
239 formtarget,
240 headers,
241 height,
242 high,
243 href,
244 hreflang,
245 http_equiv,
246 icon,
247 id,
248 importance,
249 integrity,
250 intrinsicsize,
251 ismap[bool],
252 itemscope[bool],
253 kind,
254 label,
255 list,
256 loading,
257 low,
258 manifest,
259 max_length,
260 max,
261 maxlength,
262 media,
263 method,
264 min_length,
265 min,
266 minlength,
267 multiple[bool],
268 muted[bool],
269 name,
270 nomodule[bool],
271 nonce,
272 noshade,
273 novalidate[bool],
274 open[bool],
275 optimum,
276 pattern,
277 ping,
278 placeholder,
279 playsinline,
280 poster,
281 preload,
282 profile,
283 async[bool],
284 for,
285 loop[bool],
286 type,
287 radiogroup,
288 readonly[bool],
289 referrerpolicy,
290 rel,
291 required[bool],
292 reversed[bool],
293 rows,
294 rowspan,
295 sandbox,
296 scope,
297 selected[bool],
298 shape,
299 size,
300 sizes,
301 span,
302 src,
303 srcdoc,
304 srclang,
305 srcset,
306 start,
307 step,
308 style,
309 target,
310 title,
311 usemap,
312 value,
313 width,
314 wrap,
315 xmlns,
316);
317
318macro_rules! event {
319 ($($name:ident),* $(,)?) => {
320 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
324 #[allow(missing_docs)]
325 pub enum Event {
326 $($name,)*
327 }
328
329 impl fmt::Display for Event {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 match self {
332 $(Self::$name => write!(f, paste!(concat!("on", stringify!([<$name:lower>])))),)*
333 }
334 }
335 }
336 };
337}
338
339event!(
340 Abort,
341 AfterPrint,
342 BeforePrint,
343 BeforeUnload,
344 Blur,
345 CanPlay,
346 CanPlayThrough,
347 Change,
348 Click,
349 ContextMenu,
350 Copy,
351 CueChange,
352 Cut,
353 DblClick,
354 Drag,
355 DragEnd,
356 DragEnter,
357 DragLeave,
358 DragOver,
359 DragStart,
360 Drop,
361 DurationChange,
362 Emptied,
363 Ended,
364 Error,
365 Focus,
366 HashChange,
367 Input,
368 Invalid,
369 KeyDown,
370 KeyPress,
371 KeyUp,
372 Load,
373 LoadedData,
374 LoadedMetadata,
375 LoadStart,
376 Message,
377 MouseDown,
378 MouseMove,
379 MouseOut,
380 MouseOver,
381 MouseUp,
382 MouseWheel,
383 Offline,
384 Online,
385 PageHide,
386 PageShow,
387 Paste,
388 Pause,
389 Play,
390 Playing,
391 PopState,
392 Progress,
393 RateChange,
394 Reset,
395 Resize,
396 Scroll,
397 Search,
398 Seeked,
399 Seeking,
400 Select,
401 Stalled,
402 Storage,
403 Submit,
404 Suspend,
405 TimeUpdate,
406 Toggle,
407 Unload,
408 VolumeChange,
409 Waiting,
410 Wheel,
411);
412
413#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
415pub struct Events<'a>(Vec<(Event, Cow<'a, str>)>);
416
417impl<'a> Events<'a> {
418 pub const NONE: Self = Self(Vec::new());
420 pub fn is_empty(&self) -> bool {
422 self.0.is_empty()
423 }
424 pub fn contains(&self, event: Event) -> bool {
426 self.0.iter().any(|(e, _)| e == &event)
427 }
428 pub fn get(&self, event: Event) -> Option<&str> {
430 self.0
431 .iter()
432 .find(|(e, _)| e == &event)
433 .map(|(_, v)| v.as_ref())
434 }
435 pub fn insert(&mut self, event: Event, value: impl Into<Cow<'a, str>>) {
437 if let Some(i) = self.0.iter().position(|(e, _)| e == &event) {
438 self.0[i].1 = value.into();
439 } else {
440 self.0.push((event, value.into()));
441 }
442 }
443 pub fn remove(&mut self, event: Event) {
445 self.0.retain(|(e, _)| e != &event);
446 }
447 pub fn iter(&self) -> impl Iterator<Item = (Event, &str)> {
449 self.0.iter().map(|(n, v)| (*n, v.as_ref()))
450 }
451}
452
453#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
455pub struct On<V>(
456 pub Event,
458 pub V,
460);
461
462impl<'a, E, V> ElementData<E> for On<V>
463where
464 E: Element<'a>,
465 V: Into<Cow<'a, str>>,
466{
467 fn add_to(self, element: &mut E) {
468 element.events_mut().insert(self.0, self.1);
469 }
470}