248 lines
6.8 KiB
JavaScript
248 lines
6.8 KiB
JavaScript
//.CommonJS
|
|
var CSSOM = {
|
|
CSSRule: require("./CSSRule").CSSRule,
|
|
CSSRuleList: require("./CSSRuleList").CSSRuleList,
|
|
parse: require("./parse").parse
|
|
};
|
|
var errorUtils = require("./errorUtils").errorUtils;
|
|
///CommonJS
|
|
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://www.w3.org/TR/css3-animations/#DOM-CSSKeyframesRule
|
|
*/
|
|
CSSOM.CSSKeyframesRule = function CSSKeyframesRule() {
|
|
CSSOM.CSSRule.call(this);
|
|
this.name = '';
|
|
this.cssRules = new CSSOM.CSSRuleList();
|
|
|
|
// Set up initial indexed access
|
|
this._setupIndexedAccess();
|
|
|
|
// Override cssRules methods after initial setup, store references as non-enumerable properties
|
|
var self = this;
|
|
var originalPush = this.cssRules.push;
|
|
var originalSplice = this.cssRules.splice;
|
|
|
|
// Create non-enumerable method overrides
|
|
Object.defineProperty(this.cssRules, 'push', {
|
|
value: function() {
|
|
var result = originalPush.apply(this, arguments);
|
|
self._setupIndexedAccess();
|
|
return result;
|
|
},
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
|
|
Object.defineProperty(this.cssRules, 'splice', {
|
|
value: function() {
|
|
var result = originalSplice.apply(this, arguments);
|
|
self._setupIndexedAccess();
|
|
return result;
|
|
},
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
};
|
|
|
|
CSSOM.CSSKeyframesRule.prototype = Object.create(CSSOM.CSSRule.prototype);
|
|
CSSOM.CSSKeyframesRule.prototype.constructor = CSSOM.CSSKeyframesRule;
|
|
|
|
Object.setPrototypeOf(CSSOM.CSSKeyframesRule, CSSOM.CSSRule);
|
|
|
|
Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "type", {
|
|
value: 7,
|
|
writable: false
|
|
});
|
|
|
|
// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframesRule.cpp
|
|
Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "cssText", {
|
|
get: function() {
|
|
var values = "";
|
|
var valuesArr = [" {"];
|
|
if (this.cssRules.length) {
|
|
valuesArr.push(this.cssRules.reduce(function(acc, rule){
|
|
if (rule.cssText !== "") {
|
|
acc.push(rule.cssText);
|
|
}
|
|
return acc;
|
|
}, []).join("\n "));
|
|
}
|
|
values = valuesArr.join("\n ") + "\n}";
|
|
var cssWideKeywords = ['initial', 'inherit', 'revert', 'revert-layer', 'unset', 'none'];
|
|
var processedName = cssWideKeywords.includes(this.name) ? '"' + this.name + '"' : this.name;
|
|
return "@" + (this._vendorPrefix || '') + "keyframes " + processedName + values;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Appends a new keyframe rule to the list of keyframes.
|
|
*
|
|
* @param {string} rule - The keyframe rule string to append (e.g., "50% { opacity: 0.5; }")
|
|
* @see https://www.w3.org/TR/css-animations-1/#dom-csskeyframesrule-appendrule
|
|
*/
|
|
CSSOM.CSSKeyframesRule.prototype.appendRule = function appendRule(rule) {
|
|
if (arguments.length === 0) {
|
|
errorUtils.throwMissingArguments(this, 'appendRule', 'CSSKeyframesRule');
|
|
}
|
|
|
|
var parsedRule;
|
|
try {
|
|
// Parse the rule string as a keyframe rule
|
|
var tempStyleSheet = CSSOM.parse("@keyframes temp { " + rule + " }");
|
|
if (tempStyleSheet.cssRules.length > 0 && tempStyleSheet.cssRules[0].cssRules.length > 0) {
|
|
parsedRule = tempStyleSheet.cssRules[0].cssRules[0];
|
|
} else {
|
|
throw new Error("Failed to parse keyframe rule");
|
|
}
|
|
} catch (e) {
|
|
errorUtils.throwParseError(this, 'appendRule', 'CSSKeyframesRule', rule);
|
|
}
|
|
|
|
parsedRule.__parentRule = this;
|
|
this.cssRules.push(parsedRule);
|
|
};
|
|
|
|
/**
|
|
* Deletes a keyframe rule that matches the specified key.
|
|
*
|
|
* @param {string} select - The keyframe selector to delete (e.g., "50%", "from", "to")
|
|
* @see https://www.w3.org/TR/css-animations-1/#dom-csskeyframesrule-deleterule
|
|
*/
|
|
CSSOM.CSSKeyframesRule.prototype.deleteRule = function deleteRule(select) {
|
|
if (arguments.length === 0) {
|
|
errorUtils.throwMissingArguments(this, 'deleteRule', 'CSSKeyframesRule');
|
|
}
|
|
|
|
var normalizedSelect = this._normalizeKeyText(select);
|
|
|
|
for (var i = 0; i < this.cssRules.length; i++) {
|
|
var rule = this.cssRules[i];
|
|
if (this._normalizeKeyText(rule.keyText) === normalizedSelect) {
|
|
rule.__parentRule = null;
|
|
this.cssRules.splice(i, 1);
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Finds and returns the keyframe rule that matches the specified key.
|
|
* When multiple rules have the same key, returns the last one.
|
|
*
|
|
* @param {string} select - The keyframe selector to find (e.g., "50%", "from", "to")
|
|
* @return {CSSKeyframeRule|null} The matching keyframe rule, or null if not found
|
|
* @see https://www.w3.org/TR/css-animations-1/#dom-csskeyframesrule-findrule
|
|
*/
|
|
CSSOM.CSSKeyframesRule.prototype.findRule = function findRule(select) {
|
|
if (arguments.length === 0) {
|
|
errorUtils.throwMissingArguments(this, 'findRule', 'CSSKeyframesRule');
|
|
}
|
|
|
|
var normalizedSelect = this._normalizeKeyText(select);
|
|
|
|
// Iterate backwards to find the last matching rule
|
|
for (var i = this.cssRules.length - 1; i >= 0; i--) {
|
|
var rule = this.cssRules[i];
|
|
if (this._normalizeKeyText(rule.keyText) === normalizedSelect) {
|
|
return rule;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Normalizes keyframe selector text for comparison.
|
|
* Handles "from" -> "0%" and "to" -> "100%" conversions and trims whitespace.
|
|
*
|
|
* @private
|
|
* @param {string} keyText - The keyframe selector text to normalize
|
|
* @return {string} The normalized keyframe selector text
|
|
*/
|
|
CSSOM.CSSKeyframesRule.prototype._normalizeKeyText = function _normalizeKeyText(keyText) {
|
|
if (!keyText) return '';
|
|
|
|
var normalized = keyText.toString().trim().toLowerCase();
|
|
|
|
// Convert keywords to percentages for comparison
|
|
if (normalized === 'from') {
|
|
return '0%';
|
|
} else if (normalized === 'to') {
|
|
return '100%';
|
|
}
|
|
|
|
return normalized;
|
|
};
|
|
|
|
/**
|
|
* Makes CSSKeyframesRule iterable over its cssRules.
|
|
* Allows for...of loops and other iterable methods.
|
|
*/
|
|
if (typeof Symbol !== 'undefined' && Symbol.iterator) {
|
|
CSSOM.CSSKeyframesRule.prototype[Symbol.iterator] = function() {
|
|
var index = 0;
|
|
var cssRules = this.cssRules;
|
|
|
|
return {
|
|
next: function() {
|
|
if (index < cssRules.length) {
|
|
return { value: cssRules[index++], done: false };
|
|
} else {
|
|
return { done: true };
|
|
}
|
|
}
|
|
};
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Adds indexed getters for direct access to cssRules by index.
|
|
* This enables rule[0], rule[1], etc. access patterns.
|
|
* Works in environments where Proxy is not available (like jsdom).
|
|
*/
|
|
CSSOM.CSSKeyframesRule.prototype._setupIndexedAccess = function() {
|
|
// Remove any existing indexed properties
|
|
for (var i = 0; i < 1000; i++) { // reasonable upper limit
|
|
if (this.hasOwnProperty(i)) {
|
|
delete this[i];
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Add indexed getters for current cssRules
|
|
for (var i = 0; i < this.cssRules.length; i++) {
|
|
(function(index) {
|
|
Object.defineProperty(this, index, {
|
|
get: function() {
|
|
return this.cssRules[index];
|
|
},
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}.call(this, i));
|
|
}
|
|
|
|
// Update length property
|
|
Object.defineProperty(this, 'length', {
|
|
get: function() {
|
|
return this.cssRules.length;
|
|
},
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//.CommonJS
|
|
exports.CSSKeyframesRule = CSSOM.CSSKeyframesRule;
|
|
///CommonJS
|