1use super::{Property, PropertyId};
4use crate::compat;
5use crate::context::PropertyHandlerContext;
6use crate::declaration::{DeclarationBlock, DeclarationList};
7use crate::error::{ParserError, PrinterError};
8use crate::macros::define_list_shorthand;
9use crate::prefixes::Feature;
10use crate::printer::Printer;
11use crate::properties::masking::get_webkit_mask_property;
12use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero};
13use crate::values::ident::CustomIdent;
14use crate::values::{easing::EasingFunction, time::Time};
15use crate::vendor_prefix::VendorPrefix;
16#[cfg(feature = "visitor")]
17use crate::visitor::Visit;
18use cssparser::*;
19use itertools::izip;
20use smallvec::SmallVec;
21
22define_list_shorthand! {
23 pub struct Transition<'i>(VendorPrefix) {
25 #[cfg_attr(feature = "serde", serde(borrow))]
27 property: TransitionProperty(PropertyId<'i>, VendorPrefix),
28 duration: TransitionDuration(Time, VendorPrefix),
30 delay: TransitionDelay(Time, VendorPrefix),
32 timing_function: TransitionTimingFunction(EasingFunction, VendorPrefix),
34 }
35}
36
37impl<'i> Parse<'i> for Transition<'i> {
38 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
39 let mut property = None;
40 let mut duration = None;
41 let mut delay = None;
42 let mut timing_function = None;
43
44 loop {
45 if duration.is_none() {
46 if let Ok(value) = input.try_parse(Time::parse) {
47 duration = Some(value);
48 continue;
49 }
50 }
51
52 if timing_function.is_none() {
53 if let Ok(value) = input.try_parse(EasingFunction::parse) {
54 timing_function = Some(value);
55 continue;
56 }
57 }
58
59 if delay.is_none() {
60 if let Ok(value) = input.try_parse(Time::parse) {
61 delay = Some(value);
62 continue;
63 }
64 }
65
66 if property.is_none() {
67 if let Ok(value) = input.try_parse(PropertyId::parse) {
68 property = Some(value);
69 continue;
70 }
71 }
72
73 break;
74 }
75
76 Ok(Transition {
77 property: property.unwrap_or(PropertyId::All),
78 duration: duration.unwrap_or(Time::Seconds(0.0)),
79 delay: delay.unwrap_or(Time::Seconds(0.0)),
80 timing_function: timing_function.unwrap_or(EasingFunction::Ease),
81 })
82 }
83}
84
85impl<'i> ToCss for Transition<'i> {
86 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
87 where
88 W: std::fmt::Write,
89 {
90 self.property.to_css(dest)?;
91 if !self.duration.is_zero() || !self.delay.is_zero() {
92 dest.write_char(' ')?;
93 self.duration.to_css(dest)?;
94 }
95
96 if !self.timing_function.is_ease() {
97 dest.write_char(' ')?;
98 self.timing_function.to_css(dest)?;
99 }
100
101 if !self.delay.is_zero() {
102 dest.write_char(' ')?;
103 self.delay.to_css(dest)?;
104 }
105
106 Ok(())
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Default, Parse, ToCss)]
112#[cfg_attr(feature = "visitor", derive(Visit))]
113#[cfg_attr(
114 feature = "serde",
115 derive(serde::Serialize, serde::Deserialize),
116 serde(rename_all = "kebab-case")
117)]
118#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
119#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
120pub enum ViewTransitionName<'i> {
121 #[default]
123 None,
124 Auto,
126 #[cfg_attr(feature = "serde", serde(borrow, untagged))]
128 Custom(CustomIdent<'i>),
129}
130
131#[derive(Debug, Clone, PartialEq, Default, Parse, ToCss)]
133#[cfg_attr(feature = "visitor", derive(Visit))]
134#[cfg_attr(
135 feature = "serde",
136 derive(serde::Serialize, serde::Deserialize),
137 serde(rename_all = "kebab-case")
138)]
139#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
140#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
141pub enum ViewTransitionGroup<'i> {
142 #[default]
144 Normal,
145 Contain,
147 Nearest,
149 #[cfg_attr(feature = "serde", serde(borrow, untagged))]
151 Custom(CustomIdent<'i>),
152}
153
154#[derive(Default)]
155pub(crate) struct TransitionHandler<'i> {
156 properties: Option<(SmallVec<[PropertyId<'i>; 1]>, VendorPrefix)>,
157 durations: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,
158 delays: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,
159 timing_functions: Option<(SmallVec<[EasingFunction; 1]>, VendorPrefix)>,
160 has_any: bool,
161}
162
163impl<'i> PropertyHandler<'i> for TransitionHandler<'i> {
164 fn handle_property(
165 &mut self,
166 property: &Property<'i>,
167 dest: &mut DeclarationList<'i>,
168 context: &mut PropertyHandlerContext<'i, '_>,
169 ) -> bool {
170 use Property::*;
171
172 macro_rules! maybe_flush {
173 ($prop: ident, $val: expr, $vp: ident) => {{
174 if let Some((val, prefixes)) = &self.$prop {
177 if val != $val && !prefixes.contains(*$vp) {
178 self.flush(dest, context);
179 }
180 }
181 }};
182 }
183
184 macro_rules! property {
185 ($feature: ident, $prop: ident, $val: expr, $vp: ident) => {{
186 maybe_flush!($prop, $val, $vp);
187
188 if let Some((val, prefixes)) = &mut self.$prop {
190 *val = $val.clone();
191 *prefixes |= *$vp;
192 *prefixes = context.targets.prefixes(*prefixes, Feature::$feature);
193 } else {
194 let prefixes = context.targets.prefixes(*$vp, Feature::$feature);
195 self.$prop = Some(($val.clone(), prefixes));
196 self.has_any = true;
197 }
198 }};
199 }
200
201 match property {
202 TransitionProperty(val, vp) => {
203 let merged_values = merge_properties(val.iter());
204 property!(TransitionProperty, properties, &merged_values, vp);
205 }
206 TransitionDuration(val, vp) => property!(TransitionDuration, durations, val, vp),
207 TransitionDelay(val, vp) => property!(TransitionDelay, delays, val, vp),
208 TransitionTimingFunction(val, vp) => property!(TransitionTimingFunction, timing_functions, val, vp),
209 Transition(val, vp) => {
210 let properties: SmallVec<[PropertyId; 1]> = merge_properties(val.iter().map(|b| &b.property));
211 maybe_flush!(properties, &properties, vp);
212
213 let durations: SmallVec<[Time; 1]> = val.iter().map(|b| b.duration.clone()).collect();
214 maybe_flush!(durations, &durations, vp);
215
216 let delays: SmallVec<[Time; 1]> = val.iter().map(|b| b.delay.clone()).collect();
217 maybe_flush!(delays, &delays, vp);
218
219 let timing_functions: SmallVec<[EasingFunction; 1]> =
220 val.iter().map(|b| b.timing_function.clone()).collect();
221 maybe_flush!(timing_functions, &timing_functions, vp);
222
223 property!(TransitionProperty, properties, &properties, vp);
224 property!(TransitionDuration, durations, &durations, vp);
225 property!(TransitionDelay, delays, &delays, vp);
226 property!(TransitionTimingFunction, timing_functions, &timing_functions, vp);
227 }
228 Unparsed(val) if is_transition_property(&val.property_id) => {
229 self.flush(dest, context);
230 dest.push(Property::Unparsed(
231 val.get_prefixed(context.targets, Feature::Transition),
232 ));
233 }
234 _ => return false,
235 }
236
237 true
238 }
239
240 fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
241 self.flush(dest, context);
242 }
243}
244
245impl<'i> TransitionHandler<'i> {
246 fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
247 if !self.has_any {
248 return;
249 }
250
251 self.has_any = false;
252
253 let mut properties = std::mem::take(&mut self.properties);
254 let mut durations = std::mem::take(&mut self.durations);
255 let mut delays = std::mem::take(&mut self.delays);
256 let mut timing_functions = std::mem::take(&mut self.timing_functions);
257
258 let rtl_properties = if let Some((properties, _)) = &mut properties {
259 expand_properties(properties, context)
260 } else {
261 None
262 };
263
264 if let (
265 Some((properties, property_prefixes)),
266 Some((durations, duration_prefixes)),
267 Some((delays, delay_prefixes)),
268 Some((timing_functions, timing_prefixes)),
269 ) = (&mut properties, &mut durations, &mut delays, &mut timing_functions)
270 {
271 let intersection = *property_prefixes & *duration_prefixes & *delay_prefixes & *timing_prefixes;
275 if !intersection.is_empty() {
276 macro_rules! get_transitions {
277 ($properties: ident) => {{
278 let mut transitions = SmallVec::with_capacity($properties.len());
281 let mut durations_iter = durations.iter().cycle().cloned();
282 let mut delays_iter = delays.iter().cycle().cloned();
283 let mut timing_iter = timing_functions.iter().cycle().cloned();
284 for property_id in $properties {
285 let duration = durations_iter.next().unwrap_or(Time::Seconds(0.0));
286 let delay = delays_iter.next().unwrap_or(Time::Seconds(0.0));
287 let timing_function = timing_iter.next().unwrap_or(EasingFunction::Ease);
288 let transition = Transition {
289 property: property_id.clone(),
290 duration,
291 delay,
292 timing_function,
293 };
294
295 for p in property_id.prefix().or_none() {
297 let mut t = transition.clone();
298 t.property = property_id.with_prefix(p);
299 transitions.push(t);
300 }
301 }
302 transitions
303 }};
304 }
305
306 let transitions: SmallVec<[Transition; 1]> = get_transitions!(properties);
307
308 if let Some(rtl_properties) = &rtl_properties {
309 let rtl_transitions = get_transitions!(rtl_properties);
310 context.add_logical_rule(
311 Property::Transition(transitions, intersection),
312 Property::Transition(rtl_transitions, intersection),
313 );
314 } else {
315 dest.push(Property::Transition(transitions.clone(), intersection));
316 }
317
318 property_prefixes.remove(intersection);
319 duration_prefixes.remove(intersection);
320 delay_prefixes.remove(intersection);
321 timing_prefixes.remove(intersection);
322 }
323 }
324
325 if let Some((properties, prefix)) = properties {
326 if !prefix.is_empty() {
327 if let Some(rtl_properties) = rtl_properties {
328 context.add_logical_rule(
329 Property::TransitionProperty(properties, prefix),
330 Property::TransitionProperty(rtl_properties, prefix),
331 );
332 } else {
333 dest.push(Property::TransitionProperty(properties, prefix));
334 }
335 }
336 }
337
338 if let Some((durations, prefix)) = durations {
339 if !prefix.is_empty() {
340 dest.push(Property::TransitionDuration(durations, prefix));
341 }
342 }
343
344 if let Some((delays, prefix)) = delays {
345 if !prefix.is_empty() {
346 dest.push(Property::TransitionDelay(delays, prefix));
347 }
348 }
349
350 if let Some((timing_functions, prefix)) = timing_functions {
351 if !prefix.is_empty() {
352 dest.push(Property::TransitionTimingFunction(timing_functions, prefix));
353 }
354 }
355
356 self.reset();
357 }
358
359 fn reset(&mut self) {
360 self.properties = None;
361 self.durations = None;
362 self.delays = None;
363 self.timing_functions = None;
364 }
365}
366
367#[inline]
368fn is_transition_property(property_id: &PropertyId) -> bool {
369 match property_id {
370 PropertyId::TransitionProperty(_)
371 | PropertyId::TransitionDuration(_)
372 | PropertyId::TransitionDelay(_)
373 | PropertyId::TransitionTimingFunction(_)
374 | PropertyId::Transition(_) => true,
375 _ => false,
376 }
377}
378
379fn merge_properties<'i: 'a, 'a>(val: impl Iterator<Item = &'a PropertyId<'i>>) -> SmallVec<[PropertyId<'i>; 1]> {
380 let mut merged_values = SmallVec::<[PropertyId<'_>; 1]>::with_capacity(val.size_hint().1.unwrap_or(1));
381 for p in val {
382 let without_prefix = p.with_prefix(VendorPrefix::empty());
383 if let Some(idx) = merged_values
384 .iter()
385 .position(|c| c.with_prefix(VendorPrefix::empty()) == without_prefix)
386 {
387 merged_values[idx].add_prefix(p.prefix());
388 } else {
389 merged_values.push(p.clone());
390 }
391 }
392
393 merged_values
394}
395
396fn expand_properties<'i>(
397 properties: &mut SmallVec<[PropertyId<'i>; 1]>,
398 context: &mut PropertyHandlerContext,
399) -> Option<SmallVec<[PropertyId<'i>; 1]>> {
400 let mut rtl_properties: Option<SmallVec<[PropertyId; 1]>> = None;
401 let mut i = 0;
402
403 macro_rules! replace {
404 ($properties: ident, $props: ident) => {
405 $properties[i] = $props[0].clone();
406 if $props.len() > 1 {
407 $properties.insert_many(i + 1, $props[1..].into_iter().cloned());
408 }
409 };
410 }
411
412 while i < properties.len() {
414 match get_logical_properties(&properties[i]) {
415 LogicalPropertyId::Block(feature, props) if context.should_compile_logical(feature) => {
416 replace!(properties, props);
417 if let Some(rtl_properties) = &mut rtl_properties {
418 replace!(rtl_properties, props);
419 }
420 i += props.len();
421 }
422 LogicalPropertyId::Inline(feature, ltr, rtl) if context.should_compile_logical(feature) => {
423 if rtl_properties.is_none() {
425 rtl_properties = Some(properties.clone());
426 }
427
428 replace!(properties, ltr);
429 if let Some(rtl_properties) = &mut rtl_properties {
430 replace!(rtl_properties, rtl);
431 }
432
433 i += ltr.len();
434 }
435 _ => {
436 properties[i].set_prefixes_for_targets(context.targets);
438
439 if let Some(property_id) = get_webkit_mask_property(&properties[i]) {
441 if context
442 .targets
443 .prefixes(VendorPrefix::None, Feature::MaskBorder)
444 .contains(VendorPrefix::WebKit)
445 {
446 properties.insert(i, property_id);
447 i += 1;
448 }
449 }
450
451 if let Some(rtl_properties) = &mut rtl_properties {
452 rtl_properties[i].set_prefixes_for_targets(context.targets);
453
454 if let Some(property_id) = get_webkit_mask_property(&rtl_properties[i]) {
455 if context
456 .targets
457 .prefixes(VendorPrefix::None, Feature::MaskBorder)
458 .contains(VendorPrefix::WebKit)
459 {
460 rtl_properties.insert(i, property_id);
461 }
462 }
463 }
464 i += 1;
465 }
466 }
467 }
468
469 rtl_properties
470}
471
472enum LogicalPropertyId {
473 None,
474 Block(compat::Feature, &'static [PropertyId<'static>]),
475 Inline(
476 compat::Feature,
477 &'static [PropertyId<'static>],
478 &'static [PropertyId<'static>],
479 ),
480}
481
482#[inline]
483fn get_logical_properties(property_id: &PropertyId) -> LogicalPropertyId {
484 use compat::Feature::*;
485 use LogicalPropertyId::*;
486 use PropertyId::*;
487 match property_id {
488 BlockSize => Block(LogicalSize, &[Height]),
489 InlineSize => Inline(LogicalSize, &[Width], &[Height]),
490 MinBlockSize => Block(LogicalSize, &[MinHeight]),
491 MaxBlockSize => Block(LogicalSize, &[MaxHeight]),
492 MinInlineSize => Inline(LogicalSize, &[MinWidth], &[MinHeight]),
493 MaxInlineSize => Inline(LogicalSize, &[MaxWidth], &[MaxHeight]),
494
495 InsetBlockStart => Block(LogicalInset, &[Top]),
496 InsetBlockEnd => Block(LogicalInset, &[Bottom]),
497 InsetInlineStart => Inline(LogicalInset, &[Left], &[Right]),
498 InsetInlineEnd => Inline(LogicalInset, &[Right], &[Left]),
499 InsetBlock => Block(LogicalInset, &[Top, Bottom]),
500 InsetInline => Block(LogicalInset, &[Left, Right]),
501 Inset => Block(LogicalInset, &[Top, Bottom, Left, Right]),
502
503 MarginBlockStart => Block(LogicalMargin, &[MarginTop]),
504 MarginBlockEnd => Block(LogicalMargin, &[MarginBottom]),
505 MarginInlineStart => Inline(LogicalMargin, &[MarginLeft], &[MarginRight]),
506 MarginInlineEnd => Inline(LogicalMargin, &[MarginRight], &[MarginLeft]),
507 MarginBlock => Block(LogicalMargin, &[MarginTop, MarginBottom]),
508 MarginInline => Block(LogicalMargin, &[MarginLeft, MarginRight]),
509
510 PaddingBlockStart => Block(LogicalPadding, &[PaddingTop]),
511 PaddingBlockEnd => Block(LogicalPadding, &[PaddingBottom]),
512 PaddingInlineStart => Inline(LogicalPadding, &[PaddingLeft], &[PaddingRight]),
513 PaddingInlineEnd => Inline(LogicalPadding, &[PaddingRight], &[PaddingLeft]),
514 PaddingBlock => Block(LogicalPadding, &[PaddingTop, PaddingBottom]),
515 PaddingInline => Block(LogicalPadding, &[PaddingLeft, PaddingRight]),
516
517 BorderBlockStart => Block(LogicalBorders, &[BorderTop]),
518 BorderBlockStartWidth => Block(LogicalBorders, &[BorderTopWidth]),
519 BorderBlockStartColor => Block(LogicalBorders, &[BorderTopColor]),
520 BorderBlockStartStyle => Block(LogicalBorders, &[BorderTopStyle]),
521
522 BorderBlockEnd => Block(LogicalBorders, &[BorderBottom]),
523 BorderBlockEndWidth => Block(LogicalBorders, &[BorderBottomWidth]),
524 BorderBlockEndColor => Block(LogicalBorders, &[BorderBottomColor]),
525 BorderBlockEndStyle => Block(LogicalBorders, &[BorderBottomStyle]),
526
527 BorderInlineStart => Inline(LogicalBorders, &[BorderLeft], &[BorderRight]),
528 BorderInlineStartWidth => Inline(LogicalBorders, &[BorderLeftWidth], &[BorderRightWidth]),
529 BorderInlineStartColor => Inline(LogicalBorders, &[BorderLeftColor], &[BorderRightColor]),
530 BorderInlineStartStyle => Inline(LogicalBorders, &[BorderLeftStyle], &[BorderRightStyle]),
531
532 BorderInlineEnd => Inline(LogicalBorders, &[BorderRight], &[BorderLeft]),
533 BorderInlineEndWidth => Inline(LogicalBorders, &[BorderRightWidth], &[BorderLeftWidth]),
534 BorderInlineEndColor => Inline(LogicalBorders, &[BorderRightColor], &[BorderLeftColor]),
535 BorderInlineEndStyle => Inline(LogicalBorders, &[BorderRightStyle], &[BorderLeftStyle]),
536
537 BorderBlock => Block(LogicalBorders, &[BorderTop, BorderBottom]),
538 BorderBlockColor => Block(LogicalBorders, &[BorderTopColor, BorderBottomColor]),
539 BorderBlockWidth => Block(LogicalBorders, &[BorderTopWidth, BorderBottomWidth]),
540 BorderBlockStyle => Block(LogicalBorders, &[BorderTopStyle, BorderBottomStyle]),
541
542 BorderInline => Block(LogicalBorders, &[BorderLeft, BorderRight]),
543 BorderInlineColor => Block(LogicalBorders, &[BorderLeftColor, BorderRightColor]),
544 BorderInlineWidth => Block(LogicalBorders, &[BorderLeftWidth, BorderRightWidth]),
545 BorderInlineStyle => Block(LogicalBorders, &[BorderLeftStyle, BorderRightStyle]),
546
547 BorderStartStartRadius => Inline(
550 LogicalBorders,
551 &[BorderTopLeftRadius(VendorPrefix::None)],
552 &[BorderTopRightRadius(VendorPrefix::None)],
553 ),
554 BorderStartEndRadius => Inline(
555 LogicalBorders,
556 &[BorderTopRightRadius(VendorPrefix::None)],
557 &[BorderTopLeftRadius(VendorPrefix::None)],
558 ),
559 BorderEndStartRadius => Inline(
560 LogicalBorders,
561 &[BorderBottomLeftRadius(VendorPrefix::None)],
562 &[BorderBottomRightRadius(VendorPrefix::None)],
563 ),
564 BorderEndEndRadius => Inline(
565 LogicalBorders,
566 &[BorderBottomRightRadius(VendorPrefix::None)],
567 &[BorderBottomLeftRadius(VendorPrefix::None)],
568 ),
569
570 _ => None,
571 }
572}