Skip to main content

autumn_plugin_vanilla/behaviors/
copy.rs

1use autumn_web::Markup;
2
3use super::{Behavior, button, marker};
4use crate::markup::{Attributes, common};
5
6/// Copies text to the clipboard on click.
7///
8/// The element gets `data-vanilla-state="copied"` (or `"failed"`) for 2
9/// seconds. A screen reader hears "Copied" (or "Copy failed"). The element
10/// also sends a `vanilla:copied` (or `vanilla:copy-failed`) event. The
11/// runtime never copies a password field or a hidden field.
12///
13/// ```rust
14/// use autumn_plugin_vanilla::CopyText;
15/// use autumn_web::html;
16///
17/// let html = CopyText::selector("#api-key").button(html! { "Copy" });
18/// assert!(html.into_string().contains(r##"data-vanilla-copy="#api-key""##));
19/// ```
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[must_use]
22pub struct CopyText {
23    source: Source,
24    messages: Option<(String, String)>,
25    extra: Attributes,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29enum Source {
30    Selector(String),
31    Text(String),
32}
33
34impl CopyText {
35    /// Copies the first match of a CSS selector. For an `input`, `textarea`
36    /// or `select`, it copies the value. For other elements, the text.
37    ///
38    /// The selector is trusted input. Do not make it from user data.
39    pub fn selector(selector: impl Into<String>) -> Self {
40        Self::with(Source::Selector(selector.into()))
41    }
42
43    /// Copies this literal text.
44    pub fn text(text: impl Into<String>) -> Self {
45        Self::with(Source::Text(text.into()))
46    }
47
48    const fn with(source: Source) -> Self {
49        Self {
50            source,
51            messages: None,
52            extra: Vec::new(),
53        }
54    }
55
56    /// Sets the screen reader messages. The defaults are "Copied" and
57    /// "Copy failed".
58    pub fn messages(mut self, done: impl Into<String>, failed: impl Into<String>) -> Self {
59        self.messages = Some((done.into(), failed.into()));
60        self
61    }
62
63    /// The attributes, in render order.
64    #[must_use]
65    pub fn attributes(&self) -> Attributes {
66        let option = match &self.source {
67            Source::Selector(selector) => ("data-vanilla-copy", selector.clone()),
68            Source::Text(text) => ("data-vanilla-copy-text", text.clone()),
69        };
70        let mut attributes = vec![marker(Behavior::Copy), option];
71        if let Some((done, failed)) = &self.messages {
72            attributes.push(("data-vanilla-copy-done", done.clone()));
73            attributes.push(("data-vanilla-copy-failed", failed.clone()));
74        }
75        attributes.extend(self.extra.iter().cloned());
76        attributes
77    }
78
79    /// Renders a `<button type="button">` that copies.
80    #[must_use]
81    pub fn button(&self, content: Markup) -> Markup {
82        button(self.attributes(), content)
83    }
84}
85
86common!(CopyText, wrap);