{"version":3,"file":"scroll-timeline.js","sources":["../src/tokenizer.js","../src/utils.js","../src/simplify-calculation.js","../src/numeric-values.js","../src/proxy-cssom.js","../src/scroll-timeline-base.js","../src/proxy-animation.js","../src/scroll-timeline-css-parser.js","../src/scroll-timeline-css.js","../src/index.js","../src/init-polyfill.js"],"sourcesContent":["\nexport class Token {}\n\n// The output of tokenization step is a stream of zero or more of the following tokens: , ,\n// , , , , , , ,\n// , , , , , , ,\n// , , <[-token>, <]-token>, <(-token>, <)-token>, <{-token>, and <}-token>.\nexport class IdentToken extends Token {\n value;\n constructor(value) {\n super();\n this.value = value;\n }\n}\n\nexport class FunctionToken extends Token {\n value;\n constructor(value) {\n super();\n this.value = value;\n }\n}\n\nexport class AtKeywordToken extends Token {\n value;\n constructor(value) {\n super();\n this.value = value;\n }\n}\n\nexport class HashToken extends Token {\n type;\n value;\n constructor(value, type = 'unrestricted') {\n super();\n this.value = value;\n this.type = type;\n }\n}\n\nexport class StringToken extends Token {\n value;\n constructor(value) {\n super();\n this.value = value;\n }\n}\n\nexport class BadStringToken extends Token {}\n\nexport class UrlToken extends Token {\n value;\n constructor(value) {\n super();\n this.value = value;\n }\n}\n\nexport class BadUrlToken extends Token {}\n\nexport class DelimToken extends Token {\n value;\n constructor(value) {\n super();\n this.value = value;\n }\n}\n\nexport class NumberToken extends Token {\n value;\n type;\n constructor(value, type = \"integer\") {\n super();\n this.value = value;\n this.type = type;\n }\n}\n\nexport class PercentageToken extends Token {\n value;\n constructor(value) {\n super();\n this.value = value;\n }\n}\n\nexport class DimensionToken extends Token {\n value;\n type;\n unit;\n constructor(value, type, unit) {\n super();\n this.value = value;\n this.type = type;\n this.unit = unit;\n }\n}\n\nexport class WhitespaceToken extends Token {}\n\nexport class CDOToken extends Token {}\n\nexport class CDCToken extends Token {}\n\nexport class ColonToken extends Token {}\n\nexport class SemicolonToken extends Token {}\n\nexport class CommaToken extends Token {}\n\nexport class LeftSquareBracketToken extends Token {}\n\nexport class RightSquareBracketToken extends Token {}\n\nexport class LeftParenthesisToken extends Token {}\n\nexport class RightParenthesisToken extends Token {}\n\nexport class LeftCurlyBracketToken extends Token {}\n\nexport class RightCurlyBracketToken extends Token {}\n\nclass InputStream {\n input\n index = 0;\n constructor(input) {\n this.input = input;\n }\n\n consume() {\n const codePoint = this.input.codePointAt(this.index);\n if (typeof codePoint !== 'undefined') {\n this.index += String.fromCodePoint(codePoint).length;\n }\n return codePoint;\n }\n\n reconsume(codePoint) {\n if (typeof codePoint !== 'undefined') {\n this.index -= String.fromCodePoint(codePoint).length\n }\n }\n\n peek() {\n const codePoints = []\n let position = this.index\n for (let i = 0; i < 3 && position < this.input.length; i++) {\n const nextCodePoint = this.input.codePointAt(position);\n codePoints.push(nextCodePoint);\n position += String.fromCodePoint(nextCodePoint).length;\n }\n return codePoints;\n }\n}\n\nfunction isNewline(codePoint) {\n // U+000A LINE FEED.\n return codePoint === 0x000A;\n}\nfunction isWhitespace(codePoint) {\n // A newline, U+0009 CHARACTER TABULATION, or U+0020 SPACE.\n return isNewline(codePoint) || codePoint === 0x2000 || codePoint === 0x0020;\n}\n\nfunction isDigit(codePoint) {\n // A code point between U+0030 DIGIT ZERO (0) and U+0039 DIGIT NINE (9) inclusive.\n return codePoint >= 0x0030 && codePoint <=0x0039;\n}\n\nfunction isHexDigit(codePoint) {\n // A digit, or a code point between U+0041 LATIN CAPITAL LETTER A (A) and U+0046 LATIN CAPITAL LETTER F (F) inclusive,\n // or a code point between U+0061 LATIN SMALL LETTER A (a) and U+0066 LATIN SMALL LETTER F (f) inclusive.\n return isDigit(codePoint) ||\n (codePoint >= 0x0041 && codePoint <= 0x0046) ||\n (codePoint >= 0x0061 && codePoint <= 0x0066);\n}\n\nfunction isUppercaseLetter(codePoint) {\n // A code point between U+0041 LATIN CAPITAL LETTER A (A) and U+005A LATIN CAPITAL LETTER Z (Z) inclusive.\n return codePoint >= 0x0041 && codePoint <= 0x005A;\n}\n\nfunction isLowercaseLetter(codePoint) {\n // A code point between U+0061 LATIN SMALL LETTER A (a) and U+007A LATIN SMALL LETTER Z (z) inclusive.\n return codePoint >= 0x0061 && codePoint <= 0x007A;\n}\n\nfunction isLetter(codePoint) {\n // An uppercase letter or a lowercase letter.\n return isUppercaseLetter(codePoint) || isLowercaseLetter(codePoint);\n}\n\nfunction nonASCIICodePoint(codePoint) {\n // A code point with a value equal to or greater than U+0080 .\n return codePoint >= 0x0080;\n}\nfunction isIdentStartCodePoint(codePoint) {\n // A letter, a non-ASCII code point, or U+005F LOW LINE (_).\n return isLetter(codePoint) || nonASCIICodePoint(codePoint) || codePoint === 0x005F;\n}\n\nfunction isIdentCodePoint(codePoint) {\n // An ident-start code point, a digit, or U+002D HYPHEN-MINUS (-).\n return isIdentStartCodePoint(codePoint) || isDigit(codePoint) || codePoint === 0x002D;\n}\n\nfunction isNonPrintableCodePoint(codePoint) {\n // A code point between U+0000 NULL and U+0008 BACKSPACE inclusive, or U+000B LINE TABULATION,\n // or a code point between U+000E SHIFT OUT and U+001F INFORMATION SEPARATOR ONE inclusive, or U+007F DELETE.\n return (codePoint >= 0x0000 && codePoint <= 0x0008) || codePoint === 0x000B ||\n (codePoint >= 0x000E && codePoint <= 0x001F) || codePoint === 0x007F;\n}\n\nfunction validEscape(firstCodePoint, secondCodePoint) {\n // If the first code point is not U+005C REVERSE SOLIDUS (\\), return false.\n // Otherwise, if the second code point is a newline, return false.\n // Otherwise, return true.\n return firstCodePoint === 0x005C && !isNewline(secondCodePoint);\n}\n\nfunction startsIdentSequence(firstCodePoint, secondCodePoint, thirdCodePoint) {\n // Look at the first code point:\n if (firstCodePoint === 0x002D) {\n // U+002D HYPHEN-MINUS\n // If the second code point is an ident-start code point or a U+002D HYPHEN-MINUS,\n // or the second and third code points are a valid escape, return true. Otherwise, return false.\n return isIdentStartCodePoint(secondCodePoint) || secondCodePoint === 0x002D ||\n validEscape(secondCodePoint, thirdCodePoint);\n } else if (isIdentStartCodePoint(firstCodePoint)) {\n // ident-start code point\n // Return true.\n return true;\n } else if (firstCodePoint === 0x005C) {\n // U+005C REVERSE SOLIDUS (\\)\n // If the first and second code points are a valid escape, return true. Otherwise, return false.\n return validEscape(firstCodePoint, secondCodePoint);\n } else {\n // anything else\n // Return false.\n return false;\n }\n}\n\nfunction startsNumber(firstCodePoint, secondCodePoint, thirdCodePoint) {\n // https://www.w3.org/TR/css-syntax-3/#check-if-three-code-points-would-start-a-number\n // Look at the first code point:\n\n if (firstCodePoint === 0x002B || firstCodePoint === 0x002D) {\n // U+002B PLUS SIGN (+)\n // U+002D HYPHEN-MINUS (-)\n // If the second code point is a digit, return true.\n // Otherwise, if the second code point is a U+002E FULL STOP (.) and the third code point is a digit, return true.\n //\n // Otherwise, return false.\n return isDigit(secondCodePoint) || (secondCodePoint === 0x002E && isDigit(thirdCodePoint));\n } else if (firstCodePoint === 0x002E) {\n // U+002E FULL STOP (.)\n // If the second code point is a digit, return true. Otherwise, return false.\n return isDigit(secondCodePoint);\n } else {\n // digit\n // Return true.\n // anything else\n // Return false.\n return isDigit(firstCodePoint);\n }\n}\n\n/**\n * Consume an escaped code point\n * https://www.w3.org/TR/css-syntax-3/#consume-an-escaped-code-point\n *\n * @param {InputStream} input\n * @return number\n */\nfunction consumeEscapedCodePoint(input) {\n // Consume the next input code point.\n const codePoint = input.consume();\n if (isHexDigit(codePoint)) {\n let digits = [codePoint];\n // hex digit\n // Consume as many hex digits as possible, but no more than 5. Note that this means 1-6 hex digits have been\n // consumed in total.\n while(isHexDigit(...input.peek()) && digits.length < 5) {\n digits.push(input.consume());\n }\n\n // If the next input code point is whitespace, consume it as well.\n if (isWhitespace(...input.peek())) {\n input.consume();\n }\n\n // Interpret the hex digits as a hexadecimal number. If this number is zero, or is for a surrogate, or is greater\n // than the maximum allowed code point, return U+FFFD REPLACEMENT CHARACTER (�). Otherwise, return the code point\n // with that value.\n const number = parseInt(String.fromCodePoint(...digits), 16);\n if (number === 0 || number > 0x10FFFF) {\n return 0xFFFD;\n } else {\n return number;\n }\n } else if (typeof codePoint === 'undefined') {\n // EOF\n // This is a parse error. Return U+FFFD REPLACEMENT CHARACTER (�).\n return 0xFFFD;\n } else {\n // anything else\n // Return the current input code point.\n return codePoint;\n }\n}\n\n\n/**\n * Consume a string token\n * https://www.w3.org/TR/css-syntax-3/#consume-a-string-token\n *\n * @param {InputStream} input\n * @param {number} endingCodePoint\n */\nfunction consumeStringToken(input, endingCodePoint) {\n const stringToken = new StringToken('');\n\n while (true) {\n // Repeatedly consume the next input code point from the stream:\n const codePoint = input.consume();\n if (codePoint === endingCodePoint) {\n // ending code point\n // Return the .\n return stringToken;\n } else if (typeof codePoint === 'undefined') {\n // EOF\n // This is a parse error. Return the .\n return stringToken\n } else if (codePoint === 0x00A) {\n // newline\n // This is a parse error. Reconsume the current input code point, create a , and return it.\n input.reconsume(codePoint);\n return new BadStringToken();\n } else if (codePoint === 0x005C) {\n // U+005C REVERSE SOLIDUS (\\)\n const nextCodePoint = input.peek()[0];\n if (typeof nextCodePoint === 'undefined') {\n // If the next input code point is EOF, do nothing.\n } else if (isNewline(nextCodePoint)) {\n // Otherwise, if the next input code point is a newline, consume it.\n input.consume();\n } else {\n // Otherwise, (the stream starts with a valid escape) consume an escaped code point and\n // append the returned code point to the ’s value.\n stringToken.value += String.fromCodePoint(consumeEscapedCodePoint(input));\n }\n } else {\n // anything else\n // Append the current input code point to the ’s value.\n stringToken.value += String.fromCodePoint(codePoint);\n }\n }\n}\n\n/**\n * Consume ident sequence\n * https://www.w3.org/TR/css-syntax-3/#consume-name\n *\n * @param {InputStream} input\n */\nfunction consumeIdentSequence(input) {\n // Let result initially be an empty string.\n let result = '';\n\n // Repeatedly consume the next input code point from the stream:\n while (true) {\n const codePoint = input.consume();\n if (isIdentCodePoint(codePoint)) {\n // ident code point\n // Append the code point to result.\n result += String.fromCodePoint(codePoint);\n } else if (validEscape(...input.peek())) {\n // the stream starts with a valid escape\n // Consume an escaped code point. Append the returned code point to result.\n result += String.fromCodePoint(consumeEscapedCodePoint(input));\n } else {\n // anything else\n // Reconsume the current input code point. Return result.\n input.reconsume(codePoint);\n return result;\n }\n }\n}\n\n/**\n * Consume a number\n * https://www.w3.org/TR/css-syntax-3/#consume-a-number\n *\n * @param {InputStream} input\n */\nfunction consumeNumber(input) {\n // Execute the following steps in order:\n //\n // Initially set type to \"integer\". Let repr be the empty string.\n let type = 'integer';\n let repr = '';\n\n // If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-), consume it and append it to repr.\n if ([0x002B, 0x002D].includes(input.peek()[0])) {\n repr += String.fromCodePoint(input.consume());\n }\n\n // While the next input code point is a digit, consume it and append it to repr.\n while(isDigit(...input.peek())) {\n repr += String.fromCodePoint(input.consume());\n }\n\n // If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then:\n // Consume them.\n // Append them to repr.\n // Set type to \"number\".\n // While the next input code point is a digit, consume it and append it to repr.\n if (input.peek()[0] === 0x002E && isDigit(input.peek()[1])) {\n repr += String.fromCodePoint(input.consume(), input.consume());\n type = 'number';\n while(isDigit(...input.peek())) {\n repr += String.fromCodePoint(input.consume());\n }\n }\n\n // If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E) or U+0065 LATIN SMALL LETTER E (e),\n // optionally followed by U+002D HYPHEN-MINUS (-) or U+002B PLUS SIGN (+),\n // followed by a digit, then:\n // Consume them.\n // Append them to repr.\n // Set type to \"number\".\n // While the next input code point is a digit, consume it and append it to repr.\n if ([0x0045, 0x0065].includes(input.peek()[0])) {\n if ([0x002D, 0x002B].includes(input.peek()[1]) && isDigit(input.peek()[2])) {\n repr += String.fromCodePoint(input.consume(), input.consume(), input.consume());\n type = 'number';\n } else if (isDigit(input.peek()[1])) {\n repr += String.fromCodePoint(input.consume(), input.consume());\n type = 'number';\n }\n }\n\n // Convert repr to a number, and set the value to the returned value.\n const value = parseFloat(repr);\n // Return value and type.\n return { value, type };\n}\n\n/**\n * Consume a numeric token\n * https://www.w3.org/TR/css-syntax-3/#consume-a-numeric-token\n *\n * @param {InputStream} input\n */\nfunction consumeNumericToken(input) {\n // Consume a number and let number be the result.\n let number = consumeNumber(input);\n // If the next 3 input code points would start an ident sequence, then:\n if (startsIdentSequence(...input.peek())) {\n // Create a with the same value and type flag as number, and a unit set initially to the empty string.\n // Consume an ident sequence. Set the ’s unit to the returned value.\n // Return the .\n return new DimensionToken(number.value, number.type, consumeIdentSequence(input));\n } else if (input.peek()[0] === 0x0025) {\n // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it.\n // Create a with the same value as number, and return it.\n input.consume();\n return new PercentageToken(number.value);\n } else {\n // Otherwise, create a with the same value and type flag as number, and return it.\n return new NumberToken(number.value, number.type);\n }\n}\n\n/**\n * Consume remnants of a bad url\n * https://www.w3.org/TR/css-syntax-3/#consume-the-remnants-of-a-bad-url\n * @param {InputStream} input\n */\nfunction consumeRemnantsOfBadUrl(input) {\n // Repeatedly consume the next input code point from the stream:\n while (true) {\n const codePoint = input.consume();\n if (codePoint === 0x0029 || typeof codePoint === 'undefined') {\n // U+0029 RIGHT PARENTHESIS ())\n // EOF\n // Return.\n return;\n } else if (validEscape(...input.peek())) {\n // the input stream starts with a valid escape\n // Consume an escaped code point. This allows an escaped right parenthesis (\"\\)\") to be encountered without\n // ending the . This is otherwise identical to the \"anything else\" clause.\n consumeEscapedCodePoint(input);\n }\n // anything else\n // Do nothing.\n }\n}\n\n/**\n * Consume URL token\n * https://www.w3.org/TR/css-syntax-3/#consume-a-url-token\n * @param {InputStream} input\n */\nfunction consumeUrlToken(input) {\n // Initially create a with its value set to the empty string.\n const urlToken = new UrlToken('');\n\n // Consume as much whitespace as possible.\n while(isWhitespace(...input.peek())) {\n input.consume();\n }\n\n // Repeatedly consume the next input code point from the stream:\n while (true) {\n const codePoint = input.consume();\n if (codePoint === 0x0029) {\n\n // U+0029 RIGHT PARENTHESIS ())\n // Return the .\n return urlToken;\n } else if (typeof codePoint === 'undefined') {\n // EOF\n // This is a parse error. Return the .\n return urlToken;\n } else if (isWhitespace(codePoint)) {\n // whitespace\n // Consume as much whitespace as possible.\n while(isWhitespace(...input.peek())) {\n input.consume();\n }\n if (input.peek()[0] === 0x0029 || typeof input.peek()[0] === 'undefined') {\n // If the next input code point is U+0029 RIGHT PARENTHESIS ()) or EOF,\n // consume it and return the (if EOF was encountered, this is a parse error);\n input.consume();\n return urlToken;\n } else {\n // otherwise, consume the remnants of a bad url, create a , and return it.\n consumeRemnantsOfBadUrl(input);\n return new BadUrlToken();\n }\n } else if ([0x0022, 0x0027, 0x0028].includes(codePoint) || isNonPrintableCodePoint(codePoint)) {\n // U+0022 QUOTATION MARK (\")\n // U+0027 APOSTROPHE (')\n // U+0028 LEFT PARENTHESIS (()\n // non-printable code point\n // This is a parse error. Consume the remnants of a bad url, create a , and return it.\n consumeRemnantsOfBadUrl(input);\n return new BadUrlToken();\n } else if (codePoint === 0x005C) {\n // U+005C REVERSE SOLIDUS (\\)\n if (validEscape(...input.peek())) {\n // If the stream starts with a valid escape,\n // consume an escaped code point and append the returned code point to the ’s value.\n urlToken.value += consumeEscapedCodePoint(input);\n } else {\n // Otherwise, this is a parse error. Consume the remnants of a bad url, create a , and return it.\n consumeRemnantsOfBadUrl(input);\n return new BadUrlToken();\n }\n } else {\n // anything else\n // Append the current input code point to the ’s value.\n urlToken.value += String.fromCodePoint(codePoint);\n }\n }\n}\n\n/**\n * Consume ident like token\n * https://www.w3.org/TR/css-syntax-3/#consume-an-ident-like-token\n *\n * @param {InputStream} input\n */\nfunction consumeIdentLikeToken(input) {\n // Consume an ident sequence, and let string be the result.\n const str = consumeIdentSequence(input);\n if (str.match(/url/i) && input.peek()[0] === 0x0028) {\n // If string’s value is an ASCII case-insensitive match for \"url\",\n // and the next input code point is U+0028 LEFT PARENTHESIS ((), consume it.\n input.consume();\n // While the next two input code points are whitespace, consume the next input code point.\n while(isWhitespace(input.peek()[0]) && isWhitespace(input.peek()[1])) {\n input.consume();\n }\n\n if ([0x0022, 0x0027].includes(input.peek()[0]) ||\n (isWhitespace(input.peek()[0]) && [0x0022, 0x0027].includes(input.peek()[1]))) {\n // If the next one or two input code points are U+0022 QUOTATION MARK (\"), U+0027 APOSTROPHE ('),\n // or whitespace followed by U+0022 QUOTATION MARK (\") or U+0027 APOSTROPHE ('),\n // then create a with its value set to string and return it.\n return new FunctionToken(str);\n } else {\n // Otherwise, consume a url token, and return it.\n return consumeUrlToken(input);\n }\n } else if (input.peek()[0] === 0x0028) {\n // Otherwise, if the next input code point is U+0028 LEFT PARENTHESIS ((), consume it.\n // Create a with its value set to string and return it.\n input.consume();\n return new FunctionToken(str);\n } else {\n // Otherwise, create an with its value set to string and return it.\n return new IdentToken(str);\n }\n}\n/**\n * Consume a token.\n *\n * https://www.w3.org/TR/css-syntax-3/#consume-a-token\n *\n * @param {InputStream} input\n */\nfunction consumeToken(input) {\n // Consume the next input code point\n const codePoint = input.consume()\n const lookahead = input.peek()\n if (isWhitespace(codePoint)) {\n // whitespace\n // Consume as much whitespace as possible. Return a .\n while(isWhitespace(...input.peek())) {\n input.consume();\n }\n return new WhitespaceToken();\n } else if (codePoint === 0x0022) {\n // U+0022 QUOTATION MARK (\")\n // Consume a string token and return it.\n return consumeStringToken(input, codePoint);\n } else if (codePoint === 0x0023) {\n // U+0023 NUMBER SIGN (#)\n // If the next input code point is an ident code point or the next two input code points are a valid escape, then:\n // Create a .\n // If the next 3 input code points would start an ident sequence, set the ’s type flag to \"id\".\n // Consume an ident sequence, and set the ’s value to the returned string.\n // Return the .\n // Otherwise, return a with its value set to the current input code point.\n if (isIdentCodePoint(lookahead[0]) || validEscape(...lookahead)) {\n const hashToken = new HashToken();\n if (startsIdentSequence(...lookahead)) {\n hashToken.type = 'id';\n }\n hashToken.value = consumeIdentSequence(input);\n return hashToken;\n } else {\n return new DelimToken(String.fromCodePoint(codePoint));\n }\n } else if (codePoint === 0x0027) {\n // U+0027 APOSTROPHE (')\n // Consume a string token and return it.\n return consumeStringToken(input, codePoint);\n } else if (codePoint === 0x0028) {\n // U+0028 LEFT PARENTHESIS (()\n // Return a <(-token>.\n return new LeftParenthesisToken();\n } else if (codePoint === 0x0029) {\n // U+0029 RIGHT PARENTHESIS ())\n // Return a <)-token>.\n return new RightParenthesisToken();\n } else if (codePoint === 0x002B) {\n // U+002B PLUS SIGN (+)\n // If the input stream starts with a number, reconsume the current input code point, consume a numeric token,\n // and return it.\n // Otherwise, return a with its value set to the current input code point.\n if (startsNumber(...lookahead)) {\n input.reconsume(codePoint);\n return consumeNumericToken(input);\n } else {\n return new DelimToken(String.fromCodePoint(codePoint));\n }\n } else if (codePoint === 0x002C) {\n // U+002C COMMA (,)\n // Return a .\n return new CommaToken();\n } else if (codePoint === 0x002D) {\n // U+002D HYPHEN-MINUS (-)\n if (startsNumber(...input.peek())) {\n // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it.\n input.reconsume(codePoint);\n return consumeNumericToken(input);\n } else if (input.peek()[0] === 0x002D && input.peek()[1] === 0x003E) {\n // Otherwise, if the next 2 input code points are U+002D HYPHEN-MINUS U+003E GREATER-THAN SIGN (->), consume them and return a .\n input.consume();\n input.consume();\n return new CDCToken();\n } else if (startsIdentSequence(...input.peek())) {\n // Otherwise, if the input stream starts with an ident sequence, reconsume the current input code point, consume an ident-like token, and return it.\n input.reconsume(codePoint);\n return consumeIdentLikeToken(input);\n } else {\n // Otherwise, return a with its value set to the current input code point.\n return new DelimToken(String.fromCodePoint(codePoint));\n }\n } else if (codePoint === 0x002E) {\n // U+002E FULL STOP (.)\n if (startsNumber(...input.peek())) {\n // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it.\n input.reconsume(codePoint);\n return consumeNumericToken(input);\n } else {\n // Otherwise, return a with its value set to the current input code point.\n return new DelimToken(String.fromCodePoint(codePoint));\n }\n } else if (codePoint === 0x003A) {\n // U+003A COLON (:)\n // Return a .\n return new ColonToken();\n } else if (codePoint === 0x003B) {\n // U+003B SEMICOLON (;)\n // Return a .\n return new SemicolonToken();\n } else if (codePoint === 0x003C) {\n // U+003C LESS-THAN SIGN (<)\n if (lookahead[0] === 0x0021 && lookahead[1] === 0x002D && lookahead[2] === 0x002D) {\n // If the next 3 input code points are U+0021 EXCLAMATION MARK U+002D HYPHEN-MINUS U+002D HYPHEN-MINUS (!--), consume them and return a .\n input.consume();\n input.consume();\n input.consume();\n return new CDOToken();\n } else {\n // Otherwise, return a with its value set to the current input code point.\n return new DelimToken(String.fromCodePoint(codePoint));\n }\n } else if (codePoint === 0x0040) {\n // U+0040 COMMERCIAL AT (@)\n if (startsIdentSequence(...lookahead)) {\n // If the next 3 input code points would start an ident sequence, consume an ident sequence,\n // create an with its value set to the returned value, and return it.\n return new AtKeywordToken(consumeIdentSequence(input));\n } else {\n // Otherwise, return a with its value set to the current input code point.\n return new DelimToken(String.fromCodePoint(codePoint));\n }\n } else if (codePoint === 0x005B) {\n // U+005B LEFT SQUARE BRACKET ([)\n // Return a <[-token>.\n return new LeftSquareBracketToken();\n } else if (codePoint === 0x005C) {\n // U+005C REVERSE SOLIDUS (\\)\n if (validEscape(...lookahead)) {\n // If the input stream starts with a valid escape, reconsume the current input code point, consume an ident-like token, and return it.\n input.reconsume(codePoint);\n return consumeIdentLikeToken(input);\n } else {\n // Otherwise, this is a parse error. Return a with its value set to the current input code point.\n return new DelimToken(String.fromCodePoint(codePoint));\n }\n } else if (codePoint === 0x005D) {\n // U+005D RIGHT SQUARE BRACKET (])\n // Return a <]-token>.\n return new RightSquareBracketToken();\n } else if (codePoint === 0x007B) {\n // U+007B LEFT CURLY BRACKET ({)\n // Return a <{-token>.\n return new LeftCurlyBracketToken();\n } else if (codePoint === 0x007D) {\n // U+007D RIGHT CURLY BRACKET (})\n // Return a <}-token>.\n return new RightCurlyBracketToken();\n } else if (isDigit(codePoint)) {\n // digit\n // Reconsume the current input code point, consume a numeric token, and return it.\n input.reconsume(codePoint);\n return consumeNumericToken(input);\n } else if (isIdentStartCodePoint(codePoint)) {\n // ident-start code point\n // Reconsume the current input code point, consume an ident-like token, and return it.\n input.reconsume(codePoint);\n return consumeIdentLikeToken(input);\n } else if (typeof codePoint === 'undefined') {\n // EOF\n // Return an .\n return undefined;\n } else {\n // anything else\n // Return a with its value set to the current input code point.\n return new DelimToken(String.fromCodePoint(codePoint));\n }\n}\n\n/**\n * Tokenize a string into an array of CSS tokens.\n * @param {string} str\n */\nexport function tokenizeString(str) {\n const input = new InputStream(str);\n // To tokenize a stream of code points into a stream of CSS tokens input, repeatedly consume a token from input\n // until an is reached, pushing each of the returned tokens into a stream.\n const tokens = [];\n while (true) {\n const token = consumeToken(input);\n if (typeof token === 'undefined') {\n return tokens;\n } else {\n tokens.push(token);\n }\n }\n}","const canonicalUnits = new Set([\"px\", \"deg\", \"s\", \"hz\", \"dppx\", \"number\", \"fr\"]);\n\nexport function isCanonical(unit) {\n return canonicalUnits.has(unit.toLowerCase());\n}\n\nexport function normalizeAxis(axis, computedStyle) {\n if (['x','y'].includes(axis)) return axis;\n\n if (!computedStyle) {\n throw new Error('To determine the normalized axis the computedStyle of the source is required.');\n }\n\n const horizontalWritingMode = computedStyle.writingMode == 'horizontal-tb';\n if (axis === \"block\") {\n axis = horizontalWritingMode ? \"y\" : \"x\";\n } else if (axis === \"inline\") {\n axis = horizontalWritingMode ? \"x\" : \"y\";\n } else {\n throw new TypeError(`Invalid axis “${axis}”`);\n }\n\n return axis;\n}\n\n/**\n * Split an input string into a list of individual component value strings,\n * so that each can be handled as a keyword or parsed with `CSSNumericValue.parse()`;\n *\n * Examples:\n * splitIntoComponentValues('cover'); // ['cover']\n * splitIntoComponentValues('auto 0%'); // ['auto', '100%']\n * splitIntoComponentValues('calc(0% + 50px) calc(100% - 50px)'); // ['calc(0% + 50px)', 'calc(100% - 50px)']\n * splitIntoComponentValues('1px 2px').map(val => CSSNumericValue.parse(val)) // [new CSSUnitValue(1, 'px'), new CSSUnitValue(2, 'px')]\n *\n * @param {string} input\n * @return {string[]}\n */\nexport function splitIntoComponentValues(input) {\n const res = [];\n let i = 0;\n\n function consumeComponentValue() {\n let level = 0;\n const startIndex = i;\n while (i < input.length) {\n const nextChar = input.slice(i, i + 1);\n if (/\\s/.test(nextChar) && level === 0) {\n break;\n } else if (nextChar === '(') {\n level += 1;\n } else if (nextChar === ')') {\n level -= 1;\n if (level === 0) {\n // Consume the next character and break\n i++;\n break;\n }\n }\n i++;\n }\n return input.slice(startIndex, i);\n }\n\n function consumeWhitespace() {\n while (/\\s/.test(input.slice(i, i + 1))) {\n i++;\n }\n }\n\n while(i < input.length) {\n const nextChar = input.slice(i, i + 1);\n if (/\\s/.test(nextChar)) {\n consumeWhitespace();\n } else {\n res.push(consumeComponentValue());\n }\n }\n return res;\n}","import {isCanonical} from \"./utils\";\n\n/**\n * @typedef {{percentageReference: CSSUnitValue, fontSize?: CSSUnitValue}} Info\n */\n\n/**\n * Groups a list of objects by a given string keyed property\n *\n * @template T\n * @param {T[]} items\n * @param {string} key string key\n * @return {Map}\n */\nfunction groupBy(items, key) {\n return items.reduce((groups, item) => {\n if (groups.has(item[key])) {\n groups.get(item[key]).push(item);\n } else {\n groups.set(item[key], [item]);\n }\n return groups;\n }, new Map());\n}\n\n/**\n * Partitions a list into a tuple of lists.\n * The first item in the tuple contains a list of items that pass the test provided by the callback function.\n * The second item in the tuple contains the remaining items\n *\n * @template T\n * @param {T[]} items\n * @param {(item:T) => boolean} callbackFn Returns truthy if item should be put in the first list in the tuple, falsy if it should be put in the second list.\n * @return {[T[],T[]]}\n */\nfunction partition(items, callbackFn) {\n const partA = [];\n const partB = [];\n for (const item of items) {\n if (callbackFn(item)) {\n partA.push(item);\n } else {\n partB.push(item);\n }\n }\n return [partA, partB];\n}\n\n/**\n * Partial implementation of `simplify a calculation tree` applied to CSSNumericValue\n * https://www.w3.org/TR/css-values-4/#simplify-a-calculation-tree\n *\n * @param {CSSNumericValue} root\n * @param {Info} info information used to resolve\n * @return {CSSNumericValue}\n */\nexport function simplifyCalculation(root, info = {}) {\n function simplifyNumericArray(values) {\n return Array.from(values).map((value) => simplifyCalculation(value, info));\n }\n\n // To simplify a calculation tree root:\n if (root instanceof CSSUnitValue) {\n // 1. If root is a numeric value:\n\n if (root.unit === \"percent\" && info.percentageReference) {\n // 1. If root is a percentage that will be resolved against another value, and there is enough information\n // available to resolve it, do so, and express the resulting numeric value in the appropriate canonical unit.\n // Return the value.\n const resolvedValue = (root.value / 100) * info.percentageReference.value;\n const resolvedUnit = info.percentageReference.unit;\n return new CSSUnitValue(resolvedValue, resolvedUnit);\n }\n\n // 2. If root is a dimension that is not expressed in its canonical unit, and there is enough information available\n // to convert it to the canonical unit, do so, and return the value.\n\n // Use Typed OM toSum() to convert values in compatible sets to canonical units\n const sum = root.toSum();\n if (sum && sum.values.length === 1) {\n root = sum.values[0];\n }\n // TODO: handle relative lengths\n if (root instanceof CSSUnitValue && root.unit === 'em' && info.fontSize) {\n root = new CSSUnitValue(root.value * info.fontSize.value, info.fontSize.unit);\n }\n // 3. If root is a that can be resolved, return what it resolves to, simplified.\n if (root instanceof CSSKeywordValue) {\n //https://www.w3.org/TR/css-values-4/#calc-constants\n if (root.value === 'e') {\n return new CSSUnitValue(Math.E, 'number');\n } else if (root.value === 'pi') {\n return new CSSUnitValue(Math.PI, 'number');\n }\n }\n // 4. Otherwise, return root.\n return root;\n }\n\n // 2. If root is any other leaf node (not an operator node):\n if (!root.operator) {\n // 1. If there is enough information available to determine its numeric value, return its value, expressed in the value’s canonical unit.\n // 2. Otherwise, return root.\n return root;\n }\n\n // 3. At this point, root is an operator node. Simplify all the calculation children of root.\n switch (root.operator) {\n case \"sum\":\n root = new CSSMathSum(...simplifyNumericArray(root.values));\n break;\n case \"product\":\n root = new CSSMathProduct(...simplifyNumericArray(root.values));\n break;\n case \"negate\":\n root = new CSSMathNegate(simplifyCalculation(root.value, info));\n break;\n case \"clamp\":\n root = new CSSMathClamp(simplifyCalculation(root.lower, info), simplifyCalculation(root.value, info),\n simplifyCalculation(root.upper, info));\n break;\n case \"invert\":\n root = new CSSMathInvert(simplifyCalculation(root.value, info));\n break;\n case \"min\":\n root = new CSSMathMin(...simplifyNumericArray(root.values));\n break;\n case \"max\":\n root = new CSSMathMax(...simplifyNumericArray(root.values));\n break;\n }\n\n // 4. If root is an operator node that’s not one of the calc-operator nodes, and all of its calculation children are\n // numeric values with enough information to compute the operation root represents, return the result of running\n // root’s operation using its children, expressed in the result’s canonical unit.\n if (root instanceof CSSMathMin || root instanceof CSSMathMax) {\n const children = Array.from(root.values);\n if (children.every(\n (child) => child instanceof CSSUnitValue && child.unit !== \"percent\" && isCanonical(child.unit) && child.unit ===\n children[0].unit)) {\n\n const result = Math[root.operator].apply(Math, children.map(({value}) => value));\n return new CSSUnitValue(result, children[0].unit);\n }\n }\n\n // Note: If a percentage is left at this point, it will usually block simplification of the node, since it needs to be\n // resolved against another value using information not currently available. (Otherwise, it would have been converted\n // to a different value in an earlier step.) This includes operations such as \"min\", since percentages might resolve\n // against a negative basis, and thus end up with an opposite comparative relationship than the raw percentage value\n // would seem to indicate.\n //\n // However, \"raw\" percentages—ones which do not resolve against another value, such as in opacity—might not block\n // simplification.\n\n // 5. If root is a Min or Max node, attempt to partially simplify it:\n if (root instanceof CSSMathMin || root instanceof CSSMathMax) {\n const children = Array.from(root.values);\n const [numeric, rest] = partition(children, (child) => child instanceof CSSUnitValue && child.unit !== \"percent\");\n const unitGroups = Array.from(groupBy(numeric, \"unit\").values());\n // 1. For each node child of root’s children:\n //\n // If child is a numeric value with enough information to compare magnitudes with another child of the same\n // unit (see note in previous step), and there are other children of root that are numeric children with the same\n // unit, combine all such children with the appropriate operator per root, and replace child with the result,\n // removing all other child nodes involved.\n const hasComparableChildren = unitGroups.some(group => group.length > 0);\n if (hasComparableChildren) {\n const combinedGroups = unitGroups.map(group => {\n const result = Math[root.operator].apply(Math, group.map(({value}) => value));\n return new CSSUnitValue(result, group[0].unit);\n });\n if (root instanceof CSSMathMin) {\n root = new CSSMathMin(...combinedGroups, ...rest);\n } else {\n root = new CSSMathMax(...combinedGroups, ...rest);\n }\n }\n\n // 2. If root has only one child, return the child.\n //\n // Otherwise, return root.\n if (children.length === 1) {\n return children[0];\n } else {\n return root;\n }\n }\n\n // If root is a Negate node:\n //\n // If root’s child is a numeric value, return an equivalent numeric value, but with the value negated (0 - value).\n // If root’s child is a Negate node, return the child’s child.\n // Return root.\n if (root instanceof CSSMathNegate) {\n if (root.value instanceof CSSUnitValue) {\n return new CSSUnitValue(0 - root.value.value, root.value.unit);\n } else if (root.value instanceof CSSMathNegate) {\n return root.value.value;\n } else {\n return root;\n }\n }\n\n // If root is an Invert node:\n //\n // If root’s child is a number (not a percentage or dimension) return the reciprocal of the child’s value.\n // If root’s child is an Invert node, return the child’s child.\n // Return root.\n if (root instanceof CSSMathInvert) {\n if (root.value instanceof CSSMathInvert) {\n return root.value.value;\n } else {\n return root;\n }\n }\n\n // If root is a Sum node:\n if (root instanceof CSSMathSum) {\n let children = [];\n // For each of root’s children that are Sum nodes, replace them with their children.\n for (const value of root.values) {\n if (value instanceof CSSMathSum) {\n children.push(...value.values);\n } else {\n children.push(value);\n }\n }\n\n // For each set of root’s children that are numeric values with identical units, remove those children and\n // replace them with a single numeric value containing the sum of the removed nodes, and with the same unit.\n //\n // (E.g. combine numbers, combine percentages, combine px values, etc.)\n function sumValuesWithSameUnit(values) {\n const numericValues = values.filter((c) => c instanceof CSSUnitValue);\n const nonNumericValues = values.filter((c) => !(c instanceof CSSUnitValue));\n\n const summedNumericValues = Array.from(groupBy(numericValues, \"unit\").entries())\n .map(([unit, values]) => {\n const sum = values.reduce((a, {value}) => a + value, 0);\n return new CSSUnitValue(sum, unit);\n });\n return [...nonNumericValues, ...summedNumericValues];\n }\n\n children = sumValuesWithSameUnit(children);\n\n // If root has only a single child at this point, return the child. Otherwise, return root.\n // NOTE: Zero-valued terms cannot be simply removed from a Sum; they can only be combined with other values\n // that have identical units. (This is because the mere presence of a unit, even with a zero value,\n // can sometimes imply a change in behavior.)\n if (children.length === 1) {\n return children[0];\n } else {\n return new CSSMathSum(...children);\n }\n }\n\n // If root is a Product node:\n //\n // For each of root’s children that are Product nodes, replace them with their children.\n if (root instanceof CSSMathProduct) {\n let children = [];\n for (const value of root.values) {\n if (value instanceof CSSMathProduct) {\n children.push(...value.values);\n } else {\n children.push(value);\n }\n }\n\n // If root has multiple children that are numbers (not percentages or dimensions), remove them and replace them with\n // a single number containing the product of the removed nodes.\n const [numbers, rest] = partition(children, (child) => child instanceof CSSUnitValue && child.unit === \"number\");\n if (numbers.length > 1) {\n const product = numbers.reduce((a, {value}) => a * value, 1);\n children = [new CSSUnitValue(product, \"number\"), ...rest];\n }\n\n // If root contains only two children, one of which is a number (not a percentage or dimension) and the other of\n // which is a Sum whose children are all numeric values, multiply all of the Sum’s children by the number,\n // then return the Sum.\n if (children.length === 2) {\n let numeric, sum;\n for (const child of children) {\n if (child instanceof CSSUnitValue && child.unit === \"number\") {\n numeric = child;\n } else if (child instanceof CSSMathSum && [...child.values].every((c) => c instanceof CSSUnitValue)) {\n sum = child;\n }\n }\n if (numeric && sum) {\n return new CSSMathSum(\n ...[...sum.values].map((value) => new CSSUnitValue(value.value * numeric.value, value.unit)));\n }\n }\n\n // If root contains only numeric values and/or Invert nodes containing numeric values, and multiplying the types of\n // all the children (noting that the type of an Invert node is the inverse of its child’s type) results in a type\n // that matches any of the types that a math function can resolve to, return the result of multiplying all the values\n // of the children (noting that the value of an Invert node is the reciprocal of its child’s value),\n // expressed in the result’s canonical unit.\n if (children.every((child) => (child instanceof CSSUnitValue && isCanonical(child.unit)) ||\n (child instanceof CSSMathInvert && child.value instanceof CSSUnitValue && isCanonical(child.value.unit)))) {\n // Use CSS Typed OM to multiply types\n const sum = new CSSMathProduct(...children).toSum();\n if (sum && sum.values.length === 1) {\n return sum.values[0];\n }\n }\n\n // Return root.\n return new CSSMathProduct(...children);\n }\n // Return root.\n return root;\n}\n","import {\n CommaToken,\n DelimToken,\n DimensionToken,\n FunctionToken, IdentToken,\n LeftCurlyBracketToken,\n LeftParenthesisToken,\n LeftSquareBracketToken,\n NumberToken,\n PercentageToken, RightCurlyBracketToken,\n RightParenthesisToken, RightSquareBracketToken,\n Token,\n tokenizeString,\n WhitespaceToken\n} from './tokenizer';\nimport {simplifyCalculation} from './simplify-calculation';\n\n/**\n * @typedef {{[string]: integer}} UnitMap\n * @typedef {[number, UnitMap]} SumValueItem\n * @typedef {SumValueItem[]} SumValue\n * @typedef {null} Failure\n * @typedef {{[string]: integer} & {percentHint: string | undefined}} Type\n * @typedef {{type: 'ADDITION'}|{type: 'MULTIPLICATION'}|{type: 'NEGATE'}|{type: 'INVERT'}} ASTNode\n */\n\nconst failure = null;\nconst baseTypes = [\"percent\", \"length\", \"angle\", \"time\", \"frequency\", \"resolution\", \"flex\"];\n\nconst unitGroups = {\n // https://www.w3.org/TR/css-values-4/#font-relative-lengths\n fontRelativeLengths: {\n units: new Set([\"em\", \"rem\", \"ex\", \"rex\", \"cap\", \"rcap\", \"ch\", \"rch\", \"ic\", \"ric\", \"lh\", \"rlh\"])\n },\n // https://www.w3.org/TR/css-values-4/#viewport-relative-lengths\n viewportRelativeLengths: {\n units: new Set(\n [\"vw\", \"lvw\", \"svw\", \"dvw\", \"vh\", \"lvh\", \"svh\", \"dvh\", \"vi\", \"lvi\", \"svi\", \"dvi\", \"vb\", \"lvb\", \"svb\", \"dvb\",\n \"vmin\", \"lvmin\", \"svmin\", \"dvmin\", \"vmax\", \"lvmax\", \"svmax\", \"dvmax\"])\n },\n // https://www.w3.org/TR/css-values-4/#absolute-lengths\n absoluteLengths: {\n units: new Set([\"cm\", \"mm\", \"Q\", \"in\", \"pt\", \"pc\", \"px\"]),\n compatible: true,\n canonicalUnit: \"px\",\n ratios: {\n \"cm\": 96 / 2.54, \"mm\": (96 / 2.54) / 10, \"Q\": (96 / 2.54) / 40, \"in\": 96, \"pc\": 96 / 6, \"pt\": 96 / 72, \"px\": 1\n }\n },\n // https://www.w3.org/TR/css-values-4/#angles\n angle: {\n units: new Set([\"deg\", \"grad\", \"rad\", \"turn\"]),\n compatible: true,\n canonicalUnit: \"deg\",\n ratios: {\n \"deg\": 1, \"grad\": 360 / 400, \"rad\": 180 / Math.PI, \"turn\": 360\n }\n },\n // https://www.w3.org/TR/css-values-4/#time\n time: {\n units: new Set([\"s\", \"ms\"]),\n compatible: true,\n canonicalUnit: \"s\",\n ratios: {\n \"s\": 1, \"ms\": 1 / 1000\n }\n },\n // https://www.w3.org/TR/css-values-4/#frequency\n frequency: {\n units: new Set([\"hz\", \"khz\"]),\n compatible: true,\n canonicalUnit: \"hz\",\n ratios: {\n \"hz\": 1, \"khz\": 1000\n }\n },\n // https://www.w3.org/TR/css-values-4/#resolution\n resolution: {\n units: new Set([\"dpi\", \"dpcm\", \"dppx\"]),\n compatible: true,\n canonicalUnit: \"dppx\",\n ratios: {\n \"dpi\": 1 / 96, \"dpcm\": 2.54 / 96, \"dppx\": 1\n }\n }\n};\n\nconst unitToCompatibleUnitsMap = new Map();\nfor (const group of Object.values(unitGroups)) {\n if (!group.compatible) {\n continue;\n }\n for (const unit of group.units) {\n unitToCompatibleUnitsMap.set(unit, group);\n }\n}\n\nexport function getSetOfCompatibleUnits(unit) {\n return unitToCompatibleUnitsMap.get(unit);\n}\n\n/**\n * Implementation of `product of two unit maps` from css-typed-om-1:\n * https://www.w3.org/TR/css-typed-om-1/#product-of-two-unit-maps\n *\n * @param {UnitMap} units1 map of units (strings) to powers (integers)\n * @param {UnitMap} units2 map of units (strings) to powers (integers)\n * @return {UnitMap} map of units (strings) to powers (integers)\n */\nfunction productOfTwoUnitMaps(units1, units2) {\n // 1. Let result be a copy of units1.\n const result = {...units1};\n // 2. For each unit → power in units2:\n for (const unit of Object.keys(units2)) {\n if (result[unit]) {\n // 1. If result[unit] exists, increment result[unit] by power.\n result[unit] += units2[unit];\n } else {\n // 2. Otherwise, set result[unit] to power.\n result[unit] = units2[unit];\n }\n }\n // 3. Return result.\n return result;\n}\n\n/**\n * Implementation of `create a type` from css-typed-om-1:\n * https://www.w3.org/TR/css-typed-om-1/#create-a-type\n *\n * @param {string} unit\n * @return {Type|Failure}\n */\nexport function createAType(unit) {\n if (unit === \"number\") {\n return {};\n } else if (unit === \"percent\") {\n return {\"percent\": 1};\n } else if (unitGroups.absoluteLengths.units.has(unit) || unitGroups.fontRelativeLengths.units.has(unit) ||\n unitGroups.viewportRelativeLengths.units.has(unit)) {\n return {\"length\": 1};\n } else if (unitGroups.angle.units.has(unit)) {\n return {\"angle\": 1};\n } else if (unitGroups.time.units.has(unit)) {\n return {\"time\": 1};\n } else if (unitGroups.frequency.units.has(unit)) {\n return {\"frequency\": 1};\n } else if (unitGroups.resolution.units.has(unit)) {\n return {\"resolution\": 1};\n } else if (unit === \"fr\") {\n return {\"flex\": 1};\n } else {\n return failure;\n }\n}\n\n/**\n * Partial implementation of `create a sum value` from css-typed-om-1:\n * https://www.w3.org/TR/css-typed-om-1/#create-a-sum-value\n *\n * Supports CSSUnitValue, CSSMathProduct and CSSMathInvert with a CSSUnitValue value.\n * Other types are not supported, and will throw an error.\n *\n * @param {CSSNumericValue} cssNumericValue\n * @return {SumValue} Abstract representation of a CSSNumericValue as a sum of numbers with (possibly complex) units\n */\nexport function createSumValue(cssNumericValue) {\n if (cssNumericValue instanceof CSSUnitValue) {\n let {unit, value} = cssNumericValue;\n // Let unit be the value of this’s unit internal slot, and value be the value of this’s value internal slot.\n // If unit is a member of a set of compatible units, and is not the set’s canonical unit,\n // multiply value by the conversion ratio between unit and the canonical unit, and change unit to the canonical unit.\n const compatibleUnits = getSetOfCompatibleUnits(cssNumericValue.unit);\n if (compatibleUnits && unit !== compatibleUnits.canonicalUnit) {\n value *= compatibleUnits.ratios[unit];\n unit = compatibleUnits.canonicalUnit;\n }\n\n if (unit === \"number\") {\n // If unit is \"number\", return «(value, «[ ]»)».\n return [[value, {}]];\n } else {\n // Otherwise, return «(value, «[unit → 1]»)».\n return [[value, {[unit]: 1}]];\n }\n } else if (cssNumericValue instanceof CSSMathInvert) {\n if (!(cssNumericValue.value instanceof CSSUnitValue)) {\n // Limit implementation to CSSMathInvert of CSSUnitValue\n throw new Error(\"Not implemented\");\n }\n // 1. Let values be the result of creating a sum value from this’s value internal slot.\n const values = createSumValue(cssNumericValue.value);\n // 2. If values is failure, return failure.\n if (values === failure) {\n return failure;\n }\n // 3. If the length of values is more than one, return failure.\n if (values.length > 1) {\n return failure;\n }\n // 4. Invert (find the reciprocal of) the value of the item in values, and negate the value of each entry in its unit map.\n const item = values[0];\n const tempUnionMap = {};\n for (const [unit, power] of Object.entries(item[1])) {\n tempUnionMap[unit] = -1 * power;\n }\n values[0] = [1 / item[0], tempUnionMap];\n\n // 5. Return values.\n return values;\n } else if (cssNumericValue instanceof CSSMathProduct) {\n // 1. Let values initially be the sum value «(1, «[ ]»)». (I.e. what you’d get from 1.)\n\n let values = [[1, {}]];\n\n // 2. For each item in this’s values internal slot:\n for (const item of cssNumericValue.values) {\n // 1. Let new values be the result of creating a sum value from item. Let temp initially be an empty list.\n const newValues = createSumValue(item);\n const temp = [];\n // 2. If new values is failure, return failure.\n if (newValues === failure) {\n return failure;\n }\n // 3. For each item1 in values:\n for (const item1 of values) {\n // 1. For each item2 in new values:\n for (const item2 of newValues) {\n // 1. Let item be a tuple with its value set to the product of the values of item1 and item2, and its unit\n // map set to the product of the unit maps of item1 and item2, with all entries with a zero value removed.\n // 2. Append item to temp.\n temp.push([item1[0] * item2[0], productOfTwoUnitMaps(item1[1], item2[1])]);\n }\n }\n // 4. Set values to temp.\n values = temp;\n }\n // Return values.\n return values;\n } else {\n throw new Error(\"Not implemented\");\n }\n}\n\n\n/**\n * Implementation of `to(unit)` for CSSNumericValue from css-typed-om-1:\n * https://www.w3.org/TR/css-typed-om-1/#dom-cssnumericvalue-to\n *\n * Converts an existing CSSNumeric value into another with the specified unit, if possible.\n *\n * @param {CSSNumericValue} cssNumericValue value to convert\n * @param {string} unit\n * @return {CSSUnitValue}\n */\nexport function to(cssNumericValue, unit) {\n // Let type be the result of creating a type from unit. If type is failure, throw a SyntaxError.\n const type = createAType(unit);\n if (type === failure) {\n throw new SyntaxError(\"The string did not match the expected pattern.\");\n }\n\n // Let sum be the result of creating a sum value from this.\n const sumValue = createSumValue(cssNumericValue);\n\n // If sum is failure, throw a TypeError.\n if (!sumValue) {\n throw new TypeError();\n }\n\n // If sum has more than one item, throw a TypeError.\n if (sumValue.length > 1) {\n throw new TypeError(\"Sum has more than one item\");\n }\n\n // Otherwise, let item be the result of creating a CSSUnitValue\n // from the sole item in sum, then converting it to unit.\n const item = convertCSSUnitValue(createCSSUnitValue(sumValue[0]), unit);\n\n\n // If item is failure, throw a TypeError.\n if (item === failure) {\n throw new TypeError();\n }\n // Return item.\n return item;\n}\n\n/**\n * Implementation of `create a CSSUnitValue from a sum value item` from css-typed-om-1:\n * https://www.w3.org/TR/css-typed-om-1/#create-a-cssunitvalue-from-a-sum-value-item\n *\n * @param {SumValueItem} sumValueItem a tuple of a value, and a unit map\n * @return {CSSUnitValue|Failure}\n */\nexport function createCSSUnitValue(sumValueItem) {\n const [value, unitMap] = sumValueItem;\n // When asked to create a CSSUnitValue from a sum value item item, perform the following steps:\n // If item has more than one entry in its unit map, return failure.\n const entries = Object.entries(unitMap);\n if (entries.length > 1) {\n return failure;\n }\n // If item has no entries in its unit map, return a new CSSUnitValue whose unit internal slot is set to \"number\",\n // and whose value internal slot is set to item’s value.\n if (entries.length === 0) {\n return new CSSUnitValue(value, \"number\");\n }\n // Otherwise, item has a single entry in its unit map. If that entry’s value is anything other than 1, return failure.\n const entry = entries[0];\n if (entry[1] !== 1) {\n return failure;\n }\n // Otherwise, return a new CSSUnitValue whose unit internal slot is set to that entry’s key, and whose value internal slot is set to item’s value.\n else {\n return new CSSUnitValue(value, entry[0]);\n }\n}\n\n/**\n * Implementation of `convert a CSSUnitValue` from css-typed-om-1:\n * https://www.w3.org/TR/css-typed-om-1/#convert-a-cssunitvalue\n\n * @param {CSSUnitValue} cssUnitValue\n * @param {string} unit\n * @return {CSSUnitValue|Failure}\n */\nexport function convertCSSUnitValue(cssUnitValue, unit) {\n // Let old unit be the value of this’s unit internal slot, and old value be the value of this’s value internal slot.\n const oldUnit = cssUnitValue.unit;\n const oldValue = cssUnitValue.value;\n // If old unit and unit are not compatible units, return failure.\n const oldCompatibleUnitGroup = getSetOfCompatibleUnits(oldUnit);\n const compatibleUnitGroup = getSetOfCompatibleUnits(unit);\n if (!compatibleUnitGroup || oldCompatibleUnitGroup !== compatibleUnitGroup) {\n return failure;\n }\n // Return a new CSSUnitValue whose unit internal slot is set to unit, and whose value internal slot is set to\n // old value multiplied by the conversation ratio between old unit and unit.\n return new CSSUnitValue(oldValue * compatibleUnitGroup.ratios[oldUnit] / compatibleUnitGroup.ratios[unit], unit);\n}\n\n/**\n * Partial implementation of `toSum(...units)`:\n * https://www.w3.org/TR/css-typed-om-1/#dom-cssnumericvalue-tosum\n *\n * The implementation is restricted to conversion without units.\n * It simplifies a CSSNumericValue into a minimal sum of CSSUnitValues.\n * Will throw an error if called with units.\n *\n * @param {CSSNumericValue} cssNumericValue value to convert to a CSSMathSum\n * @param {string[]} units Not supported in this implementation\n * @return {CSSMathSum}\n */\nexport function toSum(cssNumericValue, ...units) {\n // The toSum(...units) method converts an existing CSSNumericValue this into a CSSMathSum of only CSSUnitValues\n // with the specified units, if possible. (It’s like to(), but allows the result to have multiple units in it.)\n // If called without any units, it just simplifies this into a minimal sum of CSSUnitValues.\n // When called, it must perform the following steps:\n //\n // For each unit in units, if the result of creating a type from unit is failure, throw a SyntaxError.\n //\n if (units && units.length) {\n // Only unitless method calls are implemented in this polyfill\n throw new Error(\"Not implemented\");\n }\n\n // Let sum be the result of creating a sum value from this. If sum is failure, throw a TypeError.\n const sum = createSumValue(cssNumericValue);\n\n // Let values be the result of creating a CSSUnitValue for each item in sum. If any item of values is failure,\n // throw a TypeError.\n const values = sum.map(item => createCSSUnitValue(item));\n if (values.some(value => value === failure)) {\n throw new TypeError(\"Type error\");\n }\n\n // If units is empty, sort values in code point order according to the unit internal slot of its items,\n // then return a new CSSMathSum object whose values internal slot is set to values.\n return new CSSMathSum(...values);\n}\n\n/**\n * Implementation of `invert a type` from css-typed-om-1 Editors Draft:\n * https://drafts.css-houdini.org/css-typed-om/\n *\n * @param {Type} type\n * @return {Type}\n */\nexport function invertType(type) {\n // To invert a type type, perform the following steps:\n // Let result be a new type with an initially empty ordered map and an initially null percent hint\n // For each unit → exponent of type, set result[unit] to (-1 * exponent).\n // Return result.\n const result = {};\n for (const baseType of baseTypes) {\n result[baseType] = -1 * type[baseType];\n }\n return result;\n}\n\n/**\n * Implementation of `multiply two types` from css-typed-om-1 Editor's Draft:\n * https://drafts.css-houdini.org/css-typed-om/#cssnumericvalue-multiply-two-types\n *\n * @param {Type} type1 a map of base types to integers and an associated percent hint\n * @param {Type} type2 a map of base types to integers and an associated percent hint\n * @return {Type|Failure}\n */\nexport function multiplyTypes(type1, type2) {\n if (type1.percentHint && type2.percentHint && type1.percentHint !== type2.percentHint) {\n return failure;\n }\n const finalType = {\n ...type1, percentHint: type1.percentHint ?? type2.percentHint,\n };\n\n for (const baseType of baseTypes) {\n if (!type2[baseType]) {\n continue;\n }\n finalType[baseType] ??= 0;\n finalType[baseType] += type2[baseType];\n }\n return finalType;\n}\n\nclass CSSFunction {\n name;\n values;\n constructor(name, values) {\n this.name = name;\n this.values = values;\n }\n}\n\nclass CSSSimpleBlock {\n value;\n associatedToken;\n constructor(value, associatedToken) {\n this.value = value;\n this.associatedToken = associatedToken;\n }\n}\n\n/**\n * Normalize into a token stream\n * https://www.w3.org/TR/css-syntax-3/#normalize-into-a-token-stream\n */\nfunction normalizeIntoTokenStream(input) {\n // If input is a list of CSS tokens, return input.\n // If input is a list of CSS component values, return input.\n if (Array.isArray(input)) {\n return input;\n }\n // If input is a string, then filter code points from input, tokenize the result, and return the final result.\n if (typeof input === 'string') {\n return tokenizeString(input);\n }\n // Assert: Only the preceding types should be passed as input.\n throw new TypeError(`Invalid input type ${typeof input}`)\n}\n\n/**\n * Consume a function\n * https://www.w3.org/TR/css-syntax-3/#consume-a-function\n * @param {FunctionToken} token\n * @param {Token[]} tokens\n */\nfunction consumeFunction(token, tokens) {\n // Create a function with its name equal to the value of the current input token and with its value initially set to an empty list.\n const func = new CSSFunction(token.value, []);\n\n // Repeatedly consume the next input token and process it as follows:\n while(true) {\n const nextToken = tokens.shift();\n if (nextToken instanceof RightParenthesisToken) {\n // <)-token>\n // Return the function.\n return func;\n } else if (typeof nextToken === 'undefined') {\n // \n // This is a parse error. Return the function.\n return func;\n } else {\n // anything else\n // Reconsume the current input token. Consume a component value and append the returned value to the function’s value.\n tokens.unshift(nextToken);\n func.values.push(consumeComponentValue(tokens));\n }\n }\n}\n\n/**\n * Consume a simple block\n * https://www.w3.org/TR/css-syntax-3/#consume-simple-block\n * @param {Token[]} tokens\n * @param {LeftCurlyBracketToken | LeftParenthesisToken | LeftSquareBracketToken} currentInputToken\n */\nfunction consumeSimpleBlock(tokens, currentInputToken) {\n // The ending token is the mirror variant of the current input token. (E.g. if it was called with <[-token>, the ending token is <]-token>.)\n let endingTokenConstructor ;\n if (currentInputToken instanceof LeftCurlyBracketToken) {\n endingTokenConstructor = RightCurlyBracketToken;\n } else if (currentInputToken instanceof LeftParenthesisToken) {\n endingTokenConstructor = RightParenthesisToken;\n } else if (currentInputToken instanceof LeftSquareBracketToken) {\n endingTokenConstructor = RightSquareBracketToken;\n } else {\n return undefined;\n }\n\n\n // Create a simple block with its associated token set to the current input token and with its value initially set to an empty list.\n const simpleBlock = new CSSSimpleBlock([], currentInputToken);\n\n // Repeatedly consume the next input token and process it as follows:\n while (true) {\n const token = tokens.shift();\n if (token instanceof endingTokenConstructor) {\n // ending token\n // Return the block.\n return simpleBlock;\n } else if (typeof token === 'undefined') {\n // \n // This is a parse error. Return the block.\n return simpleBlock;\n } else {\n // anything else\n // Reconsume the current input token. Consume a component value and append it to the value of the block.\n tokens.unshift(token);\n simpleBlock.value.push(consumeComponentValue(tokens));\n }\n }\n}\n\n/**\n * Consume a component value\n * https://www.w3.org/TR/css-syntax-3/#consume-a-component-value\n * @param {Token[]} tokens\n */\nfunction consumeComponentValue(tokens) {\n const syntaxError = null;\n // Consume the next input token.\n const token = tokens.shift();\n\n if (token instanceof LeftCurlyBracketToken || token instanceof LeftSquareBracketToken || token instanceof LeftParenthesisToken) {\n // If the current input token is a <{-token>, <[-token>, or <(-token>, consume a simple block and return it.\n return consumeSimpleBlock(tokens, token);\n } else if (token instanceof FunctionToken) {\n // Otherwise, if the current input token is a , consume a function and return it.\n return consumeFunction(token, tokens);\n } else {\n // Otherwise, return the current input token.\n return token;\n }\n}\n\n/**\n * Parse a component value\n * https://www.w3.org/TR/css-syntax-3/#parse-component-value\n * @param {string} input\n */\nfunction parseComponentValue(input) {\n const syntaxError = null;\n // To parse a component value from input:\n // 1. Normalize input, and set input to the result.\n const tokens = normalizeIntoTokenStream(input);\n\n // 2. While the next input token from input is a , consume the next input token from input.\n while (tokens[0] instanceof WhitespaceToken) {\n tokens.shift();\n }\n // 3. If the next input token from input is an , return a syntax error.\n if (typeof tokens[0] === 'undefined') {\n return syntaxError;\n }\n // 4. Consume a component value from input and let value be the return value.\n const returnValue = consumeComponentValue(tokens);\n // 5. While the next input token from input is a , consume the next input token.\n while (tokens[0] instanceof WhitespaceToken) {\n tokens.shift();\n }\n // 6. If the next input token from input is an , return value. Otherwise, return a syntax error.\n if (typeof tokens[0] === 'undefined') {\n return returnValue;\n } else {\n return syntaxError;\n }\n}\n\nfunction precedence(token) {\n if (token instanceof LeftParenthesisToken || token instanceof RightParenthesisToken) {\n return 6;\n } else if (token instanceof DelimToken) {\n const value = token.value;\n switch (value) {\n case '*':\n return 4;\n case '/':\n return 4;\n case '+':\n return 2;\n case '-':\n return 2;\n }\n }\n}\n\n\nfunction last(items) {\n return items[items.length - 1];\n}\n\nfunction toNAryAstNode(operatorToken, first, second) {\n // Treat subtraction as instead being addition, with the RHS argument instead wrapped in a special \"negate\" node.\n // Treat division as instead being multiplication, with the RHS argument instead wrapped in a special \"invert\" node.\n\n const type = ['+','-'].includes(operatorToken.value) ? 'ADDITION' : 'MULTIPLICATION';\n const firstValues = first.type === type ? first.values : [first];\n const secondValues = second.type === type ? second.values : [second];\n\n if (operatorToken.value === '-') {\n secondValues[0] = {type: 'NEGATE', value: secondValues[0]};\n } else if (operatorToken.value === '/') {\n secondValues[0] = {type: 'INVERT', value: secondValues[0]};\n }\n return {type, values: [...firstValues, ...secondValues]};\n}\n\n/**\n * Convert expression to AST using the Shunting Yard Algorithm\n * https://en.wikipedia.org/wiki/Shunting_yard_algorithm\n * @param {(Token | CSSFunction)[]} tokens\n * @return {null}\n */\nfunction convertTokensToAST(tokens) {\n const operatorStack = [];\n const tree = [];\n while (tokens.length) {\n const token = tokens.shift();\n if (token instanceof NumberToken || token instanceof DimensionToken || token instanceof PercentageToken ||\n token instanceof CSSFunction || token instanceof CSSSimpleBlock || token instanceof IdentToken) {\n tree.push(token);\n } else if (token instanceof DelimToken && ['*', '/', '+', '-'].includes(token.value)) {\n while (operatorStack.length &&\n !(last(operatorStack) instanceof LeftParenthesisToken) &&\n precedence(last(operatorStack)) > precedence(token)) {\n const o2 = operatorStack.pop();\n const second = tree.pop();\n const first = tree.pop();\n tree.push(toNAryAstNode(o2, first, second));\n }\n operatorStack.push(token);\n } else if (token instanceof LeftParenthesisToken) {\n operatorStack.push(token);\n } else if (token instanceof RightParenthesisToken) {\n if (!operatorStack.length) {\n return null;\n }\n while (!(last(operatorStack) instanceof LeftParenthesisToken) ) {\n const o2 = operatorStack.pop();\n const second = tree.pop();\n const first = tree.pop();\n tree.push(toNAryAstNode(o2, first, second));\n }\n if (!(last(operatorStack) instanceof LeftParenthesisToken)) {\n return null;\n }\n operatorStack.pop();\n } else if (token instanceof WhitespaceToken) {\n // Consume token\n } else {\n return null;\n }\n }\n while(operatorStack.length) {\n if (last(operatorStack) instanceof LeftParenthesisToken) {\n return null;\n }\n const o2 = operatorStack.pop()\n const second = tree.pop();\n const first = tree.pop();\n tree.push(toNAryAstNode(o2, first, second));\n }\n return tree[0];\n}\n\n/**\n * Step 4 of `reify a math expression`\n * https://drafts.css-houdini.org/css-typed-om/#reify-a-math-expression\n *\n * 4. Recursively transform the expression tree into objects, as follows:\n *\n * @param {ASTNode} node\n * @return {CSSMathNegate|CSSMathProduct|CSSMathMin|CSSMathMax|CSSMathSum|CSSNumericValue|CSSUnitValue|CSSMathInvert}\n */\nfunction transformToCSSNumericValue(node) {\n if (node.type === 'ADDITION') {\n // addition node\n // becomes a new CSSMathSum object, with its values internal slot set to its list of arguments\n return new CSSMathSum(...node.values.map(value => transformToCSSNumericValue(value)));\n } else if (node.type === 'MULTIPLICATION') {\n // multiplication node\n // becomes a new CSSMathProduct object, with its values internal slot set to its list of arguments\n return new CSSMathProduct(...node.values.map(value => transformToCSSNumericValue(value)));\n } else if (node.type === 'NEGATE') {\n // negate node\n // becomes a new CSSMathNegate object, with its value internal slot set to its argument\n return new CSSMathNegate(transformToCSSNumericValue(node.value));\n } else if (node.type === 'INVERT') {\n // invert node\n // becomes a new CSSMathInvert object, with its value internal slot set to its argument\n return new CSSMathInvert(transformToCSSNumericValue(node.value));\n } else {\n // leaf node\n // reified as appropriate\n if (node instanceof CSSSimpleBlock) {\n return reifyMathExpression(new CSSFunction('calc', node.value));\n } else if (node instanceof IdentToken) {\n if (node.value === 'e') {\n return new CSSUnitValue(Math.E, 'number');\n } else if (node.value === 'pi') {\n return new CSSUnitValue(Math.PI, 'number');\n } else {\n throw new SyntaxError('Invalid math expression')\n }\n } else {\n return reifyNumericValue(node);\n }\n }\n}\n\n/**\n * Reify a math expression\n * https://drafts.css-houdini.org/css-typed-om/#reify-a-math-expression\n * @param {CSSFunction} num\n */\nfunction reifyMathExpression(num) {\n // TODO: handle `clamp()` and possibly other math functions\n // 1. If num is a min() or max() expression:\n if (num.name === 'min' || num.name === 'max')\n {\n // Let values be the result of reifying the arguments to the expression, treating each argument as if it were the contents of a calc() expression.\n const values = num.values\n .filter(value => !(value instanceof WhitespaceToken || value instanceof CommaToken))\n // TODO: Update when we have clarification on where simplify a calculation should be run:\n // https://github.com/w3c/csswg-drafts/issues/9870\n .map(value => simplifyCalculation(reifyMathExpression(new CSSFunction('calc', value))));\n // Return a new CSSMathMin or CSSMathMax object, respectively, with its values internal slot set to values.\n return num.name === 'min' ? new CSSMathMin(...values) : new CSSMathMax(...values);\n }\n\n // 2. Assert: Otherwise, num is a calc().\n if (num.name !== 'calc') {\n return null;\n }\n\n // 3. Turn num’s argument into an expression tree using standard PEMDAS precedence rules, with the following exceptions/clarification:\n //\n // Treat subtraction as instead being addition, with the RHS argument instead wrapped in a special \"negate\" node.\n // Treat division as instead being multiplication, with the RHS argument instead wrapped in a special \"invert\" node.\n // Addition and multiplication are N-ary; each node can have any number of arguments.\n // If an expression has only a single value in it, and no operation, treat it as an addition node with the single argument.\n const root = convertTokensToAST([...num.values]);\n \n // 4. Recursively transform the expression tree into objects\n const numericValue = transformToCSSNumericValue(root);\n let simplifiedValue;\n try {\n // TODO: Update when we have clarification on where simplify a calculation should be run:\n // https://github.com/w3c/csswg-drafts/issues/9870\n simplifiedValue = simplifyCalculation(numericValue);\n } catch (e) {\n // Use insertRule to trigger native SyntaxError on TypeError\n (new CSSStyleSheet()).insertRule('error', 0);\n }\n if (simplifiedValue instanceof CSSUnitValue) {\n return new CSSMathSum(simplifiedValue);\n } else {\n return simplifiedValue;\n }\n}\n\n/**\n * Reify a numeric value\n * https://drafts.css-houdini.org/css-typed-om/#reify-a-numeric-value\n * @param num\n */\nfunction reifyNumericValue(num) {\n // If an internal representation contains a var() reference, then it is reified by reifying a list of component values,\n // regardless of what property it is for.\n // TODO: handle `var()` function\n\n // If num is a math function, reify a math expression from num and return the result.\n if (num instanceof CSSFunction && ['calc', 'min', 'max', 'clamp'].includes(num.name)) {\n return reifyMathExpression(num);\n }\n // If num is the unitless value 0 and num is a ,\n // return a new CSSUnitValue with its value internal slot set to 0, and its unit internal slot set to \"px\".\n if (num instanceof NumberToken && num.value === 0 && !num.unit) {\n return new CSSUnitValue(0, 'px');\n }\n // Return a new CSSUnitValue with its value internal slot set to the numeric value of num, and its unit internal slot\n // set to \"number\" if num is a , \"percent\" if num is a , and num’s unit if num is a .\n if (num instanceof NumberToken) {\n return new CSSUnitValue(num.value, 'number');\n } else if (num instanceof PercentageToken) {\n return new CSSUnitValue(num.value, 'percent');\n } else if (num instanceof DimensionToken) {\n return new CSSUnitValue(num.value, num.unit);\n }\n}\n\n/**\n * Implementation of the parse(cssText) method.\n * https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-parse\n * @param {string} cssText\n * @return {CSSMathMin|CSSMathMax|CSSMathSum|CSSMathProduct|CSSMathNegate|CSSMathInvert|CSSUnitValue}\n */\nexport function parseCSSNumericValue(cssText) {\n // Parse a component value from cssText and let result be the result.\n // If result is a syntax error, throw a SyntaxError and abort this algorithm.\n const result = parseComponentValue(cssText);\n if (result === null) {\n // Use insertRule to trigger native SyntaxError\n (new CSSStyleSheet()).insertRule('error', 0);\n }\n // If result is not a , , , or a math function, throw a SyntaxError and abort this algorithm.\n if (!(result instanceof NumberToken || result instanceof PercentageToken || result instanceof DimensionToken || result instanceof CSSFunction)) {\n // Use insertRule to trigger native SyntaxError\n (new CSSStyleSheet()).insertRule('error', 0);\n }\n // If result is a and creating a type from result’s unit returns failure, throw a SyntaxError and abort this algorithm.\n if (result instanceof DimensionToken) {\n const type = createAType(result.unit);\n if (type === null) {\n // Use insertRule to trigger native SyntaxError\n (new CSSStyleSheet()).insertRule('error', 0);\n }\n }\n // Reify a numeric value result, and return the result.\n return reifyNumericValue(result);\n}","// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\nimport {createAType, invertType, multiplyTypes, parseCSSNumericValue, to, toSum} from './numeric-values';\nimport {simplifyCalculation} from './simplify-calculation';\nimport './tokenizer'\n\nexport function installCSSOM() {\n // Object for storing details associated with an object which are to be kept\n // private. This approach allows the constructed objects to more closely\n // resemble their native counterparts when inspected.\n let privateDetails = new WeakMap();\n\n function displayUnit(unit) {\n switch(unit) {\n case 'percent':\n return '%';\n case 'number':\n return '';\n default:\n return unit.toLowerCase();\n }\n }\n\n function toCssUnitValue(v) {\n if (typeof v === 'number')\n return new CSSUnitValue(v, 'number');\n return v;\n }\n\n function toCssNumericArray(values) {\n const result = [];\n for (let i = 0; i < values.length; i++) {\n result[i] = toCssUnitValue(values[i]);\n }\n return result;\n }\n\n class CSSNumericValue {\n static parse(value) {\n if (value instanceof CSSNumericValue) return value;\n\n return simplifyCalculation(parseCSSNumericValue(value), {});\n }\n\n // TODO: Add other methods: add, sub, mul, div, …\n // Spec: https://drafts.css-houdini.org/css-typed-om/#numeric-value\n }\n\n class CSSMathValue extends CSSNumericValue {\n constructor(values, operator, opt_name, opt_delimiter) {\n super();\n privateDetails.set(this, {\n values: toCssNumericArray(values),\n operator: operator,\n name: opt_name || operator,\n delimiter: opt_delimiter || ', '\n });\n }\n\n get operator() {\n return privateDetails.get(this).operator;\n }\n\n get values() {\n return privateDetails.get(this).values;\n }\n\n toString() {\n const details = privateDetails.get(this);\n return `${details.name}(${details.values.join(details.delimiter)})`;\n }\n }\n\n const cssOMTypes = {\n 'CSSNumericValue': CSSNumericValue,\n 'CSSMathValue': CSSMathValue,\n 'CSSUnitValue': class extends CSSNumericValue {\n constructor(value, unit) {\n super();\n privateDetails.set(this, {\n value: value,\n unit: unit\n });\n }\n\n get value() {\n return privateDetails.get(this).value;\n }\n\n set value(value) {\n privateDetails.get(this).value = value;\n }\n\n get unit() {\n return privateDetails.get(this).unit;\n }\n\n to(unit) {\n return to(this, unit)\n }\n\n toSum(...units) {\n return toSum(this, ...units)\n }\n\n type() {\n const details = privateDetails.get(this)\n // The type of a CSSUnitValue is the result of creating a type from its unit internal slot.\n return createAType(details.unit)\n }\n\n toString() {\n const details = privateDetails.get(this);\n return `${details.value}${displayUnit(details.unit)}`;\n }\n },\n\n 'CSSKeywordValue': class {\n constructor(value) {\n this.value = value;\n }\n\n toString() {\n return this.value.toString();\n }\n },\n\n 'CSSMathSum': class extends CSSMathValue {\n constructor(values) {\n super(arguments, 'sum', 'calc', ' + ');\n }\n },\n\n 'CSSMathProduct': class extends CSSMathValue {\n constructor(values) {\n super(arguments, 'product', 'calc', ' * ');\n }\n\n toSum(...units) {\n return toSum(this, ...units)\n }\n\n type() {\n const values = privateDetails.get(this).values;\n // The type is the result of multiplying the types of each of the items in its values internal slot.\n return values.map(v => v.type()).reduce(multiplyTypes)\n }\n },\n\n 'CSSMathNegate': class extends CSSMathValue {\n constructor(values) {\n super([arguments[0]], 'negate', '-');\n }\n\n get value() {\n return privateDetails.get(this).values[0];\n }\n\n type() {\n return this.value.type();\n }\n },\n\n 'CSSMathInvert': class extends CSSMathValue {\n constructor(values) {\n super([1, arguments[0]], 'invert', 'calc', ' / ');\n }\n\n get value() {\n return privateDetails.get(this).values[1];\n }\n\n type() {\n const details = privateDetails.get(this)\n // The type of a CSSUnitValue is the result of creating a type from its unit internal slot.\n return invertType(details.values[1].type())\n }\n },\n\n 'CSSMathMax': class extends CSSMathValue {\n constructor() {\n super(arguments, 'max');\n }\n },\n\n 'CSSMathMin': class extends CSSMathValue {\n constructor() {\n super(arguments, 'min');\n }\n }\n };\n\n if (!window.CSS) {\n if (!Reflect.defineProperty(window, 'CSS', { value: {} }))\n throw Error(`Error installing CSSOM support`);\n }\n\n if (!window.CSSUnitValue) {\n [\n 'number',\n 'percent',\n // Length units\n 'em',\n 'ex',\n 'px',\n 'cm',\n 'mm',\n 'in',\n 'pt',\n 'pc', // Picas\n 'Q', // Quarter millimeter\n 'vw',\n 'vh',\n 'vmin',\n 'vmax',\n 'rems',\n \"ch\",\n // Angle units\n 'deg',\n 'rad',\n 'grad',\n 'turn',\n // Time units\n 'ms',\n 's',\n 'Hz',\n 'kHz',\n // Resolution\n 'dppx',\n 'dpi',\n 'dpcm',\n // Other units\n \"fr\"\n ].forEach((name) => {\n const fn = (value) => {\n return new CSSUnitValue(value, name);\n };\n if (!Reflect.defineProperty(CSS, name, { value: fn }))\n throw Error(`Error installing CSS.${name}`);\n });\n }\n\n for (let [type, value] of Object.entries(cssOMTypes)) {\n if (type in window)\n continue;\n if (!Reflect.defineProperty(window, type, { value }))\n throw Error(`Error installing CSSOM support for ${type}`);\n }\n}\n","// Copyright 2019 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport {installCSSOM} from \"./proxy-cssom.js\";\nimport {simplifyCalculation} from \"./simplify-calculation\";\nimport {normalizeAxis, splitIntoComponentValues} from './utils.js';\n\ninstallCSSOM();\n\nconst DEFAULT_TIMELINE_AXIS = 'block';\n\nlet scrollTimelineOptions = new WeakMap();\nlet sourceDetails = new WeakMap();\n\nexport const ANIMATION_RANGE_NAMES = ['entry', 'exit', 'cover', 'contain', 'entry-crossing', 'exit-crossing'];\n\nfunction scrollEventSource(source) {\n if (source === document.scrollingElement) return document;\n return source;\n}\n\n/**\n * Updates the currentTime for all Web Animation instanced attached to a ScrollTimeline instance\n * @param scrollTimelineInstance {ScrollTimeline}\n */\nfunction updateInternal(scrollTimelineInstance) {\n validateSource(scrollTimelineInstance);\n const details = scrollTimelineOptions.get(scrollTimelineInstance);\n let animations = details.animations;\n if (animations.length === 0) return;\n let timelineTime = scrollTimelineInstance.currentTime;\n for (let i = 0; i < animations.length; i++) {\n animations[i].tickAnimation(timelineTime);\n }\n}\n\n/**\n * Calculates a scroll offset that corrects for writing modes, text direction\n * and a logical axis.\n * @param scrollTimeline {ScrollTimeline}\n * @param axis {String}\n * @returns {Number}\n */\nfunction directionAwareScrollOffset(source, axis) {\n if (!source)\n return null;\n const sourceMeasurements = sourceDetails.get(source).sourceMeasurements;\n const style = getComputedStyle(source);\n // All writing modes are vertical except for horizontal-tb.\n // TODO: sideways-lr should flow bottom to top, but is currently unsupported\n // in Chrome.\n // http://drafts.csswg.org/css-writing-modes-4/#block-flow\n let currentScrollOffset = sourceMeasurements.scrollTop;\n if (normalizeAxis(axis, style) === 'x') {\n // Negative values are reported for scrollLeft when the inline text\n // direction is right to left or for vertical text with a right to left\n // block flow. This is a consequence of shifting the scroll origin due to\n // changes in the overflow direction.\n // http://drafts.csswg.org/cssom-view/#overflow-directions.\n currentScrollOffset = Math.abs(sourceMeasurements.scrollLeft);\n }\n return currentScrollOffset;\n}\n\n/**\n * Determines target effect end based on animation duration, iterations count and start and end delays\n * returned value should always be positive\n * @param options {Animation} animation\n * @returns {number}\n */\nexport function calculateTargetEffectEnd(animation) {\n return animation.effect.getComputedTiming().activeDuration;\n}\n\n/**\n * Calculates scroll offset based on axis and source geometry\n * @param source {DOMElement}\n * @param axis {String}\n * @returns {number}\n */\nexport function calculateMaxScrollOffset(source, axis) {\n const sourceMeasurements = sourceDetails.get(source).sourceMeasurements;\n // Only one horizontal writing mode: horizontal-tb. All other writing modes\n // flow vertically.\n const horizontalWritingMode =\n getComputedStyle(source).writingMode == 'horizontal-tb';\n if (axis === \"block\")\n axis = horizontalWritingMode ? \"y\" : \"x\";\n else if (axis === \"inline\")\n axis = horizontalWritingMode ? \"x\" : \"y\";\n if (axis === \"y\")\n return sourceMeasurements.scrollHeight - sourceMeasurements.clientHeight;\n else if (axis === \"x\")\n return sourceMeasurements.scrollWidth - sourceMeasurements.clientWidth;\n}\n\nfunction resolvePx(cssValue, info) {\n const cssNumericValue = simplifyCalculation(cssValue, info);\n if (cssNumericValue instanceof CSSUnitValue) {\n if (cssNumericValue.unit === 'px') {\n return cssNumericValue.value;\n } else {\n throw TypeError(\"Unhandled unit type \" + cssNumericValue.unit);\n }\n } else {\n throw TypeError('Unsupported value type: ' + typeof (cssValue));\n }\n}\n\n// Detects if the cached source is obsolete, and updates if required\n// to ensure the new source has a scroll listener.\nfunction validateSource(timeline) {\n if (!(timeline instanceof ViewTimeline)) {\n validateAnonymousSource(timeline);\n return;\n }\n\n const node = timeline.subject;\n if (!node) {\n updateSource(timeline, null);\n return;\n }\n\n const display = getComputedStyle(node).display;\n if (display == 'none') {\n updateSource(timeline, null);\n return;\n }\n\n const source = getScrollParent(node);\n updateSource(timeline, source);\n}\n\nfunction validateAnonymousSource(timeline) {\n const details = scrollTimelineOptions.get(timeline);\n if(!details.anonymousSource)\n return;\n\n const source = getAnonymousSourceElement(details.anonymousSource, details.anonymousTarget);\n updateSource(timeline, source);\n}\n\nfunction isValidAxis(axis) {\n return [\"block\", \"inline\", \"x\", \"y\"].includes(axis);\n}\n\n/**\n * Read measurements of source element\n * @param {HTMLElement} source\n * @return {{clientWidth: *, scrollHeight: *, scrollLeft, clientHeight: *, scrollTop, scrollWidth: *}}\n */\nexport function measureSource (source) {\n const style = getComputedStyle(source);\n return {\n scrollLeft: source.scrollLeft,\n scrollTop: source.scrollTop,\n scrollWidth: source.scrollWidth,\n scrollHeight: source.scrollHeight,\n clientWidth: source.clientWidth,\n clientHeight: source.clientHeight,\n writingMode: style.writingMode,\n direction: style.direction,\n scrollPaddingTop: style.scrollPaddingTop,\n scrollPaddingBottom: style.scrollPaddingBottom,\n scrollPaddingLeft: style.scrollPaddingLeft,\n scrollPaddingRight: style.scrollPaddingRight\n };\n}\n\n/**\n * Measure subject element relative to source\n * @param {HTMLElement} source\n * @param {HTMLElement|undefined} subject\n * @param subject\n */\nexport function measureSubject(source, subject) {\n if (!source || !subject) {\n return\n }\n let top = 0;\n let left = 0;\n let node = subject;\n const ancestor = source.offsetParent;\n while (node && node != ancestor) {\n left += node.offsetLeft;\n top += node.offsetTop;\n node = node.offsetParent;\n }\n left -= source.offsetLeft + source.clientLeft;\n top -= source.offsetTop + source.clientTop;\n const style = getComputedStyle(subject);\n return {\n top,\n left,\n offsetWidth: subject.offsetWidth,\n offsetHeight: subject.offsetHeight,\n fontSize: style.fontSize,\n };\n}\n\n/**\n * Update measurements of source, and update timelines\n * @param {HTMLElement} source\n */\nfunction updateMeasurements(source) {\n let details = sourceDetails.get(source);\n details.sourceMeasurements = measureSource(source);\n\n // Update measurements of the subject of connected view timelines\n for (const ref of details.timelineRefs) {\n const timeline = ref.deref();\n if ((timeline instanceof ViewTimeline)) {\n const timelineDetails = scrollTimelineOptions.get(timeline)\n timelineDetails.subjectMeasurements = measureSubject(source, timeline.subject)\n }\n }\n\n if (details.updateScheduled)\n return;\n\n setTimeout(() => {\n // Schedule a task to update timelines after all measurements are completed\n for (const ref of details.timelineRefs) {\n const timeline = ref.deref();\n if (timeline) {\n updateInternal(timeline);\n }\n }\n\n details.updateScheduled = false;\n });\n details.updateScheduled = true;\n}\n\nfunction updateSource(timeline, source) {\n const timelineDetails = scrollTimelineOptions.get(timeline);\n const oldSource = timelineDetails.source;\n if (oldSource == source)\n return;\n\n if (oldSource) {\n const details = sourceDetails.get(oldSource);\n if (details) {\n // Remove timeline reference from old source\n details.timelineRefs.delete(timeline);\n\n // Clean up timeline refs that have been garbage-collected\n const undefinedRefs = Array.from(details.timelineRefs).filter(ref => typeof ref.deref() === 'undefined');\n for (const ref of undefinedRefs) {\n details.timelineRefs.delete(ref);\n }\n\n if (details.timelineRefs.size === 0) {\n // All timelines have been disconnected from the source\n // Clean up\n details.disconnect();\n sourceDetails.delete(oldSource);\n }\n }\n }\n timelineDetails.source = source;\n if (source) {\n let details = sourceDetails.get(source);\n if (!details) {\n // This is the first timeline for this source\n // Store a set of weak refs to connected timelines and current measurements\n details = {\n timelineRefs: new Set(),\n sourceMeasurements: measureSource(source)\n };\n sourceDetails.set(source, details);\n\n // Use resize observer to detect changes to source size\n const resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n updateMeasurements(timelineDetails.source)\n }\n });\n resizeObserver.observe(source);\n for (const child of source.children) {\n resizeObserver.observe(child)\n }\n\n // Use mutation observer to detect updated style attributes on source element\n const mutationObserver = new MutationObserver((records) => {\n for (const record of records) {\n updateMeasurements(record.target);\n }\n });\n mutationObserver.observe(source, {attributes: true, attributeFilter: ['style', 'class']});\n\n const scrollListener = () => {\n // Sample and store scroll pos\n details.sourceMeasurements.scrollLeft = source.scrollLeft;\n details.sourceMeasurements.scrollTop = source.scrollTop;\n\n for (const ref of details.timelineRefs) {\n const timeline = ref.deref();\n if (timeline) {\n updateInternal(timeline);\n }\n }\n };\n scrollEventSource(source).addEventListener(\"scroll\", scrollListener);\n details.disconnect = () => {\n resizeObserver.disconnect();\n mutationObserver.disconnect();\n scrollEventSource(source).removeEventListener(\"scroll\", scrollListener);\n };\n }\n\n // Add a weak ref to the timeline so that we can update it when the source changes\n details.timelineRefs.add(new WeakRef(timeline));\n }\n}\n\n/**\n * Removes a Web Animation instance from ScrollTimeline\n * @param scrollTimeline {ScrollTimeline}\n * @param animation {Animation}\n * @param options {Object}\n */\nexport function removeAnimation(scrollTimeline, animation) {\n let animations = scrollTimelineOptions.get(scrollTimeline).animations;\n for (let i = 0; i < animations.length; i++) {\n if (animations[i].animation == animation) {\n animations.splice(i, 1);\n }\n }\n}\n\n/**\n * Attaches a Web Animation instance to ScrollTimeline.\n * @param scrollTimeline {ScrollTimeline}\n * @param animation {Animation}\n * @param tickAnimation {function(number)}\n */\nexport function addAnimation(scrollTimeline, animation, tickAnimation) {\n let animations = scrollTimelineOptions.get(scrollTimeline).animations;\n for (let i = 0; i < animations.length; i++) {\n // @TODO: This early return causes issues when a page with the polyfill\n // is loaded from the BFCache. Ideally, this code gets fixed instead of\n // the workaround which clears the proxyAnimations cache on pagehide.\n // See https://github.com/flackr/scroll-timeline/issues/146#issuecomment-1698159183\n // for details.\n if (animations[i].animation == animation)\n return;\n }\n\n animations.push({\n animation: animation,\n tickAnimation: tickAnimation\n });\n queueMicrotask(() => {\n updateInternal(scrollTimeline);\n });\n}\n\n// TODO: this is a private function used for unit testing add function\nexport function _getStlOptions(scrollTimeline) {\n return scrollTimelineOptions.get(scrollTimeline);\n}\n\nexport class ScrollTimeline {\n constructor(options) {\n scrollTimelineOptions.set(this, {\n source: null,\n axis: DEFAULT_TIMELINE_AXIS,\n anonymousSource: (options ? options.anonymousSource : null),\n anonymousTarget: (options ? options.anonymousTarget : null),\n\n // View timeline\n subject: null,\n inset: null,\n\n // Internal members\n animations: [],\n subjectMeasurements: null\n });\n const source =\n options && options.source !== undefined ? options.source\n : document.scrollingElement;\n updateSource(this, source);\n\n if ((options && options.axis !== undefined) &&\n (options.axis != DEFAULT_TIMELINE_AXIS)) {\n if (!isValidAxis(options.axis)) {\n throw TypeError(\"Invalid axis\");\n }\n\n scrollTimelineOptions.get(this).axis = options.axis;\n }\n\n updateInternal(this);\n }\n\n set source(element) {\n updateSource(this, element);\n updateInternal(this);\n }\n\n get source() {\n return scrollTimelineOptions.get(this).source;\n }\n\n set axis(axis) {\n if (!isValidAxis(axis)) {\n throw TypeError(\"Invalid axis\");\n }\n\n scrollTimelineOptions.get(this).axis = axis;\n updateInternal(this);\n }\n\n get axis() {\n return scrollTimelineOptions.get(this).axis;\n }\n\n get duration() {\n return CSS.percent(100);\n }\n\n get phase() {\n // Per https://drafts.csswg.org/scroll-animations-1/#phase-algorithm\n // Step 1\n const unresolved = null;\n // if source is null\n const container = this.source;\n if (!container) return \"inactive\";\n let scrollerStyle = getComputedStyle(container);\n\n // if source does not currently have a CSS layout box\n if (scrollerStyle.display == \"none\")\n return \"inactive\";\n\n // if source's layout box is not a scroll container\"\n if (container != document.scrollingElement &&\n (scrollerStyle.overflow == 'visible' ||\n scrollerStyle.overflow == \"clip\")) {\n return \"inactive\";\n }\n\n return \"active\"\n }\n\n get currentTime() {\n const unresolved = null;\n const container = this.source;\n if (!container || !container.isConnected) return unresolved;\n if (this.phase == 'inactive')\n return unresolved;\n const scrollerStyle = getComputedStyle(container);\n if (\n scrollerStyle.display === \"inline\" ||\n scrollerStyle.display === \"none\"\n ) {\n return unresolved;\n }\n\n const axis = this.axis;\n const scrollPos = directionAwareScrollOffset(container, axis);\n const maxScrollPos = calculateMaxScrollOffset(container, axis);\n\n return maxScrollPos > 0 ? CSS.percent(100 * scrollPos / maxScrollPos)\n : CSS.percent(100);\n }\n\n get __polyfill() {\n return true;\n }\n}\n\n// Methods for calculation of the containing block.\n// See https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block.\n\nfunction findClosestAncestor(element, matcher) {\n let candidate = element.parentElement;\n while(candidate != null) {\n if (matcher(candidate))\n return candidate;\n candidate = candidate.parentElement;\n }\n}\n\nexport function getAnonymousSourceElement(sourceType, node) {\n switch (sourceType) {\n case 'root':\n return document.scrollingElement;\n case 'nearest':\n return getScrollParent(node);\n case 'self':\n return node;\n default:\n throw new TypeError('Invalid ScrollTimeline Source Type.');\n }\n}\n\nfunction isBlockContainer(element) {\n const style = getComputedStyle(element);\n switch (style.display) {\n case 'block':\n case 'inline-block':\n case 'list-item':\n case 'table':\n case 'table-caption':\n case 'flow-root':\n case 'flex':\n case 'grid':\n return true;\n }\n\n return false;\n}\n\nfunction isFixedElementContainer(element) {\n const style = getComputedStyle(element);\n if (style.transform != 'none' || style.perspective != 'none')\n return true;\n\n if (style.willChange == 'transform' || style.willChange == 'perspective')\n return true;\n\n if (style.filter != 'none' || style.willChange == 'filter')\n return true;\n\n if (style.backdropFilter != 'none')\n return true;\n\n return false;\n}\n\nfunction isAbsoluteElementContainer(element) {\n const style = getComputedStyle(element);\n if (style.position != 'static')\n return true;\n\n return isFixedElementContainer(element);\n}\n\nfunction getContainingBlock(element) {\n switch (getComputedStyle(element).position) {\n case 'static':\n case 'relative':\n case 'sticky':\n return findClosestAncestor(element, isBlockContainer);\n\n case 'absolute':\n return findClosestAncestor(element, isAbsoluteElementContainer);\n\n case 'fixed':\n return findClosestAncestor(element, isFixedElementContainer);\n }\n}\n\nexport function getScrollParent(node) {\n if (!node || !node.isConnected)\n return undefined;\n\n while (node = getContainingBlock(node)) {\n const style = getComputedStyle(node);\n switch(style['overflow-x']) {\n case 'auto':\n case 'scroll':\n case 'hidden':\n // https://drafts.csswg.org/css-overflow-3/#overflow-propagation\n // The UA must apply the overflow from the root element to the viewport;\n // however, if the overflow is visible in both axis, then the overflow\n // of the first visible child body is applied instead.\n if (node == document.body &&\n getComputedStyle(document.scrollingElement).overflow == \"visible\")\n return document.scrollingElement;\n\n return node;\n }\n }\n return document.scrollingElement;\n}\n\n// ---- View timelines -----\n\n// Computes the scroll offsets corresponding to the [0, 100]% range for a\n// specific phase on a view timeline.\n// TODO: Track changes to determine when associated animations require their\n// timing to be renormalized.\nexport function range(timeline, phase) {\n const details = scrollTimelineOptions.get(timeline);\n const subjectMeasurements = details.subjectMeasurements\n const sourceMeasurements = sourceDetails.get(details.source).sourceMeasurements\n\n const unresolved = null;\n if (timeline.phase === 'inactive')\n return unresolved;\n\n if (!(timeline instanceof ViewTimeline))\n return unresolved;\n\n return calculateRange(phase, sourceMeasurements, subjectMeasurements, details.axis, details.inset);\n}\n\nexport function calculateRange(phase, sourceMeasurements, subjectMeasurements, axis, optionsInset) {\n // TODO: handle position sticky\n\n // Determine the view and container size based on the scroll direction.\n // The view position is the scroll position of the logical starting edge\n // of the view.\n const rtl = sourceMeasurements.direction == 'rtl' || sourceMeasurements.writingMode == 'vertical-rl';\n let viewSize = undefined;\n let viewPos = undefined;\n let sizes = {\n fontSize: subjectMeasurements.fontSize\n };\n if (normalizeAxis(axis, sourceMeasurements) === 'x') {\n viewSize = subjectMeasurements.offsetWidth;\n viewPos = subjectMeasurements.left;\n sizes.scrollPadding = [sourceMeasurements.scrollPaddingLeft, sourceMeasurements.scrollPaddingRight];\n if (rtl) {\n viewPos += sourceMeasurements.scrollWidth - sourceMeasurements.clientWidth;\n sizes.scrollPadding = [sourceMeasurements.scrollPaddingRight, sourceMeasurements.scrollPaddingLeft];\n }\n sizes.containerSize = sourceMeasurements.clientWidth;\n } else {\n // TODO: support sideways-lr\n viewSize = subjectMeasurements.offsetHeight;\n viewPos = subjectMeasurements.top;\n sizes.scrollPadding = [sourceMeasurements.scrollPaddingTop, sourceMeasurements.scrollPaddingBottom];\n sizes.containerSize = sourceMeasurements.clientHeight;\n }\n\n const inset = calculateInset(optionsInset, sizes);\n\n // Cover:\n // 0% progress represents the position at which the start border edge of the\n // element’s principal box coincides with the end edge of its view progress\n // visibility range.\n // 100% progress represents the position at which the end border edge of the\n // element’s principal box coincides with the start edge of its view progress\n // visibility range.\n const coverStartOffset = viewPos - sizes.containerSize + inset.end;\n const coverEndOffset = viewPos + viewSize - inset.start;\n\n // Contain:\n // The 0% progress represents the earlier of the following positions:\n // 1. The start border edge of the element’s principal box coincides with\n // the start edge of its view progress visibility range.\n // 2. The end border edge of the element’s principal box coincides with\n // the end edge of its view progress visibility range.\n // The 100% progress represents the greater of the following positions:\n // 1. The start border edge of the element’s principal box coincides with\n // the start edge of its view progress visibility range.\n // 2. The end border edge of the element’s principal box coincides with\n // the end edge of its view progress visibility range.\n const alignStartOffset = coverStartOffset + viewSize;\n const alignEndOffset = coverEndOffset - viewSize;\n const containStartOffset = Math.min(alignStartOffset, alignEndOffset);\n const containEndOffset = Math.max(alignStartOffset, alignEndOffset);\n\n // Entry and Exit bounds align with cover and contains bounds.\n\n let startOffset = undefined;\n let endOffset = undefined;\n // Take inset into account when determining the scrollport size\n const adjustedScrollportSize = sizes.containerSize - inset.start - inset.end;\n const subjectIsLargerThanScrollport = viewSize > adjustedScrollportSize;\n\n switch(phase) {\n case 'cover':\n startOffset = coverStartOffset;\n endOffset = coverEndOffset;\n break;\n\n case 'contain':\n startOffset = containStartOffset;\n endOffset = containEndOffset;\n break;\n\n case 'entry':\n startOffset = coverStartOffset;\n endOffset = containStartOffset;\n break;\n\n case 'exit':\n startOffset = containEndOffset;\n endOffset = coverEndOffset;\n break;\n\n case 'entry-crossing':\n startOffset = coverStartOffset;\n endOffset = subjectIsLargerThanScrollport ? containEndOffset : containStartOffset;\n break;\n\n case 'exit-crossing':\n startOffset = subjectIsLargerThanScrollport ? containStartOffset : containEndOffset;\n endOffset = coverEndOffset;\n break;\n }\n return { start: startOffset, end: endOffset };\n}\n\nfunction parseInset(value) {\n const inset = { start: 0, end: 0 };\n\n if (!value) return inset;\n\n let parts;\n // Parse string parts to\n if (typeof value === 'string') {\n parts = splitIntoComponentValues(value).map(str => {\n if (str === 'auto') {\n return 'auto';\n }\n try {\n return CSSNumericValue.parse(str);\n } catch (e) {\n throw TypeError(`Could not parse inset \"${value}\"`);\n }\n });\n } else if (Array.isArray(value)) {\n parts = value;\n } else {\n parts = [value];\n }\n if (parts.length === 0 || parts.length > 2) {\n throw TypeError('Invalid inset');\n }\n\n // Validate that the parts are 'auto' or \n for (const part of parts) {\n if (part === 'auto') {\n continue;\n }\n const type = part.type();\n if (!(type.length === 1 || type.percent === 1)) {\n throw TypeError('Invalid inset');\n }\n }\n\n return {\n start: parts[0],\n end: parts[1] ?? parts[0]\n };\n}\n\nfunction calculateInset(value, sizes) {\n const inset = { start: 0, end: 0 };\n\n if (!value) return inset;\n\n const [start, end] = [value.start, value.end].map((part, i) => {\n if (part === 'auto') {\n return sizes.scrollPadding[i] === 'auto' ? 0 : parseFloat(sizes.scrollPadding[i]);\n }\n\n return resolvePx(part, {\n percentageReference: CSS.px(sizes.containerSize),\n fontSize: CSS.px(parseFloat(sizes.fontSize))\n })\n });\n\n return { start, end };\n}\n\n// Calculate the fractional offset of a range value relative to the normal range.\nexport function fractionalOffset(timeline, value) {\n if (timeline instanceof ViewTimeline) {\n const { rangeName, offset } = value;\n\n const phaseRange = range(timeline, rangeName);\n const coverRange = range(timeline, 'cover');\n\n return calculateRelativePosition(phaseRange, offset, coverRange, timeline.subject);\n }\n\n if (timeline instanceof ScrollTimeline) {\n const { axis, source } = timeline;\n const { sourceMeasurements } = sourceDetails.get(source);\n\n let sourceScrollDistance = undefined;\n if (normalizeAxis(axis, sourceMeasurements) === 'x') {\n sourceScrollDistance = sourceMeasurements.scrollWidth - sourceMeasurements.clientWidth;\n } else {\n sourceScrollDistance = sourceMeasurements.scrollHeight - sourceMeasurements.clientHeight;\n }\n\n // TODO: pass relative measurements (viewport, font-size, root font-size, etc. ) to resolvePx() to resolve relative units\n const position = resolvePx(value, {percentageReference: CSS.px(sourceScrollDistance)});\n const fractionalOffset = position / sourceScrollDistance;\n\n return fractionalOffset;\n }\n\n unsupportedTimeline(timeline);\n}\n\nexport function calculateRelativePosition(phaseRange, offset, coverRange, subject) {\n if (!phaseRange || !coverRange)\n return 0;\n\n let style = getComputedStyle(subject)\n const info = {\n percentageReference: CSS.px(phaseRange.end - phaseRange.start),\n fontSize: CSS.px(parseFloat(style.fontSize))\n };\n\n const offsetPX = resolvePx(offset, info) + phaseRange.start;\n return (offsetPX - coverRange.start) / (coverRange.end - coverRange.start);\n}\n\n// https://drafts.csswg.org/scroll-animations-1/#view-progress-timelines\nexport class ViewTimeline extends ScrollTimeline {\n // As specced, ViewTimeline has a subject and a source, but\n // ViewTimelineOptions only has source. Furthermore, there is a strict\n // relationship between subject and source (source is nearest scrollable\n // ancestor of subject).\n\n // Proceeding under the assumption that subject will be added to\n // ViewTimelineOptions. Inferring the source from the subject if not\n // explicitly set.\n constructor(options) {\n super(options);\n const details = scrollTimelineOptions.get(this);\n details.subject = options && options.subject ? options.subject : undefined;\n // TODO: Handle insets.\n if (options && options.inset) {\n details.inset = parseInset(options.inset);\n }\n if (details.subject) {\n const resizeObserver = new ResizeObserver(() => {\n updateMeasurements(details.source)\n })\n resizeObserver.observe(details.subject)\n\n const mutationObserver = new MutationObserver(() => {\n updateMeasurements(details.source);\n });\n mutationObserver.observe(details.subject, {attributes: true, attributeFilter: ['class', 'style']});\n }\n validateSource(this);\n details.subjectMeasurements = measureSubject(details.source, details.subject);\n updateInternal(this);\n }\n\n get source() {\n validateSource(this);\n return scrollTimelineOptions.get(this).source;\n }\n\n set source(source) {\n throw new Error(\"Cannot set the source of a view timeline\");\n }\n\n get subject() {\n return scrollTimelineOptions.get(this).subject;\n }\n\n // The axis is called \"axis\" for a view timeline.\n // Internally we still call it axis.\n get axis() {\n return scrollTimelineOptions.get(this).axis;\n }\n\n get currentTime() {\n const unresolved = null;\n const scrollPos = directionAwareScrollOffset(this.source, this.axis);\n if (scrollPos == unresolved)\n return unresolved;\n\n const offsets = range(this, 'cover');\n if (!offsets)\n return unresolved;\n const progress =\n (scrollPos - offsets.start) / (offsets.end - offsets.start);\n\n return CSS.percent(100 * progress);\n }\n\n get startOffset() {\n return CSS.px(range(this,'cover').start);\n }\n\n get endOffset() {\n return CSS.px(range(this,'cover').end);\n }\n\n}\n","import {\n ANIMATION_RANGE_NAMES,\n ScrollTimeline,\n addAnimation,\n removeAnimation,\n fractionalOffset,\n} from \"./scroll-timeline-base\";\nimport {splitIntoComponentValues} from './utils';\nimport {simplifyCalculation} from './simplify-calculation';\n\nconst nativeDocumentGetAnimations = document.getAnimations;\nconst nativeElementGetAnimations = window.Element.prototype.getAnimations;\nconst nativeElementAnimate = window.Element.prototype.animate;\nconst nativeAnimation = window.Animation;\n\nclass PromiseWrapper {\n constructor() {\n this.state = 'pending';\n this.nativeResolve = this.nativeReject = null;\n this.promise = new Promise((resolve, reject) => {\n this.nativeResolve = resolve;\n this.nativeReject = reject;\n });\n }\n resolve(value) {\n this.state = 'resolved';\n this.nativeResolve(value);\n }\n reject(reason) {\n this.state = 'rejected';\n // Do not report unhandled promise rejections.\n this.promise.catch(() => {});\n this.nativeReject(reason);\n }\n}\n\nfunction createReadyPromise(details) {\n details.readyPromise = new PromiseWrapper();\n // Trigger the pending task on the next animation frame.\n requestAnimationFrame(() => {\n const timelineTime = details.timeline?.currentTime ?? null;\n if (timelineTime === null) {\n return\n }\n // Run auto align start time procedure, in case measurements are ready\n autoAlignStartTime(details);\n if (details.pendingTask === 'play' && (details.startTime !== null || details.holdTime !== null)) {\n commitPendingPlay(details);\n } else if (details.pendingTask === 'pause') {\n commitPendingPause(details);\n }\n });\n}\n\nfunction createAbortError() {\n return new DOMException(\"The user aborted a request\", \"AbortError\");\n}\n\n// Converts a time from its internal representation to a percent. For a\n// monotonic timeline, time is reported as a double with implicit units of\n// milliseconds. For progress-based animations, times are reported as\n// percentages.\nfunction toCssNumberish(details, value) {\n if (value === null)\n return value;\n\n if (typeof value !== 'number') {\n throw new DOMException(\n `Unexpected value: ${value}. Cannot convert to CssNumberish`,\n \"InvalidStateError\");\n }\n\n const rangeDuration = details.rangeDuration ?? 100;\n const limit = effectEnd(details);\n const percent = limit ? rangeDuration * value / limit : 0;\n return CSS.percent(percent);\n}\n\n// Covnerts a time to its internal representation. Progress-based animations\n// use times expressed as percentages. Each progress-based animation is backed\n// by a native animation with a document timeline in the polyfill. Thus, we\n// need to convert the timing from percent to milliseconds with implicit units.\nfunction fromCssNumberish(details, value) {\n if (!details.timeline) {\n // Document timeline\n if (value == null || typeof value === 'number')\n return value;\n\n const convertedTime = value.to('ms');\n if (convertedTime)\n return convertedTime.value;\n\n throw new DOMException(\n \"CSSNumericValue must be either a number or a time value for \" +\n \"time based animations.\",\n \"InvalidStateError\");\n } else {\n // Scroll timeline.\n if (value === null)\n return value;\n\n if (value.unit === 'percent') {\n const rangeDuration = details.rangeDuration ?? 100;\n const duration = effectEnd(details);\n return value.value * duration / rangeDuration;\n }\n\n throw new DOMException(\n \"CSSNumericValue must be a percentage for progress based animations.\",\n \"NotSupportedError\");\n }\n}\n\nfunction normalizedTiming(details) {\n // Used normalized timing in the case of a progress-based animation or\n // specified timing with a document timeline. The normalizedTiming property\n // is initialized and cached when fetching the timing information.\n const timing = details.proxy.effect.getTiming();\n return details.normalizedTiming || timing;\n}\n\nfunction commitPendingPlay(details) {\n // https://drafts4.csswg.org/web-animations-2/#playing-an-animation-section\n // Refer to steps listed under \"Schedule a task to run ...\"\n\n const timelineTime = fromCssNumberish(details, details.timeline.currentTime);\n if (details.holdTime != null) {\n // A: If animation’s hold time is resolved,\n // A.1. Apply any pending playback rate on animation.\n // A.2. Let new start time be the result of evaluating:\n // ready time - hold time / playback rate for animation.\n // If the playback rate is zero, let new start time be simply ready\n // time.\n // A.3. Set the start time of animation to new start time.\n // A.4. If animation’s playback rate is not 0, make animation’s hold\n // time unresolved.\n applyPendingPlaybackRate(details);\n if (details.animation.playbackRate == 0) {\n details.startTime = timelineTime;\n } else {\n details.startTime\n = timelineTime -\n details.holdTime / details.animation.playbackRate;\n details.holdTime = null;\n }\n } else if (details.startTime !== null &&\n details.pendingPlaybackRate !== null) {\n // B: If animation’s start time is resolved and animation has a pending\n // playback rate,\n // B.1. Let current time to match be the result of evaluating:\n // (ready time - start time) × playback rate for animation.\n // B.2 Apply any pending playback rate on animation.\n // B.3 If animation’s playback rate is zero, let animation’s hold time\n // be current time to match.\n // B.4 Let new start time be the result of evaluating:\n // ready time - current time to match / playback rate\n // for animation.\n // If the playback rate is zero, let new start time be simply ready\n // time.\n // B.5 Set the start time of animation to new start time.\n const currentTimeToMatch =\n (timelineTime - details.startTime) * details.animation.playbackRate;\n applyPendingPlaybackRate(details);\n const playbackRate = details.animation.playbackRate;\n if (playbackRate == 0) {\n details.holdTime = null;\n details.startTime = timelineTime;\n } else {\n details.startTime = timelineTime - currentTimeToMatch / playbackRate;\n }\n }\n\n // 8.4 Resolve animation’s current ready promise with animation.\n if (details.readyPromise && details.readyPromise.state == 'pending')\n details.readyPromise.resolve(details.proxy);\n\n // 8.5 Run the procedure to update an animation’s finished state for\n // animation with the did seek flag set to false, and the\n // synchronously notify flag set to false.\n updateFinishedState(details, false, false);\n\n // Additional polyfill step to update the native animation's current time.\n syncCurrentTime(details);\n details.pendingTask = null;\n};\n\nfunction commitPendingPause(details) {\n // https://www.w3.org/TR/web-animations-1/#pausing-an-animation-section\n // Refer to steps listed under \"Schedule a task to run ...\"\n\n // 1. Let ready time be the time value of the timeline associated with\n // animation at the moment when the user agent completed processing\n // necessary to suspend playback of animation’s target effect.\n const readyTime = fromCssNumberish(details, details.timeline.currentTime);\n\n // 2. If animation’s start time is resolved and its hold time is not\n // resolved, let animation’s hold time be the result of evaluating\n // (ready time - start time) × playback rate.\n if (details.startTime != null && details.holdTime == null) {\n details.holdTime =\n (readyTime - details.startTime) * details.animation.playbackRate;\n }\n\n // 3. Apply any pending playback rate on animation.\n applyPendingPlaybackRate(details);\n\n // 4. Make animation’s start time unresolved.\n details.startTime = null;\n\n // 5. Resolve animation’s current ready promise with animation.\n details.readyPromise.resolve(details.proxy);\n\n // 6. Run the procedure to update an animation’s finished state for\n // animation with the did seek flag set to false, and the synchronously\n // notify flag set to false.\n updateFinishedState(details, false, false);\n\n // Additional polyfill step to update the native animation's current time.\n syncCurrentTime(details);\n details.pendingTask = null;\n};\n\nfunction commitFinishedNotification(details) {\n if (!details.finishedPromise || details.finishedPromise.state != 'pending')\n return;\n\n if (details.proxy.playState != 'finished')\n return;\n\n details.finishedPromise.resolve(details.proxy);\n\n details.animation.pause();\n\n // Event times are speced as doubles in web-animations-1.\n // Cannot dispatch a proxy to an event since the proxy is not a fully\n // transparent replacement. As a workaround, use a custom event and inject\n // the necessary getters.\n const finishedEvent =\n new CustomEvent('finish',\n { detail: {\n currentTime: details.proxy.currentTime,\n timelineTime: details.proxy.timeline.currentTime\n }});\n Object.defineProperty(finishedEvent, 'currentTime', {\n get: function() { return this.detail.currentTime; }\n });\n Object.defineProperty(finishedEvent, 'timelineTime', {\n get: function() { return this.detail.timelineTime; }\n });\n\n requestAnimationFrame(() => {\n queueMicrotask(() => {\n details.animation.dispatchEvent(finishedEvent);\n });\n });\n}\n\nfunction effectivePlaybackRate(details) {\n if (details.pendingPlaybackRate !== null)\n return details.pendingPlaybackRate;\n return details.animation.playbackRate;\n}\n\nfunction applyPendingPlaybackRate(details) {\n if (details.pendingPlaybackRate !== null) {\n details.animation.playbackRate = details.pendingPlaybackRate;\n details.pendingPlaybackRate = null;\n }\n}\n\n/**\n * Procedure to silently set the current time of an animation to seek time\n * https://drafts.csswg.org/web-animations-2/#silently-set-the-current-time\n * @param details\n * @param {CSSUnitValue} seekTime\n */\nfunction silentlySetTheCurrentTime(details, seekTime) {\n // The procedure to silently set the current time of an animation, animation, to seek time is as follows:\n // 1. If seek time is an unresolved time value, then perform the following steps.\n // 1. If the current time is resolved, then throw a TypeError.\n // 2. Abort these steps.\n if (seekTime == null) {\n if (details.currentTime !== null) {\n throw new TypeError();\n }\n }\n // 2. Let valid seek time be the result of running the validate a CSSNumberish time procedure with seek time as the input.\n // 3. If valid seek time is false, abort this procedure.\n seekTime = fromCssNumberish(details, seekTime);\n\n // 4. Set auto align start time to false.\n details.autoAlignStartTime = false;\n\n // 5. Update either animation’s hold time or start time as follows:\n //\n // 5a If any of the following conditions are true:\n // - animation’s hold time is resolved, or\n // - animation’s start time is unresolved, or\n // - animation has no associated timeline or the associated timeline is inactive, or\n // - animation’s playback rate is 0,\n // 1. Set animation’s hold time to seek time.\n //\n // 5b Otherwise,\n // Set animation’s start time to the result of evaluating timeline time - (seek time / playback rate) where\n // timeline time is the current time value of timeline associated with animation.\n if (details.holdTime !== null || details.startTime === null ||\n details.timeline.phase === 'inactive' || details.animation.playbackRate === 0) {\n details.holdTime = seekTime;\n } else {\n details.startTime =\n fromCssNumberish(details, details.timeline.currentTime) - seekTime / details.animation.playbackRate;\n }\n\n // 6. If animation has no associated timeline or the associated timeline is inactive, make animation’s start time\n // unresolved.\n // This preserves the invariant that when we don’t have an active timeline it is only possible to set either the\n // start time or the animation’s current time.\n if (details.timeline.phase === 'inactive') {\n details.startTime = null;\n }\n\n // 7. Make animation’s previous current time unresolved.\n details.previousCurrentTime = null\n}\n\nfunction calculateCurrentTime(details) {\n if (!details.timeline)\n return null;\n\n const timelineTime = fromCssNumberish(details, details.timeline.currentTime);\n if (timelineTime === null)\n return null;\n\n if (details.startTime === null)\n return null;\n\n let currentTime =\n (timelineTime - details.startTime) * details.animation.playbackRate;\n\n // Handle special case.\n if (currentTime == -0)\n currentTime = 0;\n\n return currentTime;\n}\n\nfunction calculateStartTime(details, currentTime) {\n if (!details.timeline)\n return null;\n\n const timelineTime = fromCssNumberish(details, details.timeline.currentTime);\n if (timelineTime == null)\n return null;\n\n return timelineTime - currentTime / details.animation.playbackRate;\n}\n\nfunction updateFinishedState(details, didSeek, synchronouslyNotify) {\n if (!details.timeline)\n return;\n\n // https://www.w3.org/TR/web-animations-1/#updating-the-finished-state\n // 1. Calculate the unconstrained current time. The dependency on did_seek is\n // required to accommodate timelines that may change direction. Without this\n // distinction, a once-finished animation would remain finished even when its\n // timeline progresses in the opposite direction.\n let unconstrainedCurrentTime =\n didSeek ? fromCssNumberish(details, details.proxy.currentTime)\n : calculateCurrentTime(details);\n\n // 2. Conditionally update the hold time.\n if (unconstrainedCurrentTime && details.startTime != null &&\n !details.proxy.pending) {\n // Can seek outside the bounds of the active effect. Set the hold time to\n // the unconstrained value of the current time in the event that this update\n // is the result of explicitly setting the current time and the new time\n // is out of bounds. An update due to a time tick should not snap the hold\n // value back to the boundary if previously set outside the normal effect\n // boundary. The value of previous current time is used to retain this\n // value.\n const playbackRate = effectivePlaybackRate(details);\n const upperBound = effectEnd(details);\n let boundary = details.previousCurrentTime;\n if (playbackRate > 0 && unconstrainedCurrentTime >= upperBound &&\n details.previousCurrentTime != null) {\n if (boundary === null || boundary < upperBound)\n boundary = upperBound;\n details.holdTime = didSeek ? unconstrainedCurrentTime : boundary;\n } else if (playbackRate < 0 && unconstrainedCurrentTime <= 0) {\n if (boundary == null || boundary > 0)\n boundary = 0;\n details.holdTime = didSeek ? unconstrainedCurrentTime : boundary;\n } else if (playbackRate != 0) {\n // Update start time and reset hold time.\n if (didSeek && details.holdTime !== null)\n details.startTime = calculateStartTime(details, details.holdTime);\n details.holdTime = null;\n }\n }\n\n // Additional step to ensure that the native animation has the same value for\n // current time as the proxy.\n syncCurrentTime(details);\n\n // 3. Set the previous current time.\n details.previousCurrentTime = fromCssNumberish(details,\n details.proxy.currentTime);\n\n // 4. Set the current finished state.\n const playState = details.proxy.playState;\n\n if (playState == 'finished') {\n if (!details.finishedPromise)\n details.finishedPromise = new PromiseWrapper();\n if (details.finishedPromise.state == 'pending') {\n // 5. Setup finished notification.\n if (synchronouslyNotify) {\n commitFinishedNotification(details);\n } else {\n Promise.resolve().then(() => {\n commitFinishedNotification(details);\n });\n }\n }\n } else {\n // 6. If not finished but the current finished promise is already resolved,\n // create a new promise.\n if (details.finishedPromise &&\n details.finishedPromise.state == 'resolved') {\n details.finishedPromise = new PromiseWrapper();\n }\n if (details.animation.playState != 'paused')\n details.animation.pause();\n }\n}\n\nfunction effectEnd(details) {\n // https://www.w3.org/TR/web-animations-1/#end-time\n const timing = normalizedTiming(details);\n const totalDuration =\n timing.delay + timing.endDelay + timing.iterations * timing.duration;\n\n return Math.max(0, totalDuration);\n}\n\nfunction hasActiveTimeline(details) {\n return !details.timeline || details.timeline.phase != 'inactive';\n}\n\nfunction syncCurrentTime(details) {\n if (!details.timeline)\n return;\n\n if (details.startTime !== null) {\n const timelineTime = details.timeline.currentTime;\n if (timelineTime == null)\n return;\n\n const timelineTimeMs = fromCssNumberish(details, timelineTime);\n\n setNativeCurrentTime(details,\n (timelineTimeMs - details.startTime) *\n details.animation.playbackRate);\n } else if (details.holdTime !== null) {\n setNativeCurrentTime(details, details.holdTime);\n }\n}\n\n// Sets the time of the underlying animation, nudging the time slightly if at\n// a scroll-timeline boundary to remain in the active phase.\nfunction setNativeCurrentTime(details, time) {\n const timeline = details.timeline;\n const playbackRate = details.animation.playbackRate;\n const atScrollTimelineBoundary =\n timeline.currentTime &&\n timeline.currentTime.value == (playbackRate < 0 ? 0 : 100);\n const delta =\n atScrollTimelineBoundary ? (playbackRate < 0 ? 0.001 : -0.001) : 0;\n\n details.animation.currentTime = time + delta;\n}\n\nfunction resetPendingTasks(details) {\n // https://www.w3.org/TR/web-animations-1/#reset-an-animations-pending-tasks\n\n // 1. If animation does not have a pending play task or a pending pause task,\n // abort this procedure.\n if (!details.pendingTask)\n return;\n\n // 2. If animation has a pending play task, cancel that task.\n // 3. If animation has a pending pause task, cancel that task.\n details.pendingTask = null;\n\n // 4. Apply any pending playback rate on animation.\n applyPendingPlaybackRate(details);\n\n // 5. Reject animation’s current ready promise with a DOMException named\n // \"AbortError\".\n details.readyPromise.reject(createAbortError());\n\n // 6. Let animation’s current ready promise be the result of creating a new\n // resolved Promise object.\n createReadyPromise(details);\n details.readyPromise.resolve(details.proxy);\n}\n\nfunction playInternal(details, autoRewind) {\n if (!details.timeline)\n return;\n\n // https://drafts.csswg.org/web-animations/#playing-an-animation-section.\n // 1. Let aborted pause be a boolean flag that is true if animation has a\n // pending pause task, and false otherwise.\n const abortedPause =\n details.proxy.playState == 'paused' && details.proxy.pending;\n\n // 2. Let has pending ready promise be a boolean flag that is initially\n // false.\n let hasPendingReadyPromise = false;\n\n // 3. Let has finite timeline be true if animation has an associated\n // timeline that is not monotonically increasing.\n // Note: this value will always true at this point in the polyfill.\n // Following steps are pruned based on the procedure for scroll\n // timelines.\n //\n // 4. Let previous current time be the animation’s current time\n //\n // 5. Let enable seek be true if the auto-rewind flag is true and has finite timeline is false.\n // Otherwise, initialize to false.\n //\n // 6. Perform the steps corresponding to the first matching condition from\n // the following, if any:\n //\n // 6a If animation’s effective playback rate > 0, enable seek is\n // true and either animation’s:\n // previous current time is unresolved, or\n // previous current time < zero, or\n // previous current time >= associated effect end,\n // 6a1. Set the animation’s hold time to zero.\n //\n // 6b If animation’s effective playback rate < 0, enable seek is\n // true and either animation’s:\n // previous current time is unresolved, or\n // previous current time is ≤ zero, or\n // previous current time is > associated effect end,\n // 6b1. If associated effect end is positive infinity,\n // throw an \"InvalidStateError\" DOMException and abort these steps.\n // 6b2. Otherwise,\n // 5b2a Set the animation’s hold time to the animation’s associated effect end.\n //\n // 6c If animation’s effective playback rate = 0 and animation’s current time\n // is unresolved,\n // 6c1. Set the animation’s hold time to zero.\n let previousCurrentTime = fromCssNumberish(details,\n details.proxy.currentTime);\n\n const playbackRate = effectivePlaybackRate(details);\n if (playbackRate == 0 && previousCurrentTime == null) {\n details.holdTime = 0;\n }\n // 7. If has finite timeline and previous current time is unresolved:\n // Set the flag auto align start time to true.\n // NOTE: If play is called for a CSS animation during style update, the animation’s start time cannot be reliably\n // calculated until post layout since the start time is to align with the start or end of the animation range\n // (depending on the playback rate). In this case, the animation is said to have an auto-aligned start time,\n // whereby the start time is automatically adjusted as needed to align the animation’s progress to the\n // animation range.\n if (previousCurrentTime == null) {\n details.autoAlignStartTime = true;\n }\n\n // Not by spec, but required by tests in play-animation.html:\n // - Playing a finished animation restarts the animation aligned at the start\n // - Playing a pause-pending but previously finished animation realigns with the scroll position\n // - Playing a finished animation clears the start time\n if (details.proxy.playState === 'finished' || abortedPause) {\n details.holdTime = null\n details.startTime = null\n details.autoAlignStartTime = true;\n }\n\n // 8. If animation's hold time is resolved, let its start time be\n // unresolved.\n if (details.holdTime) {\n details.startTime = null;\n }\n\n // 9. If animation has a pending play task or a pending pause task,\n // 9.1 Cancel that task.\n // 9.2 Set has pending ready promise to true.\n if (details.pendingTask) {\n details.pendingTask = null;\n hasPendingReadyPromise = true;\n }\n\n // 10. If the following three conditions are all satisfied:\n // animation’s hold time is unresolved, and\n // aborted pause is false, and\n // animation does not have a pending playback rate,\n // abort this procedure.\n // Additonal check for polyfill: Does not have the auto align start time flag set.\n // If we return when this flag is set, a play task will not be scheduled, leaving the animation in the\n // idle state. If the animation is in the idle state, the auto align procedure will bail.\n // TODO: update with results of https://github.com/w3c/csswg-drafts/issues/9871\n if (details.holdTime === null && !details.autoAlignStartTime &&\n !abortedPause && details.pendingPlaybackRate === null)\n return;\n\n // 11. If has pending ready promise is false, let animation’s current ready\n // promise be a new promise in the relevant Realm of animation.\n if (details.readyPromise && !hasPendingReadyPromise)\n details.readyPromise = null;\n\n // Additional polyfill step to ensure that the native animation has the\n // correct value for current time.\n syncCurrentTime(details);\n\n // 12. Schedule a task to run as soon as animation is ready.\n if (!details.readyPromise)\n createReadyPromise(details);\n details.pendingTask = 'play';\n\n // Additional step for the polyfill.\n // This must run after setting up the ready promise, otherwise we will run\n // the procedure for calculating auto aligned start time before play state is running\n addAnimation(details.timeline, details.animation,\n tickAnimation.bind(details.proxy));\n\n // 13. Run the procedure to update an animation’s finished state for animation\n // with the did seek flag set to false, and the synchronously notify flag\n // set to false.\n updateFinishedState(details, /* seek */ false, /* synchronous */ false);\n}\n\nfunction tickAnimation(timelineTime) {\n const details = proxyAnimations.get(this);\n if (!details) return;\n\n if (timelineTime == null) {\n // While the timeline is inactive, it's effect should not be applied.\n // To polyfill this behavior, we cancel the underlying animation.\n if (details.proxy.playState !== 'paused' && details.animation.playState != 'idle')\n details.animation.cancel();\n return;\n }\n\n // When updating timeline current time, the start time of any attached animation is conditionally updated. For each\n // attached animation, run the procedure for calculating an auto-aligned start time.\n autoAlignStartTime(details);\n\n if (details.pendingTask) {\n // Commit pending tasks asynchronously if they are ready after aligning start time\n requestAnimationFrame(() => {\n if (details.pendingTask === 'play' && (details.startTime !== null || details.holdTime !== null)) {\n commitPendingPlay(details);\n } else if (details.pendingTask === 'pause') {\n commitPendingPause(details);\n }\n });\n }\n\n const playState = this.playState;\n if (playState == 'running' || playState == 'finished') {\n const timelineTimeMs = fromCssNumberish(details, timelineTime);\n\n setNativeCurrentTime(\n details,\n (timelineTimeMs - fromCssNumberish(details, this.startTime)) *\n this.playbackRate);\n\n updateFinishedState(details, false, false);\n }\n}\n\nfunction renormalizeTiming(details) {\n // Force renormalization.\n details.specifiedTiming = null;\n}\n\nfunction createProxyEffect(details) {\n const effect = details.animation.effect;\n const nativeUpdateTiming = effect.updateTiming;\n\n // Generic pass-through handler for any method or attribute that is not\n // explicitly overridden.\n const handler = {\n get: function(obj, prop) {\n const result = obj[prop];\n if (typeof result === 'function')\n return result.bind(effect);\n return result;\n },\n\n set: function(obj, prop, value) {\n obj[prop] = value;\n return true;\n }\n };\n // Override getComputedTiming to convert to percentages when using a\n // progress-based timeline.\n const getComputedTimingHandler = {\n apply: function(target) {\n // Ensure that the native animation is using normalized values.\n effect.getTiming();\n\n const timing = target.apply(effect);\n\n if (details.timeline) {\n const rangeDuration = details.duration ?? 100;\n timing.localTime = toCssNumberish(details, timing.localTime);\n timing.endTime = toCssNumberish(details, timing.endTime);\n timing.activeDuration =\n toCssNumberish(details, timing.activeDuration);\n const limit = effectEnd(details);\n const iteration_duration = timing.iterations ?\n (limit - timing.delay - timing.endDelay) / timing.iterations : 0;\n timing.duration = limit ?\n CSS.percent(rangeDuration * iteration_duration / limit) :\n CSS.percent(0);\n\n // Correct for inactive timeline.\n if (details.timeline.currentTime === undefined) {\n timing.localTime = null;\n }\n }\n return timing;\n }\n };\n // Override getTiming to normalize the timing. EffectEnd for the animation\n // align with the range duration.\n const getTimingHandler = {\n apply: function(target, thisArg) {\n // Arbitrary conversion of 100% to ms.\n const INTERNAL_DURATION_MS = 100000;\n\n if (details.specifiedTiming)\n return details.specifiedTiming;\n\n details.specifiedTiming = target.apply(effect);\n let timing = Object.assign({}, details.specifiedTiming);\n\n let totalDuration;\n\n if (timing.duration === Infinity) {\n throw TypeError(\n \"Effect duration cannot be Infinity when used with Scroll \" +\n \"Timelines\");\n }\n\n // Duration 'auto' case.\n if (timing.duration === null || timing.duration === 'auto' || details.autoDurationEffect) {\n if (details.timeline) {\n details.autoDurationEffect = true\n // TODO: start and end delay are specced as doubles and currently\n // ignored for a progress based animation. Support delay and endDelay\n // once CSSNumberish.\n timing.delay = 0;\n timing.endDelay = 0;\n totalDuration = timing.iterations ? INTERNAL_DURATION_MS : 0;\n timing.duration = timing.iterations\n ? (totalDuration - timing.delay - timing.endDelay) /\n timing.iterations\n : 0;\n // When the rangeStart comes after the rangeEnd, we end up in a situation\n // that cannot work. We can tell this by having ended up with a negative\n // duration. In that case, we need to adjust the computed timings. We do\n // this by setting the duration to 0 and then assigning the remainder of\n // the totalDuration to the endDelay\n if (timing.duration < 0) {\n timing.duration = 0;\n timing.endDelay = totalDuration - timing.delay;\n }\n // Set the timing on the native animation to the normalized values\n // while preserving the specified timing.\n nativeUpdateTiming.apply(effect, [timing]);\n }\n }\n details.normalizedTiming = timing;\n return details.specifiedTiming;\n }\n };\n const updateTimingHandler = {\n apply: function(target, thisArg, argumentsList) {\n if (!argumentsList || !argumentsList.length)\n return;\n\n // Additional validation that is specific to scroll timelines.\n if (details.timeline && argumentsList[0]) {\n const options = argumentsList[0];\n const duration = options.duration;\n if (duration === Infinity) {\n throw TypeError(\n \"Effect duration cannot be Infinity when used with Scroll \" +\n \"Timelines\");\n }\n const iterations = options.iterations;\n if (iterations === Infinity) {\n throw TypeError(\n \"Effect iterations cannot be Infinity when used with Scroll \" +\n \"Timelines\");\n }\n\n if (typeof duration !== 'undefined' && duration !== 'auto') {\n details.autoDurationEffect = null\n }\n }\n\n // Apply updates on top of the original specified timing.\n if (details.specifiedTiming) {\n target.apply(effect, [details.specifiedTiming]);\n }\n target.apply(effect, argumentsList);\n renormalizeTiming(details);\n }\n };\n const proxy = new Proxy(effect, handler);\n proxy.getComputedTiming = new Proxy(effect.getComputedTiming,\n getComputedTimingHandler);\n proxy.getTiming = new Proxy(effect.getTiming, getTimingHandler);\n proxy.updateTiming = new Proxy(effect.updateTiming, updateTimingHandler);\n return proxy;\n}\n\n// Computes the start delay as a fraction of the active cover range.\nfunction fractionalStartDelay(details) {\n if (!details.animationRange) return 0;\n const rangeStart = details.animationRange.start === 'normal' ?\n getNormalStartRange(details.timeline) :\n details.animationRange.start;\n return fractionalOffset(details.timeline, rangeStart);\n}\n\n// Computes the ends delay as a fraction of the active cover range.\nfunction fractionalEndDelay(details) {\n if (!details.animationRange) return 0;\n const rangeEnd = details.animationRange.end === 'normal' ?\n getNormalEndRange(details.timeline) :\n details.animationRange.end;\n return 1 - fractionalOffset(details.timeline, rangeEnd);\n}\n\n// Map from an instance of ProxyAnimation to internal details about that animation.\n// See ProxyAnimation constructor for details.\nlet proxyAnimations = new WeakMap();\n\n// Clear cache containing the ProxyAnimation instances when leaving the page.\n// See https://github.com/flackr/scroll-timeline/issues/146#issuecomment-1698159183\n// for details.\nwindow.addEventListener('pagehide', (e) => {\n proxyAnimations = new WeakMap();\n}, false);\n\n// Map from the real underlying native animation to the ProxyAnimation proxy of it.\nlet proxiedAnimations = new WeakMap();\n\n/**\n * Procedure for calculating an auto-aligned start time.\n * https://drafts.csswg.org/web-animations-2/#animation-calculating-an-auto-aligned-start-time\n * @param details\n */\nfunction autoAlignStartTime(details) {\n // When attached to a non-monotonic timeline, the start time of the animation may be layout dependent. In this case,\n // we defer calculation of the start time until the timeline has been updated post layout. When updating timeline\n // current time, the start time of any attached animation is conditionally updated. The procedure for calculating an\n // auto-aligned start time is as follows:\n\n // 1. If the auto-align start time flag is false, abort this procedure.\n if (!details.autoAlignStartTime) {\n return;\n }\n\n // 2. If the timeline is inactive, abort this procedure.\n if (!details.timeline || !details.timeline.currentTime) {\n return;\n }\n\n // 3. If play state is idle, abort this procedure.\n // 4. If play state is paused, and hold time is resolved, abort this procedure.\n if (details.proxy.playState === 'idle' ||\n (details.proxy.playState === 'paused' && details.holdTime !== null)) {\n return;\n }\n\n const previousRangeDuration = details.rangeDuration;\n\n let startOffset, endOffset;\n\n\n // 5. Let start offset be the resolved timeline time corresponding to the start of the animation attachment range.\n // In the case of view timelines, it requires a calculation based on the proportion of the cover range.\n try {\n startOffset = CSS.percent(fractionalStartDelay(details) * 100);\n } catch (e) {\n // TODO: Validate supported values for range start, to avoid exceptions when resolving the values.\n\n // Range start is invalid, falling back to default value\n startOffset = CSS.percent(0);\n details.animationRange.start = 'normal';\n console.warn(\"Exception when calculating start offset\", e);\n }\n\n // 6. Let end offset be the resolved timeline time corresponding to the end of the animation attachment range.\n // In the case of view timelines, it requires a calculation based on the proportion of the cover range.\n try {\n endOffset = CSS.percent((1 - fractionalEndDelay(details)) * 100);\n } catch (e) {\n // TODO: Validate supported values for range end, to avoid exceptions when resolving the values.\n\n // Range start is invalid, falling back to default value\n endOffset = CSS.percent(100);\n details.animationRange.end = 'normal';\n console.warn(\"Exception when calculating end offset\", e);\n }\n\n // Store the range duration, until we can find a spec aligned method to calculate iteration duration\n // TODO: Clarify how range duration should be resolved\n details.rangeDuration = endOffset.value - startOffset.value;\n // 7. Set start time to start offset if effective playback rate ≥ 0, and end offset otherwise.\n const playbackRate = effectivePlaybackRate(details);\n details.startTime = fromCssNumberish(details,playbackRate >= 0 ? startOffset : endOffset);\n\n // 8. Clear hold time.\n details.holdTime = null;\n\n // Additional polyfill step needed to renormalize timing when range has changed\n if (details.rangeDuration !== previousRangeDuration) {\n renormalizeTiming(details);\n }\n}\n\nfunction unsupportedTimeline(timeline) {\n throw new Error('Unsupported timeline class');\n}\n\nfunction getNormalStartRange(timeline) {\n if (timeline instanceof ViewTimeline) {\n return { rangeName: 'cover', offset: CSS.percent(0) };\n }\n\n if (timeline instanceof ScrollTimeline) {\n return CSS.percent(0);\n }\n\n unsupportedTimeline(timeline);\n}\n\nfunction getNormalEndRange(timeline) {\n if (timeline instanceof ViewTimeline) {\n return { rangeName: 'cover', offset: CSS.percent(100) };\n }\n\n if (timeline instanceof ScrollTimeline) {\n return CSS.percent(100);\n }\n\n unsupportedTimeline(timeline);\n}\n\nfunction parseAnimationRange(timeline, value) {\n if (!value)\n return {\n start: 'normal',\n end: 'normal',\n };\n\n const animationRange = {\n start: getNormalStartRange(timeline),\n end: getNormalEndRange(timeline),\n };\n\n if (timeline instanceof ViewTimeline) {\n // Format:\n // \n // --> 0% 100%\n // --> \n // \n // --> cover cover \n // TODO: Support all formatting options once ratified in the spec.\n const parts = splitIntoComponentValues(value);\n const rangeNames = [];\n const offsets = [];\n\n parts.forEach(part => {\n if (ANIMATION_RANGE_NAMES.includes(part)) {\n rangeNames.push(part);\n } else {\n try {\n offsets.push(CSSNumericValue.parse(part));\n } catch (e) {\n throw TypeError(`Could not parse range \"${value}\"`);\n }\n }\n });\n\n if (rangeNames.length > 2 || offsets.length > 2 || offsets.length == 1) {\n throw TypeError(\"Invalid time range or unsupported time range format.\");\n }\n\n if (rangeNames.length) {\n animationRange.start.rangeName = rangeNames[0];\n animationRange.end.rangeName = rangeNames.length > 1 ? rangeNames[1] : rangeNames[0];\n }\n\n if (offsets.length > 1) {\n animationRange.start.offset = offsets[0];\n animationRange.end.offset = offsets[1];\n }\n\n return animationRange;\n }\n\n if (timeline instanceof ScrollTimeline) {\n // @TODO: Play nice with only 1 offset being set\n // @TODO: Play nice with expressions such as `calc(50% + 10px) 100%`\n const parts = value.split(' ');\n if (parts.length != 2) {\n throw TypeError(\"Invalid time range or unsupported time range format.\");\n }\n\n animationRange.start = CSSNumericValue.parse(parts[0]);\n animationRange.end = CSSNumericValue.parse(parts[1]);\n\n return animationRange;\n }\n\n unsupportedTimeline(timeline);\n}\n\nfunction parseTimelineRangePart(timeline, value, position) {\n if (!value || value === 'normal') return 'normal';\n\n if (timeline instanceof ViewTimeline) {\n // Extract parts from the passed in value.\n let rangeName = 'cover'\n let offset = position === 'start' ? CSS.percent(0) : CSS.percent(100)\n\n // Author passed in something like `{ rangeName: 'cover', offset: CSS.percent(100) }`\n if (value instanceof Object) {\n if (value.rangeName !== undefined) {\n rangeName = value.rangeName;\n }\n\n if (value.offset !== undefined) {\n offset = value.offset;\n }\n }\n // Author passed in something like `\"cover 100%\"`\n else {\n const parts = splitIntoComponentValues(value);\n\n if (parts.length === 1) {\n if (ANIMATION_RANGE_NAMES.includes(parts[0])) {\n rangeName = parts[0];\n } else {\n offset = simplifyCalculation(CSSNumericValue.parse(parts[0]), {});\n }\n } else if (parts.length === 2) {\n rangeName = parts[0];\n offset = simplifyCalculation(CSSNumericValue.parse(parts[1]), {});\n }\n }\n\n // Validate rangeName\n if (!ANIMATION_RANGE_NAMES.includes(rangeName)) {\n throw TypeError(\"Invalid range name\");\n }\n\n return { rangeName, offset };\n }\n\n if (timeline instanceof ScrollTimeline) {\n // The value is a standalone offset, so simply parse it.\n return CSSNumericValue.parse(value);\n }\n\n unsupportedTimeline(timeline);\n}\n\n// Create an alternate Animation class which proxies API requests.\n// TODO: Create a full-fledged proxy so missing methods are automatically\n// fetched from Animation.\nexport class ProxyAnimation {\n constructor(effect, timeline, animOptions={}) {\n const isScrollAnimation = timeline instanceof ScrollTimeline;\n const animationTimeline = isScrollAnimation ? undefined : timeline;\n const animation =\n (effect instanceof nativeAnimation) ?\n effect : new nativeAnimation(effect, animationTimeline);\n proxiedAnimations.set(animation, this);\n proxyAnimations.set(this, {\n animation: animation,\n timeline: isScrollAnimation ? timeline : undefined,\n playState: isScrollAnimation ? \"idle\" : null,\n readyPromise: null,\n finishedPromise: null,\n // Start and hold times are directly tracked in the proxy despite being\n // accessible via the animation so that direct manipulation of these\n // properties does not affect the play state of the underlying animation.\n // Note that any changes to these values require an update of current\n // time for the underlying animation to ensure that its hold time is set\n // to the correct position. These values are represented as floating point\n // numbers in milliseconds.\n startTime: null,\n holdTime: null,\n rangeDuration: null,\n previousCurrentTime: null,\n autoAlignStartTime: false,\n // Calls to reverse and updatePlaybackRate set a pending rate that does\n // not immediately take effect. The value of this property is\n // inaccessible via the web animations API and therefore explicitly\n // tracked.\n pendingPlaybackRate: null,\n pendingTask: null,\n // Record the specified timing since it may be different than the timing\n // actually used for the animation. When fetching the timing, this value\n // will be returned, however, the native animation will use normalized\n // values.\n specifiedTiming: null,\n // The normalized timing has the corrected timing with the intrinsic\n // iteration duration resolved.\n normalizedTiming: null,\n // Effect proxy that performs the necessary time conversions when using a\n // progress-based timelines.\n effect: null,\n // The animation attachment range, restricting the animation’s\n // active interval to that range of a timeline\n animationRange: isScrollAnimation ? parseAnimationRange(timeline, animOptions['animation-range']) : null,\n proxy: this\n });\n }\n\n // -----------------------------------------\n // Web animation API\n // -----------------------------------------\n\n get effect() {\n const details = proxyAnimations.get(this);\n if (!details.timeline)\n return details.animation.effect;\n\n // Proxy the effect to support timing conversions for progress based\n // animations.\n if (!details.effect)\n details.effect = createProxyEffect(details);\n\n return details.effect;\n }\n set effect(newEffect) {\n const details = proxyAnimations.get(this);\n details.animation.effect = newEffect;\n // Reset proxy to force re-initialization the next time it is accessed.\n details.effect = null;\n details.autoDurationEffect = null;\n }\n\n get timeline() {\n const details = proxyAnimations.get(this);\n // If we explicitly set a null timeline we will return the underlying\n // animation's timeline.\n return details.timeline || details.animation.timeline;\n }\n set timeline(newTimeline) {\n // https://drafts4.csswg.org/web-animations-2/#setting-the-timeline\n const details = proxyAnimations.get(this);\n\n // 1. Let old timeline be the current timeline of animation, if any.\n // 2. If new timeline is the same object as old timeline, abort this\n // procedure.\n const oldTimeline = this.timeline;\n if (oldTimeline == newTimeline)\n return;\n\n // 3. Let previous play state be animation’s play state.\n const previousPlayState = this.playState;\n\n // 4. Let previous current time be the animation’s current time.\n const previousCurrentTime = this.currentTime;\n\n // 5. Set previous progress based in the first condition that applies:\n // If previous current time is unresolved:\n // Set previous progress to unresolved.\n // If endTime time is zero:\n // Set previous progress to zero.\n // Otherwise\n // Set previous progress = previous current time / endTime time\n let end = effectEnd(details);\n let previousProgress;\n if (previousCurrentTime === null) {\n previousProgress = null\n } else if (end === 0) {\n previousProgress = 0;\n } else {\n previousProgress = fromCssNumberish(details, previousCurrentTime) / end;\n }\n\n // 9. Let from finite timeline be true if old timeline is not null and not\n // monotonically increasing.\n const fromScrollTimeline = (oldTimeline instanceof ScrollTimeline);\n\n // 10. Let to finite timeline be true if timeline is not null and not\n // monotonically increasing.\n const toScrollTimeline = (newTimeline instanceof ScrollTimeline);\n\n // 11. Let the timeline of animation be new timeline.\n // Cannot assume that the native implementation has mutable timeline\n // support. Deferring this step until we know that we are either\n // polyfilling, supporting natively, or throwing an error.\n\n // Additional step required to track whether the animation was pending in\n // order to set up a new ready promise if needed.\n const pending = this.pending;\n\n if (fromScrollTimeline) {\n removeAnimation(details.timeline, details.animation);\n }\n\n // 12. Perform the steps corresponding to the first matching condition from\n // the following, if any:\n\n // If to finite timeline,\n if (toScrollTimeline) {\n // Deferred step 11.\n details.timeline = newTimeline;\n\n // 1. Apply any pending playback rate on animation\n applyPendingPlaybackRate(details);\n\n // 2. Set auto align start time to true.\n details.autoAlignStartTime = true;\n // 3. Set start time to unresolved.\n details.startTime = null;\n // 4. Set hold time to unresolved.\n details.holdTime = null;\n\n // 5. If previous play state is \"finished\" or \"running\"\n if (previousPlayState === 'running' || previousPlayState === 'finished') {\n // 1. Schedule a pending play task\n if (!details.readyPromise || details.readyPromise.state === 'resolved') {\n createReadyPromise(details);\n }\n details.pendingTask = 'play';\n // Additional polyfill step needed to associate the animation with\n // the scroll timeline.\n addAnimation(details.timeline, details.animation,\n tickAnimation.bind(this));\n }\n // 6. If previous play state is \"paused\" and previous progress is resolved:\n if (previousPlayState === 'paused' && previousProgress !== null) {\n // 1. Set hold time to previous progress * endTime time. This step ensures that previous progress is preserved\n // even in the case of a pause-pending animation with a resolved start time.\n details.holdTime = previousProgress * end;\n }\n\n // Additional steps required if the animation is pending as we need to\n // associate the pending promise with proxy animation.\n // Note: if the native promise already has an associated \"then\", we will\n // lose this association.\n if (pending) {\n if (!details.readyPromise ||\n details.readyPromise.state == 'resolved') {\n createReadyPromise(details);\n }\n if (previousPlayState == 'paused')\n details.pendingTask = 'pause';\n else\n details.pendingTask = 'play';\n }\n\n // Note that the following steps should apply when transitioning to\n // a monotonic timeline as well; however, we do not have a direct means\n // of applying the steps to the native animation.\n\n // 15. If the start time of animation is resolved, make animation’s hold\n // time unresolved. This step ensures that the finished play state of\n // animation is not “sticky” but is re-evaluated based on its updated\n // current time.\n if (details.startTime !== null)\n details.holdTime = null;\n\n // 16. Run the procedure to update an animation’s finished state for\n // animation with the did seek flag set to false, and the\n // synchronously notify flag set to false.\n updateFinishedState(details, false, false);\n return;\n }\n\n // To monotonic timeline.\n if (details.animation.timeline == newTimeline) {\n // Deferred step 11 from above. Clearing the proxy's timeline will\n // re-associate the proxy with the native animation.\n removeAnimation(details.timeline, details.animation);\n details.timeline = null;\n\n // If from finite timeline and previous current time is resolved,\n // Run the procedure to set the current time to previous current time.\n if (fromScrollTimeline) {\n if (previousCurrentTime !== null)\n details.animation.currentTime = previousProgress * effectEnd(details);\n\n switch (previousPlayState) {\n case 'paused':\n details.animation.pause();\n break;\n\n case 'running':\n case 'finished':\n details.animation.play();\n }\n }\n } else {\n throw TypeError(\"Unsupported timeline: \" + newTimeline);\n }\n }\n\n get startTime() {\n const details = proxyAnimations.get(this);\n if (details.timeline)\n return toCssNumberish(details, details.startTime);\n\n return details.animation.startTime;\n }\n set startTime(value) {\n // https://drafts.csswg.org/web-animations/#setting-the-start-time-of-an-animation\n const details = proxyAnimations.get(this);\n // 1. Let valid start time be the result of running the validate a CSSNumberish time procedure with new start time\n // as the input.\n // 2. If valid start time is false, abort this procedure.\n value = fromCssNumberish(details, value);\n if (!details.timeline) {\n details.animation.startTime = value;\n return;\n }\n\n // 3. Set auto align start time to false.\n details.autoAlignStartTime = false;\n\n // 4. Let timeline time be the current time value of the timeline that\n // animation is associated with. If there is no timeline associated with\n // animation or the associated timeline is inactive, let the timeline\n // time be unresolved.\n const timelineTime = fromCssNumberish(details,\n details.timeline.currentTime);\n\n // 5. If timeline time is unresolved and new start time is resolved, make\n // animation’s hold time unresolved.\n if (timelineTime == null && details.startTime != null) {\n details.holdTime = null;\n // Clearing the hold time may have altered the value of current time.\n // Ensure that the underlying animations has the correct value.\n syncCurrentTime(details);\n }\n\n // 6. Let previous current time be animation’s current time.\n // Note: This is the current time after applying the changes from the\n // previous step which may cause the current time to become unresolved.\n const previousCurrentTime = fromCssNumberish(details, this.currentTime);\n\n // 7. Apply any pending playback rate on animation.\n applyPendingPlaybackRate(details);\n\n // 8. Set animation’s start time to new start time.\n details.startTime = value;\n\n // 9. Update animation’s hold time based on the first matching condition\n // from the following,\n\n // If new start time is resolved,\n // If animation’s playback rate is not zero,\n // make animation’s hold time unresolved.\n\n // Otherwise (new start time is unresolved),\n // Set animation’s hold time to previous current time even if\n // previous current time is unresolved.\n\n if (details.startTime !== null && details.animation.playbackRate != 0)\n details.holdTime = null;\n else\n details.holdTime = previousCurrentTime;\n\n // 12. If animation has a pending play task or a pending pause task, cancel\n // that task and resolve animation’s current ready promise with\n // animation.\n if (details.pendingTask) {\n details.pendingTask = null;\n details.readyPromise.resolve(this);\n }\n\n // 13. Run the procedure to update an animation’s finished state for animation\n // with the did seek flag set to true, and the synchronously notify flag\n // set to false.\n updateFinishedState(details, true, false);\n\n // Ensure that currentTime is updated for the native animation.\n syncCurrentTime(details);\n }\n\n get currentTime() {\n const details = proxyAnimations.get(this);\n if (!details.timeline)\n return details.animation.currentTime;\n\n if (details.holdTime != null)\n return toCssNumberish(details, details.holdTime);\n\n return toCssNumberish(details, calculateCurrentTime(details));\n }\n set currentTime(value) {\n const details = proxyAnimations.get(this);\n if (!details.timeline) {\n details.animation.currentTime = value;\n return;\n }\n // https://drafts.csswg.org/web-animations-2/#setting-the-current-time-of-an-animation\n // 1. Run the steps to silently set the current time of animation to seek time.\n silentlySetTheCurrentTime(details, value);\n\n // 2. If animation has a pending pause task, synchronously complete the pause operation by performing the following steps:\n // 1. Set animation’s hold time to seek time.\n // 2. Apply any pending playback rate to animation.\n // 3. Make animation’s start time unresolved.\n // 4. Cancel the pending pause task.\n // 5. Resolve animation’s current ready promise with animation.\n if (details.pendingTask == 'pause') {\n details.holdTime = fromCssNumberish(details, value);\n applyPendingPlaybackRate(details);\n details.startTime = null;\n details.pendingTask = null;\n details.readyPromise.resolve(this);\n }\n\n // 3. Run the procedure to update an animation’s finished state for animation with the did seek flag set to true,\n // and the synchronously notify flag set to false.\n updateFinishedState(details, true, false);\n }\n\n get playbackRate() {\n return proxyAnimations.get(this).animation.playbackRate;\n }\n set playbackRate(value) {\n const details = proxyAnimations.get(this);\n\n if (!details.timeline) {\n details.animation.playbackRate = value;\n return;\n }\n\n // 1. Clear any pending playback rate on animation.\n details.pendingPlaybackRate = null;\n\n // 2. Let previous time be the value of the current time of animation before\n // changing the playback rate.\n const previousCurrentTime = this.currentTime;\n\n // 3. Set the playback rate to new playback rate.\n details.animation.playbackRate = value;\n\n // 4. If previous time is resolved, set the current time of animation to\n // previous time\n if (previousCurrentTime !== null)\n this.currentTime = previousCurrentTime;\n }\n\n get playState() {\n const details = proxyAnimations.get(this);\n if (!details.timeline)\n return details.animation.playState;\n\n const currentTime = fromCssNumberish(details, this.currentTime);\n\n // 1. All of the following conditions are true:\n // * The current time of animation is unresolved, and\n // * the start time of animation is unresolved, and\n // * animation does not have either a pending play task or a pending pause\n // task,\n // then idle.\n if (currentTime === null && details.startTime === null &&\n details.pendingTask == null)\n return 'idle';\n\n // 2. Either of the following conditions are true:\n // * animation has a pending pause task, or\n // * both the start time of animation is unresolved and it does not have a\n // pending play task,\n // then paused.\n if (details.pendingTask == 'pause' ||\n (details.startTime === null && details.pendingTask != 'play'))\n return 'paused';\n\n // 3. For animation, current time is resolved and either of the following\n // conditions are true:\n // * animation’s effective playback rate > 0 and current time >= target\n // effect end; or\n // * animation’s effective playback rate < 0 and current time <= 0,\n // then finished.\n if (currentTime != null) {\n if (details.animation.playbackRate > 0 &&\n currentTime >= effectEnd(details))\n return 'finished';\n if (details.animation.playbackRate < 0 && currentTime <= 0)\n return 'finished';\n }\n\n // 4. Otherwise\n return 'running';\n }\n\n get rangeStart() {\n return proxyAnimations.get(this).animationRange?.start ?? 'normal';\n }\n\n set rangeStart(value) {\n const details = proxyAnimations.get(this);\n if (!details.timeline) {\n return details.animation.rangeStart = value;\n }\n\n if (details.timeline instanceof ScrollTimeline) {\n const animationRange = details.animationRange;\n animationRange.start = parseTimelineRangePart(details.timeline, value, 'start');\n\n // Additional polyfill step to ensure that the native animation has the\n // correct value for current time.\n autoAlignStartTime(details);\n syncCurrentTime(details);\n }\n }\n\n get rangeEnd() {\n return proxyAnimations.get(this).animationRange?.end ?? 'normal';\n }\n\n set rangeEnd(value) {\n const details = proxyAnimations.get(this);\n if (!details.timeline) {\n return details.animation.rangeEnd = value;\n }\n\n if (details.timeline instanceof ScrollTimeline) {\n const animationRange = details.animationRange;\n animationRange.end = parseTimelineRangePart(details.timeline, value, 'end');\n\n // Additional polyfill step to ensure that the native animation has the\n // correct value for current time.\n autoAlignStartTime(details);\n syncCurrentTime(details);\n }\n }\n\n get replaceState() {\n // TODO: Fix me. Replace state is not a boolean.\n return proxyAnimations.get(this).animation.pending;\n }\n\n get pending() {\n const details = proxyAnimations.get(this);\n if (details.timeline) {\n return !!details.readyPromise &&\n details.readyPromise.state == 'pending';\n }\n\n return details.animation.pending;\n }\n\n finish() {\n const details = proxyAnimations.get(this);\n if (!details.timeline) {\n details.animation.finish();\n return;\n }\n\n // 1. If animation’s effective playback rate is zero, or if animation’s\n // effective playback rate > 0 and target effect end is infinity, throw\n // an InvalidStateError and abort these steps.\n const playbackRate = effectivePlaybackRate(details);\n const duration = effectEnd(details);\n if (playbackRate == 0) {\n throw new DOMException(\n \"Cannot finish Animation with a playbackRate of 0.\",\n \"InvalidStateError\");\n }\n if (playbackRate > 0 && duration == Infinity) {\n throw new DOMException(\n \"Cannot finish Animation with an infinite target effect end.\",\n \"InvalidStateError\");\n }\n\n // 2. Apply any pending playback rate to animation.\n applyPendingPlaybackRate(details);\n\n // 3. Set limit as follows:\n // If playback rate > 0,\n // Let limit be target effect end.\n // Otherwise,\n // Let limit be zero.\n const limit = playbackRate < 0 ? 0 : duration;\n\n // 4. Silently set the current time to limit.\n this.currentTime = toCssNumberish(details, limit);\n\n // 5. If animation’s start time is unresolved and animation has an\n // associated active timeline, let the start time be the result of\n // evaluating\n // timeline time - (limit / playback rate)\n // where timeline time is the current time value of the associated\n // timeline.\n const timelineTime = fromCssNumberish(details,\n details.timeline.currentTime);\n\n if (details.startTime === null && timelineTime !== null) {\n details.startTime =\n timelineTime - (limit / details.animation.playbackRate);\n }\n\n // 6. If there is a pending pause task and start time is resolved,\n // 6.1 Let the hold time be unresolved.\n // 6.2 Cancel the pending pause task.\n // 6.3 Resolve the current ready promise of animation with animation.\n if (details.pendingTask == 'pause' && details.startTime !== null) {\n details.holdTime = null;\n details.pendingTask = null;\n details.readyPromise.resolve(this);\n }\n\n // 7. If there is a pending play task and start time is resolved, cancel\n // that task and resolve the current ready promise of animation with\n // animation.\n if (details.pendingTask == 'play' && details.startTime !== null) {\n details.pendingTask = null;\n details.readyPromise.resolve(this);\n }\n\n // 8. Run the procedure to update an animation’s finished state for\n // animation with the did seek flag set to true, and the synchronously\n // notify flag set to true.\n updateFinishedState(details, true, true);\n }\n\n play() {\n const details = proxyAnimations.get(this);\n if (!details.timeline) {\n details.animation.play();\n return;\n }\n\n playInternal(details, /* autoRewind */ true);\n }\n\n pause() {\n const details = proxyAnimations.get(this);\n if (!details.timeline) {\n details.animation.pause();\n return;\n }\n\n // https://www.w3.org/TR/web-animations-1/#pausing-an-animation-section\n // and https://drafts.csswg.org/web-animations-2/#pausing-an-animation-section\n\n // 1. If animation has a pending pause task, abort these steps.\n // 2. If the play state of animation is paused, abort these steps.\n if (this.playState == \"paused\")\n return;\n\n // Replaced steps from https://drafts.csswg.org/web-animations-2/#pausing-an-animation-section\n //\n // 3. Let has finite timeline be true if animation has an associated timeline that is not monotonically increasing.\n // Note: always true if we have reached this point in the polyfill.\n // Pruning following steps to be specific to scroll timelines.\n // 4. If the animation’s current time is unresolved and has finite timeline is false, perform the steps according\n // to the first matching condition below:\n //\n // 4a If animation’s playback rate is ≥ 0,\n // Set hold time to zero.\n // 4b Otherwise,\n // 4b1 If associated effect end for animation is positive infinity,\n // throw an \"InvalidStateError\" DOMException and abort these steps.\n // 4b2 Otherwise,\n // Set hold time to animation’s associated effect end.\n // If has finite timeline is true, and the animation’s current time is unresolved\n // Set the auto align start time flag to true.\n if (details.animation.currentTime === null) {\n details.autoAlignStartTime = true;\n }\n\n // 7. Let has pending ready promise be a boolean flag that is initially\n // false.\n // 8. If animation has a pending play task, cancel that task and let has\n // pending ready promise be true.\n // 9. If has pending ready promise is false, set animation’s current ready\n // promise to a new promise in the relevant Realm of animation.\n if (details.pendingTask == 'play')\n details.pendingTask = null;\n else\n details.readyPromise = null;\n\n // 10. Schedule a task to be executed at the first possible moment where all of the following conditions are true:\n //\n // the user agent has performed any processing necessary to suspend the playback of animation’s associated\n // effect, if any.\n // the animation is associated with a timeline that is not inactive.\n // the animation has a resolved hold time or start time.\n if (!details.readyPromise)\n createReadyPromise(details);\n details.pendingTask ='pause';\n\n // Additional step for the polyfill.\n // This must run after setting up the ready promise, otherwise we will run\n // the procedure for calculating auto aligned start time before play state is running\n addAnimation(details.timeline, details.animation, tickAnimation.bind(details.proxy));\n }\n\n reverse() {\n const details = proxyAnimations.get(this);\n const playbackRate = effectivePlaybackRate(details);\n const previousCurrentTime = fromCssNumberish(details, this.currentTime);\n const inifiniteDuration = effectEnd(details) == Infinity;\n\n // Let the native implementation handle throwing the exception in cases\n // where reversal is not possible. Error cases will not change the state\n // of the native animation.\n const reversable =\n (playbackRate != 0) &&\n (playbackRate < 0 || previousCurrentTime > 0 || !inifiniteDuration);\n if (!details.timeline || !reversable) {\n if (reversable)\n details.pendingPlaybackRate = -effectivePlaybackRate(details);\n details.animation.reverse();\n return;\n }\n\n if (details.timeline.phase == 'inactive') {\n throw new DOMException(\n \"Cannot reverse an animation with no active timeline\",\n \"InvalidStateError\");\n }\n\n this.updatePlaybackRate(-playbackRate);\n playInternal(details, /* autoRewind */ true);\n }\n\n updatePlaybackRate(rate) {\n const details = proxyAnimations.get(this);\n details.pendingPlaybackRate = rate;\n if (!details.timeline) {\n details.animation.updatePlaybackRate(rate);\n return;\n }\n\n // https://drafts.csswg.org/web-animations/#setting-the-playback-rate-of-an-animation\n\n // 1. Let previous play state be animation’s play state.\n // 2. Let animation’s pending playback rate be new playback rate.\n // Step 2 already performed as we need to record it even when using a\n // monotonic timeline.\n const previousPlayState = this.playState;\n\n // 3. Perform the steps corresponding to the first matching condition from\n // below:\n //\n // 3a If animation has a pending play task or a pending pause task,\n // Abort these steps.\n if (details.readyPromise && details.readyPromise.state == 'pending')\n return;\n\n switch(previousPlayState) {\n // 3b If previous play state is idle or paused,\n // Apply any pending playback rate on animation.\n case 'idle':\n case 'paused':\n applyPendingPlaybackRate(details);\n break;\n\n // 3c If previous play state is finished,\n // 3c.1 Let the unconstrained current time be the result of calculating\n // the current time of animation substituting an unresolved time\n // value for the hold time.\n // 3c.2 Let animation’s start time be the result of evaluating the\n // following expression:\n // timeline time - (unconstrained current time / pending playback rate)\n // Where timeline time is the current time value of the timeline\n // associated with animation.\n // 3c.3 If pending playback rate is zero, let animation’s start time be\n // timeline time.\n // 3c.4 Apply any pending playback rate on animation.\n // 3c.5 Run the procedure to update an animation’s finished state for\n // animation with the did seek flag set to false, and the\n // synchronously notify flag set to false.\n\n case 'finished':\n const timelineTime = fromCssNumberish(details,\n details.timeline.currentTime);\n const unconstrainedCurrentTime = timelineTime !== null ?\n (timelineTime - details.startTime) * details.animation.playbackRate\n : null;\n if (rate == 0) {\n details.startTime = timelineTime;\n } else {\n details.startTime =\n timelineTime != null && unconstrainedCurrentTime != null ?\n (timelineTime - unconstrainedCurrentTime) / rate : null;\n }\n applyPendingPlaybackRate(details);\n updateFinishedState(details, false, false);\n syncCurrentTime(details);\n break;\n\n // 3d Otherwise,\n // Run the procedure to play an animation for animation with the\n // auto-rewind flag set to false.\n default:\n playInternal(details, false);\n }\n }\n\n persist() {\n proxyAnimations.get(this).animation.persist();\n }\n\n get id() {\n return proxyAnimations.get(this).animation.id;\n }\n \n set id(value) {\n proxyAnimations.get(this).animation.id = value;\n }\n\n cancel() {\n const details = proxyAnimations.get(this);\n if (!details.timeline) {\n details.animation.cancel();\n return;\n }\n\n // https://www.w3.org/TR/web-animations-1/#canceling-an-animation-section\n // 1. If animation’s play state is not idle, perform the following steps:\n // 1.1 Run the procedure to reset an animation’s pending tasks on\n // animation.\n // 1.2 Reject the current finished promise with a DOMException named\n // \"AbortError\"\n // 1.3 Let current finished promise be a new (pending) Promise object.\n // 1.4+ Deferred to native implementation.\n // TODO: polyfill since timelineTime will be incorrect for the\n // cancel event. Also, should avoid sending a cancel event if\n // the native animation is canceled due to the scroll timeline\n // becoming inactive. This can likely be done by associating\n // the cancel event with the proxy and not the underlying\n // animation.\n if (this.playState != 'idle') {\n resetPendingTasks(details);\n if (details.finishedPromise &&\n details.finishedPromise.state == 'pending') {\n details.finishedPromise.reject(createAbortError());\n }\n details.finishedPromise = new PromiseWrapper();\n details.animation.cancel();\n }\n\n // 2. Make animation’s hold time unresolved.\n // 3. Make animation’s start time unresolved.\n details.startTime = null;\n details.holdTime = null;\n\n // Extra step in the polyfill the ensure the animation stops ticking.\n removeAnimation(details.timeline, details.animation);\n }\n\n get onfinish() {\n return proxyAnimations.get(this).animation.onfinish;\n }\n set onfinish(value) {\n proxyAnimations.get(this).animation.onfinish = value;\n }\n get oncancel() {\n return proxyAnimations.get(this).animation.oncancel;\n }\n set oncancel(value) {\n proxyAnimations.get(this).animation.oncancel = value;\n }\n get onremove() {\n return proxyAnimations.get(this).animation.onremove;\n }\n set onremove(value) {\n proxyAnimations.get(this).animation.onremove = value;\n }\n\n get finished() {\n const details = proxyAnimations.get(this);\n if (!details.timeline)\n return details.animation.finished;\n\n if (!details.finishedPromise) {\n details.finishedPromise = new PromiseWrapper();\n }\n return details.finishedPromise.promise;\n }\n\n get ready() {\n const details = proxyAnimations.get(this);\n if (!details.timeline)\n return details.animation.ready;\n\n if (!details.readyPromise) {\n details.readyPromise = new PromiseWrapper();\n details.readyPromise.resolve(this);\n }\n return details.readyPromise.promise;\n }\n\n // --------------------------------------------------\n // Event target API\n // --------------------------------------------------\n\n addEventListener(type, callback, options) {\n proxyAnimations.get(this).animation.addEventListener(type, callback,\n options);\n }\n\n removeEventListener(type, callback, options) {\n proxyAnimations.get(this).animation.removeEventListener(type, callback,\n options);\n }\n\n dispatchEvent(event) {\n proxyAnimations.get(this).animation.dispatchEvent(event);\n }\n};\n\nexport function animate(keyframes, options) {\n const timeline = options.timeline;\n\n if (timeline instanceof ScrollTimeline)\n delete options.timeline;\n\n const animation = nativeElementAnimate.apply(this, [keyframes, options]);\n const proxyAnimation = new ProxyAnimation(animation, timeline);\n\n if (timeline instanceof ScrollTimeline) {\n animation.pause();\n\n const details = proxyAnimations.get(proxyAnimation);\n details.animationRange = {\n start: parseTimelineRangePart(timeline, options.rangeStart, 'start'),\n end: parseTimelineRangePart(timeline, options.rangeEnd, 'end'),\n };\n\n proxyAnimation.play();\n }\n\n return proxyAnimation;\n}\n\nfunction replaceProxiedAnimations(animationsList) {\n for (let i = 0; i < animationsList.length; ++i) {\n let proxyAnimation = proxiedAnimations.get(animationsList[i]);\n if (proxyAnimation) {\n animationsList[i] = proxyAnimation;\n }\n }\n return animationsList;\n}\n\nexport function elementGetAnimations(options) {\n let animations = nativeElementGetAnimations.apply(this, [options]);\n return replaceProxiedAnimations(animations);\n}\n\nexport function documentGetAnimations(options) {\n let animations = nativeDocumentGetAnimations.apply(this, [options]);\n return replaceProxiedAnimations(animations);\n}\n","import { ANIMATION_RANGE_NAMES, getAnonymousSourceElement } from './scroll-timeline-base';\n\n// This is also used in scroll-timeline-css.js\nexport const RegexMatcher = {\n IDENTIFIER: /[\\w\\\\\\@_-]+/g,\n WHITE_SPACE: /\\s*/g,\n NUMBER: /^[0-9]+/,\n TIME: /^[0-9]+(s|ms)/,\n SCROLL_TIMELINE: /scroll-timeline\\s*:([^;}]+)/,\n SCROLL_TIMELINE_NAME: /scroll-timeline-name\\s*:([^;}]+)/,\n SCROLL_TIMELINE_AXIS: /scroll-timeline-axis\\s*:([^;}]+)/,\n VIEW_TIMELINE: /view-timeline\\s*:([^;}]+)/,\n VIEW_TIMELINE_NAME: /view-timeline-name\\s*:([^;}]+)/,\n VIEW_TIMELINE_AXIS: /view-timeline-axis\\s*:([^;}]+)/,\n VIEW_TIMELINE_INSET: /view-timeline-inset\\s*:([^;}]+)/,\n ANIMATION_TIMELINE: /animation-timeline\\s*:([^;}]+)/,\n ANIMATION_TIME_RANGE: /animation-range\\s*:([^;}]+)/,\n ANIMATION_NAME: /animation-name\\s*:([^;}]+)/,\n ANIMATION: /animation\\s*:([^;}]+)/,\n ANONYMOUS_SCROLL_TIMELINE: /scroll\\(([^)]*)\\)/,\n ANONYMOUS_VIEW_TIMELINE: /view\\(([^)]*)\\)/,\n};\n\n// Used for ANIMATION_TIMELINE, ANIMATION_NAME and ANIMATION regex\nconst VALUES_CAPTURE_INDEX = 1;\n\nconst WHOLE_MATCH_INDEX = 0;\n\nconst ANIMATION_KEYWORDS = [\n 'normal', 'reverse', 'alternate', 'alternate-reverse',\n 'none', 'forwards', 'backwards', 'both',\n 'running', 'paused',\n 'ease', 'linear', 'ease-in', 'ease-out', 'ease-in-out'\n];\n\nconst TIMELINE_AXIS_TYPES = ['block', 'inline', 'x', 'y'];\nconst ANONYMOUS_TIMELINE_SOURCE_TYPES = ['nearest', 'root', 'self'];\n\n// Parse a styleSheet to extract the relevant elements needed for\n// scroll-driven animations.\n// we will save objects in a list named cssRulesWithTimelineName\nexport class StyleParser {\n constructor() {\n this.cssRulesWithTimelineName = [];\n this.nextAnonymousTimelineNameIndex = 0;\n this.anonymousScrollTimelineOptions = new Map(); // save anonymous options by name\n this.anonymousViewTimelineOptions = new Map(); // save anonymous options by name\n this.sourceSelectorToScrollTimeline = [];\n this.subjectSelectorToViewTimeline = [];\n this.keyframeNamesSelectors = new Map();\n }\n\n // Inspired by\n // https://drafts.csswg.org/css-syntax/#parser-diagrams\n // https://github.com/GoogleChromeLabs/container-query-polyfill/blob/main/src/engine.ts\n // This function is called twice, in the first pass we are interested in saving\n // the @keyframe names, in the second pass we will parse other rules to extract\n // scroll-animations related properties and values.\n transpileStyleSheet(sheetSrc, firstPass, srcUrl) {\n // AdhocParser\n const p = {\n sheetSrc: sheetSrc,\n index: 0,\n name: srcUrl,\n };\n\n while (p.index < p.sheetSrc.length) {\n this.eatWhitespace(p);\n if (p.index >= p.sheetSrc.length) break;\n if (this.lookAhead(\"/*\", p)) {\n while (this.lookAhead(\"/*\", p)) {\n this.eatComment(p);\n this.eatWhitespace(p);\n }\n continue;\n }\n\n const rule = this.parseQualifiedRule(p);\n if (!rule) continue;\n if (firstPass)\n this.parseKeyframesAndSaveNameMapping(rule, p);\n else\n this.handleScrollTimelineProps(rule, p);\n }\n\n return this.replaceUrlFunctions(p.sheetSrc, srcUrl);\n }\n\n\n // If this sheet has no srcURL (like from a