use core::hash::{Hash, Hasher};
use crate as css;
use crate::css_rules::Location;
use crate::css_values::ident::{CustomIdent, is_reserved_custom_ident};
use crate::css_values::percentage::Percentage;
use crate::{DeclarationBlock, PrintErr, Printer, VendorPrefix};
use super::ArrayList;
pub enum KeyframesName {
Ident(CustomIdent),
Custom(&'static [u8]),
}
pub type KeyframesNameHashMap<V> = bun_collections::ArrayHashMap<KeyframesName, V>;
impl Hash for KeyframesName {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
KeyframesName::Ident(ident) => state.write(ident.v()),
KeyframesName::Custom(s) => state.write(s),
}
}
}
impl PartialEq for KeyframesName {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(KeyframesName::Ident(a), KeyframesName::Ident(b)) => bun_core::eql(a.v(), b.v()),
(KeyframesName::Custom(a), KeyframesName::Custom(b)) => bun_core::eql(a, b),
_ => false,
}
}
}
impl Eq for KeyframesName {}
impl KeyframesName {
pub fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
use bun_core::strings;
#[inline]
fn write_ident<'a>(
dest: &mut Printer<'a>,
v: &'a [u8],
handle_css_module: bool,
) -> core::result::Result<(), PrintErr> {
dest.write_ident(v, handle_css_module)
}
let css_module_animation_enabled = if let Some(css_module) = &dest.css_module {
css_module.config.animation
} else {
false
};
match self {
KeyframesName::Ident(ident) => {
write_ident(
dest,
unsafe { crate::arena_str(ident.v) },
css_module_animation_enabled,
)?;
}
KeyframesName::Custom(s) => {
if strings::eql_case_insensitive_ascii_check_length(s, b"none")
|| is_reserved_custom_ident(s)
{
dest.serialize_string(s)?;
} else {
write_ident(dest, s, css_module_animation_enabled)?;
}
}
}
Ok(())
}
}
impl KeyframesName {
pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self {
match self {
Self::Ident(i) => Self::Ident(i.deep_clone(bump)),
Self::Custom(s) => Self::Custom(s),
}
}
}
impl KeyframesName {
pub fn parse(input: &mut css::Parser) -> css::Result<KeyframesName> {
use bun_core::strings;
let tok = input.next()?.clone();
match tok {
css::Token::Ident(s) => {
if strings::eql_case_insensitive_ascii_check_length(s, b"none")
|| is_reserved_custom_ident(s)
{
Err(input.new_unexpected_token_error(css::Token::Ident(s)))
} else {
Ok(KeyframesName::Ident(CustomIdent {
v: std::ptr::from_ref::<[u8]>(s),
}))
}
}
css::Token::QuotedString(s) => Ok(KeyframesName::Custom(s)),
t => Err(input.new_unexpected_token_error(t)),
}
}
}
pub enum KeyframeSelector {
Percentage(Percentage),
From,
To,
}
impl KeyframeSelector {
pub(crate) fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
match self {
KeyframeSelector::Percentage(p) => {
if dest.minify && p.v == 1.0 {
dest.write_str("to")?;
} else {
p.to_css(dest)?;
}
}
KeyframeSelector::From => {
if dest.minify {
dest.write_str("0%")?;
} else {
dest.write_str("from")?;
}
}
KeyframeSelector::To => {
dest.write_str("to")?;
}
}
Ok(())
}
}
impl KeyframeSelector {
pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self {
match self {
Self::Percentage(p) => Self::Percentage(*p),
Self::From => Self::From,
Self::To => Self::To,
}
}
}
impl KeyframeSelector {
pub(crate) fn parse(input: &mut css::Parser) -> css::Result<KeyframeSelector> {
if let Ok(p) = input.try_parse(Percentage::parse) {
return Ok(KeyframeSelector::Percentage(p));
}
let location = input.current_source_location();
let ident = input.expect_ident_cloned()?;
if bun_core::eql_case_insensitive_ascii_check_length(ident, b"from") {
Ok(KeyframeSelector::From)
} else if bun_core::eql_case_insensitive_ascii_check_length(ident, b"to") {
Ok(KeyframeSelector::To)
} else {
Err(location.new_unexpected_token_error(css::Token::Ident(ident)))
}
}
}
pub struct Keyframe {
pub selectors: ArrayList<KeyframeSelector>,
pub declarations: DeclarationBlock<'static>,
}
impl Keyframe {
pub(crate) fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
dest.write_comma_separated(self.selectors.iter(), |d, sel| sel.to_css(d))?;
super::decl_block_to_css(&self.declarations, dest)
}
}
impl Keyframe {
pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self {
Self {
selectors: self.selectors.iter().map(|s| s.deep_clone(bump)).collect(),
declarations: super::dc::decl_block_static(&self.declarations, bump),
}
}
}
pub struct KeyframesRule {
pub name: KeyframesName,
pub keyframes: ArrayList<Keyframe>,
pub vendor_prefix: VendorPrefix,
pub loc: Location,
}
impl KeyframesRule {
pub(crate) fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
let mut first_rule = true;
const PREFIXES: [VendorPrefix; 5] = [
VendorPrefix::WEBKIT,
VendorPrefix::MOZ,
VendorPrefix::MS,
VendorPrefix::O,
VendorPrefix::NONE,
];
for prefix in PREFIXES {
if self.vendor_prefix.contains(prefix) {
if first_rule {
first_rule = false;
} else {
if !dest.minify {
dest.write_char(b'\n')?; }
dest.newline()?;
}
dest.write_char(b'@')?;
super::vendor_prefix_to_css(prefix, dest)?;
dest.write_str("keyframes ")?;
self.name.to_css(dest)?;
dest.whitespace()?;
dest.write_char(b'{')?;
dest.indent();
dest.write_separated(
self.keyframes.iter(),
|d| {
if d.minify {
Ok(())
} else {
d.write_char(b'\n')
}
}, |d, kf| {
d.newline()?;
kf.to_css(d)
},
)?;
dest.dedent();
dest.newline()?;
dest.write_char(b'}')?;
}
}
Ok(())
}
}
impl KeyframesRule {
pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self {
Self {
name: self.name.deep_clone(bump),
keyframes: self.keyframes.iter().map(|k| k.deep_clone(bump)).collect(),
vendor_prefix: self.vendor_prefix,
loc: self.loc,
}
}
}
pub(crate) struct KeyframesListParser;
const _: () = {
use css::css_parser::{
AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser,
};
use css::{BasicParseErrorKind, Maybe, Parser, ParserOptions, ParserState, Result};
impl DeclarationParser for KeyframesListParser {
type Declaration = Keyframe;
fn parse_value(
_this: &mut Self,
name: &[u8],
input: &mut Parser,
) -> Result<Self::Declaration> {
Err(
input.new_error(BasicParseErrorKind::unexpected_token(css::Token::Ident(
unsafe { css::css_parser::src_str(name) },
))),
)
}
}
impl RuleBodyItemParser for KeyframesListParser {
fn parse_qualified(_this: &Self) -> bool {
true
}
fn parse_declarations(_this: &Self) -> bool {
false
}
}
impl AtRuleParser for KeyframesListParser {
type Prelude = ();
type AtRule = Keyframe;
fn parse_prelude(
_this: &mut Self,
name: &[u8],
input: &mut Parser,
) -> Result<Self::Prelude> {
Err(
input.new_error(BasicParseErrorKind::at_rule_invalid(std::ptr::from_ref::<
[u8],
>(name))),
)
}
fn parse_block(
_this: &mut Self,
_prelude: Self::Prelude,
_start: &ParserState,
input: &mut Parser,
) -> Result<Self::AtRule> {
Err(input.new_error(BasicParseErrorKind::at_rule_body_invalid))
}
fn rule_without_block(
_this: &mut Self,
_prelude: Self::Prelude,
_start: &ParserState,
) -> Maybe<Self::AtRule, ()> {
Err(())
}
}
impl QualifiedRuleParser for KeyframesListParser {
type Prelude = ArrayList<KeyframeSelector>;
type QualifiedRule = Keyframe;
fn parse_prelude(_this: &mut Self, input: &mut Parser) -> Result<Self::Prelude> {
input.parse_comma_separated(KeyframeSelector::parse)
}
fn parse_block(
_this: &mut Self,
prelude: Self::Prelude,
_start: &ParserState,
input: &mut Parser,
) -> Result<Self::QualifiedRule> {
let options = ParserOptions::default(None);
let declarations = DeclarationBlock::parse(input, &options)?;
Ok(Keyframe {
selectors: prelude,
declarations,
})
}
}
};