use crate as css;
use crate::CSSString;
use crate::css_values::easing::EasingFunction;
use crate::css_values::ident::{CustomIdent, DashedIdent, is_reserved_custom_ident};
use crate::css_values::length::{LengthPercentage, LengthPercentageOrAuto};
use crate::css_values::number::{CSSNumber, CSSNumberFns};
use crate::css_values::size::Size2D;
use crate::css_values::time::Time;
use crate::{Parser, PrintErr, Printer, SmallList};
use bun_core::strings;
pub type AnimationList = SmallList<Animation, 1>;
pub type AnimationNameList = SmallList<AnimationName, 1>;
pub struct Animation {
pub name: AnimationName,
pub duration: Time,
pub timing_function: EasingFunction,
pub iteration_count: AnimationIterationCount,
pub direction: AnimationDirection,
pub play_state: AnimationPlayState,
pub delay: Time,
pub fill_mode: AnimationFillMode,
pub timeline: AnimationTimeline,
}
impl Animation {
pub const VENDOR_PREFIX_MAP: &'static [(&'static str, bool)] = &[
("name", true),
("duration", true),
("timing_function", true),
("iteration_count", true),
("direction", true),
("play_state", true),
("delay", true),
("fill_mode", true),
];
pub fn parse(input: &mut Parser) -> css::Result<Self> {
let mut name: Option<AnimationName> = None;
let mut duration: Option<Time> = None;
let mut timing_function: Option<EasingFunction> = None;
let mut iteration_count: Option<AnimationIterationCount> = None;
let mut direction: Option<AnimationDirection> = None;
let mut play_state: Option<AnimationPlayState> = None;
let mut delay: Option<Time> = None;
let mut fill_mode: Option<AnimationFillMode> = None;
let mut timeline: Option<AnimationTimeline> = None;
loop {
if duration.is_none() {
if let Ok(value) = input.try_parse(Time::parse) {
duration = Some(value);
continue;
}
}
if timing_function.is_none() {
if let Ok(value) = input.try_parse(EasingFunction::parse) {
timing_function = Some(value);
continue;
}
}
if delay.is_none() {
if let Ok(value) = input.try_parse(Time::parse) {
delay = Some(value);
continue;
}
}
if iteration_count.is_none() {
if let Ok(value) = input.try_parse(AnimationIterationCount::parse) {
iteration_count = Some(value);
continue;
}
}
if direction.is_none() {
if let Ok(value) = input.try_parse(AnimationDirection::parse) {
direction = Some(value);
continue;
}
}
if fill_mode.is_none() {
if let Ok(value) = input.try_parse(AnimationFillMode::parse) {
fill_mode = Some(value);
continue;
}
}
if play_state.is_none() {
if let Ok(value) = input.try_parse(AnimationPlayState::parse) {
play_state = Some(value);
continue;
}
}
if name.is_none() {
if let Ok(value) = input.try_parse(AnimationName::parse) {
name = Some(value);
continue;
}
}
if timeline.is_none() {
if let Ok(value) = input.try_parse(AnimationTimeline::parse) {
timeline = Some(value);
continue;
}
}
break;
}
css::Result::Ok(Animation {
name: name.unwrap_or(AnimationName::None),
duration: duration.unwrap_or(Time::Seconds(0.0)),
timing_function: timing_function.unwrap_or(EasingFunction::Ease),
iteration_count: iteration_count.unwrap_or(AnimationIterationCount::Number(1.0)),
direction: direction.unwrap_or(AnimationDirection::Normal),
play_state: play_state.unwrap_or(AnimationPlayState::Running),
delay: delay.unwrap_or(Time::Seconds(0.0)),
fill_mode: fill_mode.unwrap_or(AnimationFillMode::None),
timeline: timeline.unwrap_or(AnimationTimeline::Auto),
})
}
pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
let name_str: Option<&[u8]> = match &self.name {
AnimationName::None => None,
AnimationName::Ident(ident) => Some(ident.v()),
AnimationName::String(s) => Some(unsafe { crate::arena_str(*s) }),
};
if let Some(name_str) = name_str {
if !self.duration.is_zero() || !self.delay.is_zero() {
self.duration.to_css(dest)?;
dest.write_char(b' ')?;
}
if !self.timing_function.is_ease() || EasingFunction::is_ident(name_str) {
self.timing_function.to_css(dest)?;
dest.write_char(b' ')?;
}
if !self.delay.is_zero() {
self.delay.to_css(dest)?;
dest.write_char(b' ')?;
}
if self.iteration_count != AnimationIterationCount::default()
|| strings::eql_case_insensitive_ascii(name_str, b"infinite", true)
{
self.iteration_count.to_css(dest)?;
dest.write_char(b' ')?;
}
if self.direction != AnimationDirection::default()
|| css::parse_utility::parse_string::<AnimationDirection>(
dest.arena,
name_str,
AnimationDirection::parse,
)
.is_ok()
{
self.direction.to_css(dest)?;
dest.write_char(b' ')?;
}
if self.fill_mode != AnimationFillMode::default()
|| (!strings::eql_case_insensitive_ascii(name_str, b"none", true)
&& css::parse_utility::parse_string::<AnimationFillMode>(
dest.arena,
name_str,
AnimationFillMode::parse,
)
.is_ok())
{
self.fill_mode.to_css(dest)?;
dest.write_char(b' ')?;
}
if self.play_state != AnimationPlayState::default()
|| css::parse_utility::parse_string::<AnimationPlayState>(
dest.arena,
name_str,
AnimationPlayState::parse,
)
.is_ok()
{
self.play_state.to_css(dest)?;
dest.write_char(b' ')?;
}
}
self.name.to_css(dest)?;
if !matches!(self.name, AnimationName::None)
&& self.timeline != AnimationTimeline::default()
{
dest.write_char(b' ')?;
self.timeline.to_css(dest)?;
}
Ok(())
}
}
#[derive(Clone, Copy)]
pub enum AnimationName {
None,
Ident(CustomIdent),
String(CSSString),
}
impl AnimationName {
pub fn eql(&self, other: &Self) -> bool {
match (self, other) {
(AnimationName::None, AnimationName::None) => true,
(AnimationName::Ident(a), AnimationName::Ident(b)) => {
use crate::generics::CssEql;
a.eql(b)
}
(AnimationName::String(a), AnimationName::String(b)) => {
unsafe { bun_core::eql(&**a, &**b) }
}
_ => false,
}
}
pub fn hash(&self, hasher: &mut bun_wyhash::Wyhash) {
match self {
AnimationName::None => hasher.update(&0u32.to_ne_bytes()),
AnimationName::Ident(i) => {
hasher.update(&1u32.to_ne_bytes());
i.hash(hasher);
}
AnimationName::String(s) => {
hasher.update(&2u32.to_ne_bytes());
hasher.update(unsafe { crate::arena_str(*s) });
}
}
}
#[inline]
pub fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self {
match self {
AnimationName::None => AnimationName::None,
AnimationName::Ident(i) => AnimationName::Ident(*i),
AnimationName::String(s) => AnimationName::String(*s),
}
}
pub fn parse(input: &mut Parser) -> css::Result<Self> {
if input
.try_parse(|i| i.expect_ident_matching(b"none"))
.is_ok()
{
return Ok(AnimationName::None);
}
if let Ok(s) = input.try_parse(|i| i.expect_string().map(std::ptr::from_ref::<[u8]>)) {
return Ok(AnimationName::String(s));
}
let ident = CustomIdent::parse(input)?;
Ok(AnimationName::Ident(ident))
}
pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
let css_module_animation_enabled = if let Some(css_module) = &dest.css_module {
css_module.config.animation
} else {
false
};
match self {
AnimationName::None => return dest.write_str("none"),
AnimationName::Ident(s) => {
let name: &[u8] = unsafe { crate::arena_str(s.v) };
if css_module_animation_enabled {
let arena = dest.arena;
let source_index = dest.loc.source_index;
if let Some(css_module) = &mut dest.css_module {
css_module.get_reference(arena, name, source_index);
}
}
return s.to_css_with_options(dest, css_module_animation_enabled);
}
AnimationName::String(s) => {
let name: &[u8] = unsafe { crate::arena_str(*s) };
if css_module_animation_enabled {
let arena = dest.arena;
let source_index = dest.loc.source_index;
if let Some(css_module) = &mut dest.css_module {
css_module.get_reference(arena, name, source_index);
}
}
if strings::eql_case_insensitive_ascii_check_length(name, b"none")
|| is_reserved_custom_ident(name)
{
return dest.serialize_string(name);
}
return dest.write_ident(name, css_module_animation_enabled);
}
}
}
}
#[derive(PartialEq)]
pub enum AnimationIterationCount {
Number(CSSNumber),
Infinite,
}
impl AnimationIterationCount {
pub fn parse(input: &mut Parser) -> css::Result<Self> {
if let Ok(v) = input.try_parse(CSSNumberFns::parse) {
return Ok(AnimationIterationCount::Number(v));
}
input.expect_ident_matching(b"infinite")?;
Ok(AnimationIterationCount::Infinite)
}
pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
match self {
AnimationIterationCount::Number(n) => CSSNumberFns::to_css(*n, dest),
AnimationIterationCount::Infinite => dest.write_str(b"infinite"),
}
}
pub fn default() -> AnimationIterationCount {
AnimationIterationCount::Number(1.0)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)]
pub enum AnimationDirection {
Normal,
Reverse,
Alternate,
AlternateReverse, }
impl AnimationDirection {
pub fn deep_clone(self) -> Self {
self
}
pub fn default() -> AnimationDirection {
AnimationDirection::Normal
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)]
pub enum AnimationPlayState {
Running,
Paused,
}
impl AnimationPlayState {
pub fn deep_clone(self) -> Self {
self
}
pub fn default() -> AnimationPlayState {
AnimationPlayState::Running
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)]
pub enum AnimationFillMode {
None,
Forwards,
Backwards,
Both,
}
impl AnimationFillMode {
pub fn deep_clone(self) -> Self {
self
}
pub fn default() -> AnimationFillMode {
AnimationFillMode::None
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)]
pub enum AnimationComposition {
Replace,
Add,
Accumulate,
}
impl AnimationComposition {
pub fn deep_clone(self) -> Self {
self
}
}
pub enum AnimationTimeline {
Auto,
None,
DashedIdent(DashedIdent),
Scroll(ScrollTimeline),
View(ViewTimeline),
}
impl AnimationTimeline {
pub fn parse(input: &mut Parser) -> css::Result<Self> {
let state = input.state();
if let Ok(ident) = input.expect_ident() {
if strings::eql_case_insensitive_ascii_check_length(ident, b"auto") {
return Ok(AnimationTimeline::Auto);
}
if strings::eql_case_insensitive_ascii_check_length(ident, b"none") {
return Ok(AnimationTimeline::None);
}
input.reset(&state);
} else {
input.reset(&state);
}
DashedIdent::parse(input).map(AnimationTimeline::DashedIdent)
}
pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
match self {
AnimationTimeline::Auto => dest.write_str(b"auto"),
AnimationTimeline::None => dest.write_str(b"none"),
AnimationTimeline::DashedIdent(d) => d.to_css(dest),
AnimationTimeline::Scroll(_) | AnimationTimeline::View(_) => {
unreachable!("ScrollTimeline / ViewTimeline have no toCss in spec (uninstantiated)")
}
}
}
pub fn default() -> AnimationTimeline {
AnimationTimeline::Auto
}
pub fn is_default(&self) -> bool {
matches!(self, AnimationTimeline::Auto)
}
}
impl PartialEq for AnimationTimeline {
fn eq(&self, other: &Self) -> bool {
use crate::generics::CssEql;
match (self, other) {
(AnimationTimeline::Auto, AnimationTimeline::Auto) => true,
(AnimationTimeline::None, AnimationTimeline::None) => true,
(AnimationTimeline::DashedIdent(a), AnimationTimeline::DashedIdent(b)) => a.eql(b),
(AnimationTimeline::Scroll(a), AnimationTimeline::Scroll(b)) => a == b,
(AnimationTimeline::View(a), AnimationTimeline::View(b)) => {
a.axis == b.axis && Size2D::eql(&a.inset, &b.inset)
}
_ => false,
}
}
}
#[derive(PartialEq, Eq)]
pub struct ScrollTimeline {
pub scroller: Scroller,
pub axis: ScrollAxis,
}
pub struct ViewTimeline {
pub axis: ScrollAxis,
pub inset: Size2D<LengthPercentageOrAuto>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)]
pub enum Scroller {
Root,
Nearest,
#[css("self")]
Self_,
}
impl Scroller {
pub fn deep_clone(self) -> Self {
self
}
pub fn default() -> Scroller {
Scroller::Nearest
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)]
pub enum ScrollAxis {
Block,
Inline,
X,
Y,
}
impl ScrollAxis {
pub fn deep_clone(self) -> Self {
self
}
pub fn default() -> ScrollAxis {
ScrollAxis::Block
}
}
pub struct AnimationRange {
pub start: AnimationRangeStart,
pub end: AnimationRangeEnd,
}
pub struct AnimationRangeStart {
pub v: AnimationAttachmentRange,
}
pub struct AnimationRangeEnd {
pub v: AnimationAttachmentRange,
}
pub enum AnimationAttachmentRange {
Normal,
LengthPercentage(LengthPercentage),
TimelineRange {
name: TimelineRangeName,
offset: LengthPercentage,
},
}
pub enum TimelineRangeName {
Cover,
Contain,
Entry,
Exit,
EntryCrossing,
ExitCrossing,
}