1use std::time::Duration;
4
5use crate::attrs::{Attr, push_flag, push_opt};
6use crate::ease::Ease;
7use crate::fmt::secs;
8use crate::scroll::{Play, ScrollPos, Scrub, ToggleActions};
9
10#[cfg(test)]
12pub(crate) const DEFAULT_DURATION: Duration = Duration::from_millis(800);
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum Repeat {
17 Times(u32),
19 Forever,
21}
22
23#[allow(clippy::struct_excessive_bools)]
27#[derive(Debug, Clone, Default, PartialEq)]
28pub(crate) struct Common {
29 pub(crate) play: Play,
30 pub(crate) delay: Option<Duration>,
31 pub(crate) duration: Option<Duration>,
32 pub(crate) ease: Option<Ease>,
33 pub(crate) repeat: Option<Repeat>,
34 pub(crate) yoyo: bool,
35 pub(crate) repeat_delay: Option<Duration>,
36 pub(crate) trigger: Option<String>,
37 pub(crate) start: Option<ScrollPos>,
38 pub(crate) end: Option<ScrollPos>,
39 pub(crate) toggle_actions: Option<ToggleActions>,
40 pub(crate) replay: bool,
41 pub(crate) scrub: Option<Scrub>,
42 pub(crate) pin: bool,
43 pub(crate) markers: bool,
44 pub(crate) animate_reduced: bool,
45 pub(crate) id: Option<String>,
46 pub(crate) class: Option<String>,
47}
48
49impl Common {
50 pub(crate) fn html_attrs(&self) -> Vec<Attr> {
52 let mut out = Vec::new();
53 push_opt(&mut out, "id", self.id.clone());
54 push_opt(&mut out, "class", self.class.clone());
55 out
56 }
57
58 pub(crate) fn push(&self, out: &mut Vec<Attr>) {
60 push_opt(
61 out,
62 "data-gsap-on",
63 (self.play == Play::Load).then(|| "load".to_owned()),
64 );
65 push_opt(out, "data-gsap-delay", self.delay.map(secs));
66 push_opt(out, "data-gsap-duration", self.duration.map(secs));
67 push_opt(out, "data-gsap-ease", self.ease.map(|e| e.to_string()));
68 push_opt(
69 out,
70 "data-gsap-repeat",
71 self.repeat.map(|r| match r {
72 Repeat::Times(n) => n.to_string(),
73 Repeat::Forever => "-1".to_owned(),
74 }),
75 );
76 push_flag(out, "data-gsap-yoyo", self.yoyo);
77 push_opt(out, "data-gsap-repeat-delay", self.repeat_delay.map(secs));
78 push_opt(out, "data-gsap-trigger", self.trigger.clone());
79 push_opt(out, "data-gsap-start", self.start.map(|p| p.to_string()));
80 push_opt(out, "data-gsap-end", self.end.map(|p| p.to_string()));
81 push_opt(
82 out,
83 "data-gsap-toggle-actions",
84 self.toggle_actions.map(|t| t.to_string()),
85 );
86 push_opt(
87 out,
88 "data-gsap-once",
89 self.replay.then(|| "false".to_owned()),
90 );
91 push_opt(out, "data-gsap-scrub", self.scrub.map(|s| s.to_string()));
92 push_flag(out, "data-gsap-pin", self.pin);
93 push_flag(out, "data-gsap-markers", self.markers);
94 push_opt(
95 out,
96 "data-gsap-reduced",
97 self.animate_reduced.then(|| "animate".to_owned()),
98 );
99 }
100}
101
102macro_rules! common_setters {
104 () => {
105 pub const fn play(mut self, play: crate::Play) -> Self {
108 self.common.play = play;
109 self
110 }
111
112 pub const fn delay(mut self, delay: std::time::Duration) -> Self {
114 self.common.delay = Some(delay);
115 self
116 }
117
118 pub const fn duration(mut self, duration: std::time::Duration) -> Self {
122 self.common.duration = Some(duration);
123 self
124 }
125
126 pub const fn ease(mut self, ease: crate::Ease) -> Self {
129 self.common.ease = Some(ease);
130 self
131 }
132
133 pub const fn repeat(mut self, repeat: crate::Repeat) -> Self {
135 self.common.repeat = Some(repeat);
136 self
137 }
138
139 pub const fn yoyo(mut self) -> Self {
141 self.common.yoyo = true;
142 self
143 }
144
145 pub const fn repeat_delay(mut self, delay: std::time::Duration) -> Self {
147 self.common.repeat_delay = Some(delay);
148 self
149 }
150
151 pub fn trigger(mut self, selector: impl Into<String>) -> Self {
155 self.common.trigger = Some(selector.into()).filter(|s| !s.trim().is_empty());
156 self
157 }
158
159 pub const fn start(mut self, pos: crate::ScrollPos) -> Self {
161 self.common.start = Some(pos);
162 self
163 }
164
165 pub const fn end(mut self, pos: crate::ScrollPos) -> Self {
167 self.common.end = Some(pos);
168 self
169 }
170
171 pub const fn toggle_actions(mut self, actions: crate::ToggleActions) -> Self {
173 self.common.toggle_actions = Some(actions);
174 self
175 }
176
177 pub const fn replay(mut self) -> Self {
182 self.common.replay = true;
183 self
184 }
185
186 pub const fn scrub(mut self, scrub: crate::Scrub) -> Self {
190 self.common.scrub = Some(scrub);
191 self
192 }
193
194 pub const fn pin(mut self) -> Self {
197 self.common.pin = true;
198 self
199 }
200
201 pub const fn markers(mut self) -> Self {
203 self.common.markers = true;
204 self
205 }
206
207 pub fn id(mut self, id: impl Into<String>) -> Self {
209 self.common.id = Some(id.into()).filter(|s| !s.trim().is_empty());
210 self
211 }
212
213 pub fn class(mut self, class: impl Into<String>) -> Self {
215 self.common.class = Some(class.into()).filter(|s| !s.trim().is_empty());
216 self
217 }
218
219 #[allow(clippy::needless_pass_by_value)]
221 #[must_use]
222 pub fn wrap(self, markup: autumn_web::Markup) -> autumn_web::Markup {
223 self.wrap_in(crate::Tag::Div, markup)
224 }
225
226 #[allow(clippy::needless_pass_by_value)]
228 #[must_use]
229 pub fn wrap_in(self, tag: crate::Tag, markup: autumn_web::Markup) -> autumn_web::Markup {
230 let mut attrs = self.common.html_attrs();
231 attrs.extend(self.attributes());
232 crate::attrs::wrap_el(tag, &attrs, &markup)
233 }
234
235 pub const fn animate_on_reduced_motion(mut self) -> Self {
238 self.common.animate_reduced = true;
239 self
240 }
241 };
242}
243
244pub(crate) use common_setters;
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn init_js_uses_the_same_default_duration() {
252 let js = include_str!("../assets/init.js");
253 let want = format!("duration: {},", secs(DEFAULT_DURATION));
254 assert!(js.contains(&want), "init.js DEFAULTS must contain `{want}`");
255 }
256
257 #[test]
258 fn documented_defaults_match_init_js() {
259 let js = include_str!("../assets/init.js");
260 for want in [r#"ease: "power3.out","#, r#"start: "top 85%","#] {
261 assert!(js.contains(want), "init.js DEFAULTS must contain `{want}`");
262 }
263 }
264
265 #[test]
266 fn html_attrs_hold_only_id_and_class() {
267 let c = Common {
268 id: Some("a".to_owned()),
269 class: Some("b".to_owned()),
270 pin: true,
271 ..Common::default()
272 };
273 assert_eq!(
274 c.html_attrs(),
275 vec![("id", "a".to_owned()), ("class", "b".to_owned())]
276 );
277 }
278}