specificity
Advanced tools
| "use strict"; | ||
| var __assign = (this && this.__assign) || function () { | ||
| __assign = Object.assign || function(t) { | ||
| for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
| s = arguments[i]; | ||
| for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
| t[p] = s[p]; | ||
| } | ||
| return t; | ||
| }; | ||
| return __assign.apply(this, arguments); | ||
| }; | ||
| var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { | ||
| if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { | ||
| if (ar || !(i in from)) { | ||
| if (!ar) ar = Array.prototype.slice.call(from, 0, i); | ||
| ar[i] = from[i]; | ||
| } | ||
| } | ||
| return to.concat(ar || Array.prototype.slice.call(from)); | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.calculateWithDetails = exports.calculate = void 0; | ||
| var css_tree_1 = require("css-tree"); | ||
| var sort_js_1 = require("./sort.js"); | ||
| var universalSelectorName = "*"; | ||
| var increment = function (node, _a, level) { | ||
| var _b; | ||
| var _c, _d, _e, _f, _g, _h, _j, _k; | ||
| var total = _a.total, contributingParts = _a.contributingParts; | ||
| return ({ | ||
| total: __assign(__assign({}, total), (_b = {}, _b[level] = total[level] + 1, _b)), | ||
| contributingParts: __spreadArray(__spreadArray([], contributingParts, true), [ | ||
| { | ||
| level: level, | ||
| start: { | ||
| line: (_d = (_c = node === null || node === void 0 ? void 0 : node.loc) === null || _c === void 0 ? void 0 : _c.start.line) !== null && _d !== void 0 ? _d : 1, | ||
| column: (_f = (_e = node === null || node === void 0 ? void 0 : node.loc) === null || _e === void 0 ? void 0 : _e.start.column) !== null && _f !== void 0 ? _f : 1, | ||
| }, | ||
| end: { | ||
| line: (_h = (_g = node === null || node === void 0 ? void 0 : node.loc) === null || _g === void 0 ? void 0 : _g.end.line) !== null && _h !== void 0 ? _h : 1, | ||
| column: (_k = (_j = node === null || node === void 0 ? void 0 : node.loc) === null || _j === void 0 ? void 0 : _j.end.column) !== null && _k !== void 0 ? _k : 1, | ||
| }, | ||
| }, | ||
| ], false), | ||
| }); | ||
| }; | ||
| var psuedoElementsWithDeprecatedOneColonNotation = [ | ||
| "before", | ||
| "after", | ||
| "first-line", | ||
| "first-letter", | ||
| ]; | ||
| var getChildrenFromPseudoClassNode = function (node) { | ||
| if (node.children) { | ||
| var firstChild = node.children[0]; | ||
| if (firstChild.type === "SelectorList") { | ||
| return firstChild.children; | ||
| } | ||
| else if (firstChild.type === "Nth" && firstChild.selector) { | ||
| return firstChild.selector.children; | ||
| } | ||
| else { | ||
| return node.children; | ||
| } | ||
| } | ||
| return []; | ||
| }; | ||
| var handlePseudoClassSelector = function (node, accumulatingResult) { | ||
| var name = node.name.toLowerCase(); | ||
| var children = getChildrenFromPseudoClassNode(node); | ||
| if (name === "not" || name === "is" || name === "has") { | ||
| // The specificity of an :is(), :not(), or :has() pseudo-class is replaced by the specificity of the most specific complex selector in its selector list argument | ||
| if (children.length > 0) { | ||
| return children | ||
| .map(function (childNode) { return traverse(childNode, accumulatingResult); }) | ||
| .sort(function (a, b) { return (0, sort_js_1.compareDesc)(a.total, b.total); })[0]; | ||
| } | ||
| } | ||
| else if (name === "nth-child" || name === "nth-last-child") { | ||
| // The specificity of an :nth-child() or :nth-last-child() selector is the specificity of the pseudo class itself (counting as one pseudo-class selector) plus the specificity of the most specific complex selector in its selector list argument (if any). | ||
| if (children.length > 0) { | ||
| var highestChildSpecificity = children | ||
| .map(function (childNode) { return traverse(childNode, increment(node, accumulatingResult, "B")); }) | ||
| .sort(function (a, b) { return (0, sort_js_1.compareDesc)(a.total, b.total); })[0]; | ||
| return highestChildSpecificity; | ||
| } | ||
| else { | ||
| return increment(node, accumulatingResult, "B"); | ||
| } | ||
| } | ||
| else if (name === "where") { | ||
| // The specificity of a :where() pseudo-class is replaced by zero. | ||
| return accumulatingResult; | ||
| } | ||
| else if (name === "global" || name === "local") { | ||
| // The specificity for :global() and :local() is replaced by the specificity of the child selector because although they look like psuedo classes they are actually an identifier for CSS Modules | ||
| if (children.length > 0) { | ||
| return children.reduce(function (acc, childNode) { return traverse(childNode, acc); }, accumulatingResult); | ||
| } | ||
| } | ||
| else if (psuedoElementsWithDeprecatedOneColonNotation.includes(name)) { | ||
| // These pseudo-elements can look like pseudo-classes | ||
| // https://www.w3.org/TR/selectors-4/#pseudo-elements | ||
| return increment(node, accumulatingResult, "C"); | ||
| } | ||
| else { | ||
| return increment(node, accumulatingResult, "B"); | ||
| } | ||
| return accumulatingResult; | ||
| }; | ||
| var traverse = function (node, accumulatingResult) { | ||
| var _a, _b, _c, _d; | ||
| if (accumulatingResult === void 0) { accumulatingResult = { | ||
| total: { A: 0, B: 0, C: 0 }, | ||
| contributingParts: [], | ||
| }; } | ||
| if (node.type === "IdSelector") { | ||
| return increment(node, accumulatingResult, "A"); | ||
| } | ||
| else if (node.type === "PseudoClassSelector") { | ||
| return handlePseudoClassSelector(node, accumulatingResult); | ||
| } | ||
| else if (node.type === "ClassSelector" || | ||
| node.type === "AttributeSelector") { | ||
| return increment(node, accumulatingResult, "B"); | ||
| } | ||
| else if (node.type === "TypeSelector") { | ||
| if (node.name !== universalSelectorName) { | ||
| return increment(node, accumulatingResult, "C"); | ||
| } | ||
| } | ||
| else if (node.type === "PseudoElementSelector") { | ||
| return increment(node, accumulatingResult, "C"); | ||
| } | ||
| else if (node.type === "Selector" || node.type === "SelectorList") { | ||
| return node.children.reduce(function (acc, childNode) { return traverse(childNode, acc); }, accumulatingResult); | ||
| } | ||
| else if (node.type === "Raw") { | ||
| return traverse((0, css_tree_1.toPlainObject)((0, css_tree_1.parse)(node.value, { | ||
| context: "selector", | ||
| positions: true, | ||
| line: (_b = (_a = node === null || node === void 0 ? void 0 : node.loc) === null || _a === void 0 ? void 0 : _a.end.line) !== null && _b !== void 0 ? _b : 1, | ||
| column: (_d = (_c = node === null || node === void 0 ? void 0 : node.loc) === null || _c === void 0 ? void 0 : _c.end.column) !== null && _d !== void 0 ? _d : 1, | ||
| })), accumulatingResult); | ||
| } | ||
| return accumulatingResult; | ||
| }; | ||
| var calculate = function (selector) { | ||
| var ast = (0, css_tree_1.toPlainObject)((0, css_tree_1.parse)(selector, { | ||
| context: "selector", | ||
| })); | ||
| return traverse(ast).total; | ||
| }; | ||
| exports.calculate = calculate; | ||
| var calculateWithDetails = function (selector) { | ||
| var ast = (0, css_tree_1.toPlainObject)((0, css_tree_1.parse)(selector, { | ||
| context: "selector", | ||
| positions: true, | ||
| })); | ||
| return traverse(ast); | ||
| }; | ||
| exports.calculateWithDetails = calculateWithDetails; | ||
| //# sourceMappingURL=calculate.js.map |
| {"version":3,"file":"calculate.js","sourceRoot":"","sources":["../../src/calculate.ts"],"names":[],"mappings":"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"} |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.compareDesc = exports.compare = exports.calculateWithDetails = exports.calculate = void 0; | ||
| var calculate_js_1 = require("./calculate.js"); | ||
| Object.defineProperty(exports, "calculate", { enumerable: true, get: function () { return calculate_js_1.calculate; } }); | ||
| Object.defineProperty(exports, "calculateWithDetails", { enumerable: true, get: function () { return calculate_js_1.calculateWithDetails; } }); | ||
| var sort_js_1 = require("./sort.js"); | ||
| Object.defineProperty(exports, "compare", { enumerable: true, get: function () { return sort_js_1.compare; } }); | ||
| Object.defineProperty(exports, "compareDesc", { enumerable: true, get: function () { return sort_js_1.compareDesc; } }); | ||
| //# sourceMappingURL=index.js.map |
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,+CAAiE;AAAxD,yGAAA,SAAS,OAAA;AAAE,oHAAA,oBAAoB,OAAA;AACxC,qCAAiD;AAAxC,kGAAA,OAAO,OAAA;AAAE,sGAAA,WAAW,OAAA"} |
| { "type": "commonjs" } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.compareDesc = exports.compare = void 0; | ||
| var compare = function (a, b) { | ||
| if (a.A !== b.A) { | ||
| return a.A - b.A; | ||
| } | ||
| else if (a.B !== b.B) { | ||
| return a.B - b.B; | ||
| } | ||
| else { | ||
| return a.C - b.C; | ||
| } | ||
| }; | ||
| exports.compare = compare; | ||
| var compareDesc = function (a, b) { | ||
| return (0, exports.compare)(b, a); | ||
| }; | ||
| exports.compareDesc = compareDesc; | ||
| //# sourceMappingURL=sort.js.map |
| {"version":3,"file":"sort.js","sourceRoot":"","sources":["../../src/sort.ts"],"names":[],"mappings":";;;AAEO,IAAM,OAAO,GAAG,UAAC,CAAc,EAAE,CAAc;IACpD,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACf,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClB;SAAM,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACtB,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClB;SAAM;QACL,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClB;AACH,CAAC,CAAC;AARW,QAAA,OAAO,WAQlB;AAEK,IAAM,WAAW,GAAG,UAAC,CAAc,EAAE,CAAc;IACxD,OAAA,IAAA,eAAO,EAAC,CAAC,EAAE,CAAC,CAAC;AAAb,CAAa,CAAC;AADH,QAAA,WAAW,eACR"} |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| //# sourceMappingURL=types.js.map |
| {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""} |
| var __assign = (this && this.__assign) || function () { | ||
| __assign = Object.assign || function(t) { | ||
| for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
| s = arguments[i]; | ||
| for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
| t[p] = s[p]; | ||
| } | ||
| return t; | ||
| }; | ||
| return __assign.apply(this, arguments); | ||
| }; | ||
| var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { | ||
| if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { | ||
| if (ar || !(i in from)) { | ||
| if (!ar) ar = Array.prototype.slice.call(from, 0, i); | ||
| ar[i] = from[i]; | ||
| } | ||
| } | ||
| return to.concat(ar || Array.prototype.slice.call(from)); | ||
| }; | ||
| import { parse, toPlainObject, } from "css-tree"; | ||
| import { compareDesc } from "./sort.js"; | ||
| var universalSelectorName = "*"; | ||
| var increment = function (node, _a, level) { | ||
| var _b; | ||
| var _c, _d, _e, _f, _g, _h, _j, _k; | ||
| var total = _a.total, contributingParts = _a.contributingParts; | ||
| return ({ | ||
| total: __assign(__assign({}, total), (_b = {}, _b[level] = total[level] + 1, _b)), | ||
| contributingParts: __spreadArray(__spreadArray([], contributingParts, true), [ | ||
| { | ||
| level: level, | ||
| start: { | ||
| line: (_d = (_c = node === null || node === void 0 ? void 0 : node.loc) === null || _c === void 0 ? void 0 : _c.start.line) !== null && _d !== void 0 ? _d : 1, | ||
| column: (_f = (_e = node === null || node === void 0 ? void 0 : node.loc) === null || _e === void 0 ? void 0 : _e.start.column) !== null && _f !== void 0 ? _f : 1, | ||
| }, | ||
| end: { | ||
| line: (_h = (_g = node === null || node === void 0 ? void 0 : node.loc) === null || _g === void 0 ? void 0 : _g.end.line) !== null && _h !== void 0 ? _h : 1, | ||
| column: (_k = (_j = node === null || node === void 0 ? void 0 : node.loc) === null || _j === void 0 ? void 0 : _j.end.column) !== null && _k !== void 0 ? _k : 1, | ||
| }, | ||
| }, | ||
| ], false), | ||
| }); | ||
| }; | ||
| var psuedoElementsWithDeprecatedOneColonNotation = [ | ||
| "before", | ||
| "after", | ||
| "first-line", | ||
| "first-letter", | ||
| ]; | ||
| var getChildrenFromPseudoClassNode = function (node) { | ||
| if (node.children) { | ||
| var firstChild = node.children[0]; | ||
| if (firstChild.type === "SelectorList") { | ||
| return firstChild.children; | ||
| } | ||
| else if (firstChild.type === "Nth" && firstChild.selector) { | ||
| return firstChild.selector.children; | ||
| } | ||
| else { | ||
| return node.children; | ||
| } | ||
| } | ||
| return []; | ||
| }; | ||
| var handlePseudoClassSelector = function (node, accumulatingResult) { | ||
| var name = node.name.toLowerCase(); | ||
| var children = getChildrenFromPseudoClassNode(node); | ||
| if (name === "not" || name === "is" || name === "has") { | ||
| // The specificity of an :is(), :not(), or :has() pseudo-class is replaced by the specificity of the most specific complex selector in its selector list argument | ||
| if (children.length > 0) { | ||
| return children | ||
| .map(function (childNode) { return traverse(childNode, accumulatingResult); }) | ||
| .sort(function (a, b) { return compareDesc(a.total, b.total); })[0]; | ||
| } | ||
| } | ||
| else if (name === "nth-child" || name === "nth-last-child") { | ||
| // The specificity of an :nth-child() or :nth-last-child() selector is the specificity of the pseudo class itself (counting as one pseudo-class selector) plus the specificity of the most specific complex selector in its selector list argument (if any). | ||
| if (children.length > 0) { | ||
| var highestChildSpecificity = children | ||
| .map(function (childNode) { return traverse(childNode, increment(node, accumulatingResult, "B")); }) | ||
| .sort(function (a, b) { return compareDesc(a.total, b.total); })[0]; | ||
| return highestChildSpecificity; | ||
| } | ||
| else { | ||
| return increment(node, accumulatingResult, "B"); | ||
| } | ||
| } | ||
| else if (name === "where") { | ||
| // The specificity of a :where() pseudo-class is replaced by zero. | ||
| return accumulatingResult; | ||
| } | ||
| else if (name === "global" || name === "local") { | ||
| // The specificity for :global() and :local() is replaced by the specificity of the child selector because although they look like psuedo classes they are actually an identifier for CSS Modules | ||
| if (children.length > 0) { | ||
| return children.reduce(function (acc, childNode) { return traverse(childNode, acc); }, accumulatingResult); | ||
| } | ||
| } | ||
| else if (psuedoElementsWithDeprecatedOneColonNotation.includes(name)) { | ||
| // These pseudo-elements can look like pseudo-classes | ||
| // https://www.w3.org/TR/selectors-4/#pseudo-elements | ||
| return increment(node, accumulatingResult, "C"); | ||
| } | ||
| else { | ||
| return increment(node, accumulatingResult, "B"); | ||
| } | ||
| return accumulatingResult; | ||
| }; | ||
| var traverse = function (node, accumulatingResult) { | ||
| var _a, _b, _c, _d; | ||
| if (accumulatingResult === void 0) { accumulatingResult = { | ||
| total: { A: 0, B: 0, C: 0 }, | ||
| contributingParts: [], | ||
| }; } | ||
| if (node.type === "IdSelector") { | ||
| return increment(node, accumulatingResult, "A"); | ||
| } | ||
| else if (node.type === "PseudoClassSelector") { | ||
| return handlePseudoClassSelector(node, accumulatingResult); | ||
| } | ||
| else if (node.type === "ClassSelector" || | ||
| node.type === "AttributeSelector") { | ||
| return increment(node, accumulatingResult, "B"); | ||
| } | ||
| else if (node.type === "TypeSelector") { | ||
| if (node.name !== universalSelectorName) { | ||
| return increment(node, accumulatingResult, "C"); | ||
| } | ||
| } | ||
| else if (node.type === "PseudoElementSelector") { | ||
| return increment(node, accumulatingResult, "C"); | ||
| } | ||
| else if (node.type === "Selector" || node.type === "SelectorList") { | ||
| return node.children.reduce(function (acc, childNode) { return traverse(childNode, acc); }, accumulatingResult); | ||
| } | ||
| else if (node.type === "Raw") { | ||
| return traverse(toPlainObject(parse(node.value, { | ||
| context: "selector", | ||
| positions: true, | ||
| line: (_b = (_a = node === null || node === void 0 ? void 0 : node.loc) === null || _a === void 0 ? void 0 : _a.end.line) !== null && _b !== void 0 ? _b : 1, | ||
| column: (_d = (_c = node === null || node === void 0 ? void 0 : node.loc) === null || _c === void 0 ? void 0 : _c.end.column) !== null && _d !== void 0 ? _d : 1, | ||
| })), accumulatingResult); | ||
| } | ||
| return accumulatingResult; | ||
| }; | ||
| export var calculate = function (selector) { | ||
| var ast = toPlainObject(parse(selector, { | ||
| context: "selector", | ||
| })); | ||
| return traverse(ast).total; | ||
| }; | ||
| export var calculateWithDetails = function (selector) { | ||
| var ast = toPlainObject(parse(selector, { | ||
| context: "selector", | ||
| positions: true, | ||
| })); | ||
| return traverse(ast); | ||
| }; | ||
| //# sourceMappingURL=calculate.js.map |
| {"version":3,"file":"calculate.js","sourceRoot":"","sources":["../../src/calculate.ts"],"names":[],"mappings":"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"} |
| export { calculate, calculateWithDetails } from "./calculate.js"; | ||
| export { compare, compareDesc } from "./sort.js"; | ||
| //# sourceMappingURL=index.js.map |
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"} |
| export var compare = function (a, b) { | ||
| if (a.A !== b.A) { | ||
| return a.A - b.A; | ||
| } | ||
| else if (a.B !== b.B) { | ||
| return a.B - b.B; | ||
| } | ||
| else { | ||
| return a.C - b.C; | ||
| } | ||
| }; | ||
| export var compareDesc = function (a, b) { | ||
| return compare(b, a); | ||
| }; | ||
| //# sourceMappingURL=sort.js.map |
| {"version":3,"file":"sort.js","sourceRoot":"","sources":["../../src/sort.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,IAAM,OAAO,GAAG,UAAC,CAAc,EAAE,CAAc;IACpD,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACf,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClB;SAAM,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACtB,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClB;SAAM;QACL,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClB;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,IAAM,WAAW,GAAG,UAAC,CAAc,EAAE,CAAc;IACxD,OAAA,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;AAAb,CAAa,CAAC"} |
| export {}; | ||
| //# sourceMappingURL=types.js.map |
| {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""} |
| import { Result, Specificity } from "./types"; | ||
| export declare const calculate: (selector: string) => Specificity; | ||
| export declare const calculateWithDetails: (selector: string) => Result; | ||
| //# sourceMappingURL=calculate.d.ts.map |
| {"version":3,"file":"calculate.d.ts","sourceRoot":"","sources":["../../src/calculate.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAS,MAAM,SAAS,CAAC;AA2IrD,eAAO,MAAM,SAAS,aAAc,MAAM,KAAG,WAO5C,CAAC;AAEF,eAAO,MAAM,oBAAoB,aAAc,MAAM,KAAG,MAQvD,CAAC"} |
| export { calculate, calculateWithDetails } from "./calculate.js"; | ||
| export { compare, compareDesc } from "./sort.js"; | ||
| //# sourceMappingURL=index.d.ts.map |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"} |
| import { Specificity } from "./types"; | ||
| export declare const compare: (a: Specificity, b: Specificity) => number; | ||
| export declare const compareDesc: (a: Specificity, b: Specificity) => number; | ||
| //# sourceMappingURL=sort.d.ts.map |
| {"version":3,"file":"sort.d.ts","sourceRoot":"","sources":["../../src/sort.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,eAAO,MAAM,OAAO,MAAO,WAAW,KAAK,WAAW,KAAG,MAQxD,CAAC;AAEF,eAAO,MAAM,WAAW,MAAO,WAAW,KAAK,WAAW,KAAG,MAC9C,CAAC"} |
| export type Level = "A" | "B" | "C"; | ||
| export type Specificity = Record<Level, number>; | ||
| export type SpecificityContributingPart = { | ||
| level: Level; | ||
| start: { | ||
| line: number; | ||
| column: number; | ||
| }; | ||
| end: { | ||
| line: number; | ||
| column: number; | ||
| }; | ||
| }; | ||
| export type Result = { | ||
| total: Specificity; | ||
| contributingParts: SpecificityContributingPart[]; | ||
| }; | ||
| //# sourceMappingURL=types.d.ts.map |
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,KAAK,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEpC,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAEhD,MAAM,MAAM,2BAA2B,GAAG;IACxC,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,EAAE,WAAW,CAAC;IACnB,iBAAiB,EAAE,2BAA2B,EAAE,CAAC;CAClD,CAAC"} |
+30
-11
| { | ||
| "name": "specificity", | ||
| "version": "0.4.1", | ||
| "version": "1.0.0-beta", | ||
| "description": "Calculate the specificity of a CSS selector", | ||
@@ -22,17 +22,36 @@ "keywords": [ | ||
| }, | ||
| "main": "dist/specificity", | ||
| "module": "dist/specificity.mjs", | ||
| "bin": { | ||
| "specificity": "./bin/specificity" | ||
| "type": "module", | ||
| "module": "./dist/esm/index.js", | ||
| "main": "./dist/cjs/index.js", | ||
| "types": "./dist/types/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/types/index.d.ts", | ||
| "import": "./dist/esm/index.js", | ||
| "require": "./dist/cjs/index.js" | ||
| }, | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "scripts": { | ||
| "prepare": "rollup --config", | ||
| "test": "mocha test/test.js --require esm" | ||
| "prebuild": "rm -rf ./dist", | ||
| "build": "tsc -b ./tsconfig.json ./tsconfig.cjs.json && cp package-cjs.json dist/cjs/package.json", | ||
| "test": "jest && node test/test-esm.mjs && node test/test-cjs.cjs" | ||
| }, | ||
| "devDependencies": { | ||
| "esm": "^3.0.71", | ||
| "mocha": "^5.2.0", | ||
| "rollup": "^0.62.0" | ||
| "@jest/globals": "^29.5.0", | ||
| "@types/css-tree": "^2.3.1", | ||
| "jest": "^29.5.0", | ||
| "prettier": "^2.8.8", | ||
| "ts-jest": "^29.1.0", | ||
| "typescript": "^5.1.3" | ||
| }, | ||
| "types": "specificity.d.ts" | ||
| "dependencies": { | ||
| "css-tree": "^2.3.1" | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "package.json", | ||
| "README.md", | ||
| "LICENSE" | ||
| ] | ||
| } |
+48
-146
| # Specificity Calculator | ||
| A JavaScript module for calculating and comparing the [specificity of CSS selectors](https://www.w3.org/TR/selectors-3/#specificity). The module is used on the [Specificity Calculator](https://specificity.keegan.st/) website. | ||
| A JavaScript module for calculating and comparing the [specificity of CSS selectors](https://www.w3.org/TR/selectors-4/#specificity). The module is used on the [Specificity Calculator](https://specificity.keegan.st/) website. | ||
| Specificity Calculator is built for CSS Selectors Level 3. Specificity Calculator isn’t a CSS validator. If you enter invalid selectors it will return incorrect results. For example, the [negation pseudo-class](https://www.w3.org/TR/selectors-3/#negation) may only take a simple selector as an argument. Using a psuedo-element or combinator as an argument for `:not()` is invalid CSS so Specificity Calculator will return incorrect results. | ||
| Note that version 1 is a complete re-write with support for CSS Selectors Level 4, and has a different API than earlier versions. | ||
| ## Supported runtime environments | ||
| ## calculate(selector) | ||
| The module is provided in two formats: an ECMAScript (ES) module in `dist/specificity.mjs`, and a Universal Module Definition (UMD) in `dist/specificity.js`. This enables support for the following runtime environments: | ||
| ### Parameters | ||
| **Browser** | ||
| - `selector`: `string` - should be a valid CSS selector | ||
| * Directly loaded ES module | ||
| * ES module in a precompiled script (using a bundler like Webpack or Rollup) | ||
| * Global variable | ||
| ### Returns | ||
| **Node.js** | ||
| A `Specificity` object with a type of `Record<"A" | "B" | "C", number>`. | ||
| * ES module | ||
| * CommonJS module | ||
| ### Examples | ||
| ### Browser usage as a directly loaded ES module | ||
| ```html | ||
| <script type="module"> | ||
| import { calculate } from './specificity/dist/specificity.mjs'; | ||
| calculate('ul#nav li.active a'); | ||
| </script> | ||
| ``` | ||
| ### Browser usage as an ES module in a precompiled script | ||
| Bundlers like [Webpack and Rollup](https://github.com/rollup/rollup/wiki/pkg.module) import from the `module` field in `package.json`, which is set to the ES module artefact, `dist/specificity.mjs`. | ||
| ```js | ||
| import { calculate } from 'specificity'; | ||
| calculate("#id"); | ||
| { | ||
| A: 1, | ||
| B: 0, | ||
| C: 0 | ||
| } | ||
| calculate('ul#nav li.active a'); | ||
| ``` | ||
| calculate(".classname"); | ||
| { | ||
| A: 0, | ||
| B: 1, | ||
| C: 0 | ||
| } | ||
| ### Browser usage as a global variable | ||
| calculate("element"); | ||
| { | ||
| A: 0, | ||
| B: 0, | ||
| C: 1 | ||
| } | ||
| The UMD artefact, `dist/specificity.js`, sets a global variable, `SPECIFICITY`. | ||
| ```html | ||
| <script src="./specificity/dist/specificity.js"></script> | ||
| <script> | ||
| SPECIFICITY.calculate('ul#nav li.active a'); | ||
| </script> | ||
| ``` | ||
| ### Node.js usage as an ES module | ||
| The `main` field in `package.json` has an extensionless value, `dist/specificity`. This allows Node.js to use either the ES module, in `dist/specificity.mjs`, or the CommonJS module, in `dist/specificity.js`. | ||
| When Node.js is run with the `--experimental-modules` [flag](https://nodejs.org/api/esm.html) or an [ES module loader](https://www.npmjs.com/package/esm), it will use the ES module artefact. | ||
| ```js | ||
| import { calculate } from 'specificity'; | ||
| calculate('ul#nav li.active a'); | ||
| { | ||
| A: 1, | ||
| B: 1, | ||
| C: 3 | ||
| } | ||
| ``` | ||
| ### Node.js usage as a CommonJS module | ||
| ## compare(a, b) | ||
| Otherwise, Node.js will use the UMD artefact, which contains a CommonJS module definition. | ||
| ### Parameters | ||
| ```js | ||
| const { calculate } = require('specificity'); | ||
| - `a`: `Specificity` object with a type of `Record<"A" | "B" | "C", number>` | ||
| - `b`: `Specificity` object with a type of `Record<"A" | "B" | "C", number>` | ||
| calculate('ul#nav li.active a'); | ||
| ``` | ||
| ### Returns | ||
| ## Calculate function | ||
| Returns a positive number if `a` has a higher specificity than `b` | ||
| Returns a negative number if `a` has a lower specificity than `b` | ||
| Returns `0` if `a` has the same specificity than `b` | ||
| The `calculate` function returns an array containing a result object for each selector input. Each result object has the following properties: | ||
| ### Examples | ||
| * `selector`: the input | ||
| * `specificity`: the result as a string e.g. `0,1,0,0` | ||
| * `specificityArray`: the result as an array of numbers e.g. `[0, 1, 0, 0]` | ||
| * `parts`: array with details about each part of the selector that counts towards the specificity | ||
| ## Example | ||
| ```js | ||
| calculate('ul#nav li.active a'); | ||
| /* | ||
| [ | ||
| { | ||
| selector: 'ul#nav li.active a', | ||
| specificity: '0,1,1,3', | ||
| specificityArray: [0, 1, 1, 3], | ||
| parts: [ | ||
| { selector: 'ul', type: 'c', index: 0, length: 2 }, | ||
| { selector: '#nav', type: 'a', index: 2, length: 4 }, | ||
| { selector: 'li', type: 'c', index: 5, length: 2 }, | ||
| { selector: '.active', type: 'b', index: 8, length: 7 }, | ||
| { selector: 'a', type: 'c', index: 13, length: 1 } | ||
| ] | ||
| } | ||
| "element", | ||
| ".classname", | ||
| "#id", | ||
| ] | ||
| */ | ||
| ``` | ||
| .map(calculate) | ||
| .sort(compare); | ||
| You can use comma separation to pass in multiple selectors: | ||
| ```js | ||
| calculate('ul#nav li.active a, body.ie7 .col_3 h2 ~ h2'); | ||
| /* | ||
| [ | ||
| { | ||
| selector: 'ul#nav li.active a', | ||
| specificity: '0,1,1,3', | ||
| ... | ||
| }, | ||
| { | ||
| selector: 'body.ie7 .col_3 h2 ~ h2', | ||
| specificity: '0,0,2,3', | ||
| ... | ||
| } | ||
| ] | ||
| */ | ||
| [ { A: 0, B: 0, C: 1 }, { A: 0, B: 1, C: 0 }, { A: 1, B: 0, C: 0 } ] | ||
| ``` | ||
| ## Comparing two selectors | ||
| ## compareDesc(a, b) | ||
| Specificity Calculator also exports a `compare` function. This function accepts two CSS selectors or specificity arrays, `a` and `b`. | ||
| * It returns `-1` if `a` has a lower specificity than `b` | ||
| * It returns `1` if `a` has a higher specificity than `b` | ||
| * It returns `0` if `a` has the same specificity than `b` | ||
| ```js | ||
| compare('div', '.active'); // -1 | ||
| compare('#main', 'div'); // 1 | ||
| compare('span', 'div'); // 0 | ||
| compare('span', [0, 0, 0, 1]); // 0 | ||
| compare('#main > div', [0, 1, 0, 1]); // 0 | ||
| ``` | ||
| ## Ordering an array of selectors by specificity | ||
| You can pass the `compare` function to `Array.prototype.sort` to sort an array of CSS selectors by specificity. | ||
| ```js | ||
| import { compare } from 'specificity'; | ||
| ['#main', 'p', '.active'].sort(compare); // ['p', '.active', '#main'] | ||
| ``` | ||
| ## Command-line usage | ||
| Run `npm install specificity` to install the module locally, or `npm install -g specificity` for global installation. Run `specificity` without arguments to learn about its usage: | ||
| ```bash | ||
| $ specificity | ||
| Usage: specificity <selector> | ||
| Computes specificity of a CSS selector. | ||
| ``` | ||
| Pass a selector as the first argument to get its specificity computed: | ||
| ```bash | ||
| $ specificity "ul#nav li.active a" | ||
| 0,1,1,3 | ||
| ``` | ||
| ## Testing | ||
| To install dependencies, run: `npm install` | ||
| Then to test, run: `npm test` | ||
| Same as `compare` but returns the opposite value, for use in sorting specificity objects with the highest specificity first. |
Sorry, the diff of this file is not supported yet
| (function (global, factory) { | ||
| typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
| typeof define === 'function' && define.amd ? define(['exports'], factory) : | ||
| (factory((global.SPECIFICITY = {}))); | ||
| }(this, (function (exports) { 'use strict'; | ||
| // Calculate the specificity for a selector by dividing it into simple selectors and counting them | ||
| var calculate = function(input) { | ||
| var selectors, | ||
| selector, | ||
| i, | ||
| len, | ||
| results = []; | ||
| // Separate input by commas | ||
| selectors = input.split(','); | ||
| for (i = 0, len = selectors.length; i < len; i += 1) { | ||
| selector = selectors[i]; | ||
| if (selector.length > 0) { | ||
| results.push(calculateSingle(selector)); | ||
| } | ||
| } | ||
| return results; | ||
| }; | ||
| /** | ||
| * Calculates the specificity of CSS selectors | ||
| * http://www.w3.org/TR/css3-selectors/#specificity | ||
| * | ||
| * Returns an object with the following properties: | ||
| * - selector: the input | ||
| * - specificity: e.g. 0,1,0,0 | ||
| * - parts: array with details about each part of the selector that counts towards the specificity | ||
| * - specificityArray: e.g. [0, 1, 0, 0] | ||
| */ | ||
| var calculateSingle = function(input) { | ||
| var selector = input, | ||
| findMatch, | ||
| typeCount = { | ||
| 'a': 0, | ||
| 'b': 0, | ||
| 'c': 0 | ||
| }, | ||
| parts = [], | ||
| // The following regular expressions assume that selectors matching the preceding regular expressions have been removed | ||
| attributeRegex = /(\[[^\]]+\])/g, | ||
| idRegex = /(#[^\#\s\+>~\.\[:\)]+)/g, | ||
| classRegex = /(\.[^\s\+>~\.\[:\)]+)/g, | ||
| pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi, | ||
| // A regex for pseudo classes with brackets - :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-type(), :lang() | ||
| // The negation psuedo class (:not) is filtered out because specificity is calculated on its argument | ||
| // :global and :local are filtered out - they look like psuedo classes but are an identifier for CSS Modules | ||
| pseudoClassWithBracketsRegex = /(:(?!not|global|local)[\w-]+\([^\)]*\))/gi, | ||
| // A regex for other pseudo classes, which don't have brackets | ||
| pseudoClassRegex = /(:(?!not|global|local)[^\s\+>~\.\[:]+)/g, | ||
| elementRegex = /([^\s\+>~\.\[:]+)/g; | ||
| // Find matches for a regular expression in a string and push their details to parts | ||
| // Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements | ||
| findMatch = function(regex, type) { | ||
| var matches, i, len, match, index, length; | ||
| if (regex.test(selector)) { | ||
| matches = selector.match(regex); | ||
| for (i = 0, len = matches.length; i < len; i += 1) { | ||
| typeCount[type] += 1; | ||
| match = matches[i]; | ||
| index = selector.indexOf(match); | ||
| length = match.length; | ||
| parts.push({ | ||
| selector: input.substr(index, length), | ||
| type: type, | ||
| index: index, | ||
| length: length | ||
| }); | ||
| // Replace this simple selector with whitespace so it won't be counted in further simple selectors | ||
| selector = selector.replace(match, Array(length + 1).join(' ')); | ||
| } | ||
| } | ||
| }; | ||
| // Replace escaped characters with plain text, using the "A" character | ||
| // https://www.w3.org/TR/CSS21/syndata.html#characters | ||
| (function() { | ||
| var replaceWithPlainText = function(regex) { | ||
| var matches, i, len, match; | ||
| if (regex.test(selector)) { | ||
| matches = selector.match(regex); | ||
| for (i = 0, len = matches.length; i < len; i += 1) { | ||
| match = matches[i]; | ||
| selector = selector.replace(match, Array(match.length + 1).join('A')); | ||
| } | ||
| } | ||
| }, | ||
| // Matches a backslash followed by six hexadecimal digits followed by an optional single whitespace character | ||
| escapeHexadecimalRegex = /\\[0-9A-Fa-f]{6}\s?/g, | ||
| // Matches a backslash followed by fewer than six hexadecimal digits followed by a mandatory single whitespace character | ||
| escapeHexadecimalRegex2 = /\\[0-9A-Fa-f]{1,5}\s/g, | ||
| // Matches a backslash followed by any character | ||
| escapeSpecialCharacter = /\\./g; | ||
| replaceWithPlainText(escapeHexadecimalRegex); | ||
| replaceWithPlainText(escapeHexadecimalRegex2); | ||
| replaceWithPlainText(escapeSpecialCharacter); | ||
| }()); | ||
| // Remove anything after a left brace in case a user has pasted in a rule, not just a selector | ||
| (function() { | ||
| var regex = /{[^]*/gm, | ||
| matches, i, len, match; | ||
| if (regex.test(selector)) { | ||
| matches = selector.match(regex); | ||
| for (i = 0, len = matches.length; i < len; i += 1) { | ||
| match = matches[i]; | ||
| selector = selector.replace(match, Array(match.length + 1).join(' ')); | ||
| } | ||
| } | ||
| }()); | ||
| // Add attribute selectors to parts collection (type b) | ||
| findMatch(attributeRegex, 'b'); | ||
| // Add ID selectors to parts collection (type a) | ||
| findMatch(idRegex, 'a'); | ||
| // Add class selectors to parts collection (type b) | ||
| findMatch(classRegex, 'b'); | ||
| // Add pseudo-element selectors to parts collection (type c) | ||
| findMatch(pseudoElementRegex, 'c'); | ||
| // Add pseudo-class selectors to parts collection (type b) | ||
| findMatch(pseudoClassWithBracketsRegex, 'b'); | ||
| findMatch(pseudoClassRegex, 'b'); | ||
| // Remove universal selector and separator characters | ||
| selector = selector.replace(/[\*\s\+>~]/g, ' '); | ||
| // Remove any stray dots or hashes which aren't attached to words | ||
| // These may be present if the user is live-editing this selector | ||
| selector = selector.replace(/[#\.]/g, ' '); | ||
| // Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument | ||
| // Remove non-standard :local and :global CSS Module identifiers because they do not effect the specificity | ||
| selector = selector.replace(/:not/g, ' '); | ||
| selector = selector.replace(/:local/g, ' '); | ||
| selector = selector.replace(/:global/g, ' '); | ||
| selector = selector.replace(/[\(\)]/g, ' '); | ||
| // The only things left should be element selectors (type c) | ||
| findMatch(elementRegex, 'c'); | ||
| // Order the parts in the order they appear in the original selector | ||
| // This is neater for external apps to deal with | ||
| parts.sort(function(a, b) { | ||
| return a.index - b.index; | ||
| }); | ||
| return { | ||
| selector: input, | ||
| specificity: '0,' + typeCount.a.toString() + ',' + typeCount.b.toString() + ',' + typeCount.c.toString(), | ||
| specificityArray: [0, typeCount.a, typeCount.b, typeCount.c], | ||
| parts: parts | ||
| }; | ||
| }; | ||
| /** | ||
| * Compares two CSS selectors for specificity | ||
| * Alternatively you can replace one of the CSS selectors with a specificity array | ||
| * | ||
| * - it returns -1 if a has a lower specificity than b | ||
| * - it returns 1 if a has a higher specificity than b | ||
| * - it returns 0 if a has the same specificity than b | ||
| */ | ||
| var compare = function(a, b) { | ||
| var aSpecificity, | ||
| bSpecificity, | ||
| i; | ||
| if (typeof a ==='string') { | ||
| if (a.indexOf(',') !== -1) { | ||
| throw 'Invalid CSS selector'; | ||
| } else { | ||
| aSpecificity = calculateSingle(a)['specificityArray']; | ||
| } | ||
| } else if (Array.isArray(a)) { | ||
| if (a.filter(function(e) { return (typeof e === 'number'); }).length !== 4) { | ||
| throw 'Invalid specificity array'; | ||
| } else { | ||
| aSpecificity = a; | ||
| } | ||
| } else { | ||
| throw 'Invalid CSS selector or specificity array'; | ||
| } | ||
| if (typeof b ==='string') { | ||
| if (b.indexOf(',') !== -1) { | ||
| throw 'Invalid CSS selector'; | ||
| } else { | ||
| bSpecificity = calculateSingle(b)['specificityArray']; | ||
| } | ||
| } else if (Array.isArray(b)) { | ||
| if (b.filter(function(e) { return (typeof e === 'number'); }).length !== 4) { | ||
| throw 'Invalid specificity array'; | ||
| } else { | ||
| bSpecificity = b; | ||
| } | ||
| } else { | ||
| throw 'Invalid CSS selector or specificity array'; | ||
| } | ||
| for (i = 0; i < 4; i += 1) { | ||
| if (aSpecificity[i] < bSpecificity[i]) { | ||
| return -1; | ||
| } else if (aSpecificity[i] > bSpecificity[i]) { | ||
| return 1; | ||
| } | ||
| } | ||
| return 0; | ||
| }; | ||
| exports.calculate = calculate; | ||
| exports.compare = compare; | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| }))); |
| // Calculate the specificity for a selector by dividing it into simple selectors and counting them | ||
| var calculate = function(input) { | ||
| var selectors, | ||
| selector, | ||
| i, | ||
| len, | ||
| results = []; | ||
| // Separate input by commas | ||
| selectors = input.split(','); | ||
| for (i = 0, len = selectors.length; i < len; i += 1) { | ||
| selector = selectors[i]; | ||
| if (selector.length > 0) { | ||
| results.push(calculateSingle(selector)); | ||
| } | ||
| } | ||
| return results; | ||
| }; | ||
| /** | ||
| * Calculates the specificity of CSS selectors | ||
| * http://www.w3.org/TR/css3-selectors/#specificity | ||
| * | ||
| * Returns an object with the following properties: | ||
| * - selector: the input | ||
| * - specificity: e.g. 0,1,0,0 | ||
| * - parts: array with details about each part of the selector that counts towards the specificity | ||
| * - specificityArray: e.g. [0, 1, 0, 0] | ||
| */ | ||
| var calculateSingle = function(input) { | ||
| var selector = input, | ||
| findMatch, | ||
| typeCount = { | ||
| 'a': 0, | ||
| 'b': 0, | ||
| 'c': 0 | ||
| }, | ||
| parts = [], | ||
| // The following regular expressions assume that selectors matching the preceding regular expressions have been removed | ||
| attributeRegex = /(\[[^\]]+\])/g, | ||
| idRegex = /(#[^\#\s\+>~\.\[:\)]+)/g, | ||
| classRegex = /(\.[^\s\+>~\.\[:\)]+)/g, | ||
| pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi, | ||
| // A regex for pseudo classes with brackets - :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-type(), :lang() | ||
| // The negation psuedo class (:not) is filtered out because specificity is calculated on its argument | ||
| // :global and :local are filtered out - they look like psuedo classes but are an identifier for CSS Modules | ||
| pseudoClassWithBracketsRegex = /(:(?!not|global|local)[\w-]+\([^\)]*\))/gi, | ||
| // A regex for other pseudo classes, which don't have brackets | ||
| pseudoClassRegex = /(:(?!not|global|local)[^\s\+>~\.\[:]+)/g, | ||
| elementRegex = /([^\s\+>~\.\[:]+)/g; | ||
| // Find matches for a regular expression in a string and push their details to parts | ||
| // Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements | ||
| findMatch = function(regex, type) { | ||
| var matches, i, len, match, index, length; | ||
| if (regex.test(selector)) { | ||
| matches = selector.match(regex); | ||
| for (i = 0, len = matches.length; i < len; i += 1) { | ||
| typeCount[type] += 1; | ||
| match = matches[i]; | ||
| index = selector.indexOf(match); | ||
| length = match.length; | ||
| parts.push({ | ||
| selector: input.substr(index, length), | ||
| type: type, | ||
| index: index, | ||
| length: length | ||
| }); | ||
| // Replace this simple selector with whitespace so it won't be counted in further simple selectors | ||
| selector = selector.replace(match, Array(length + 1).join(' ')); | ||
| } | ||
| } | ||
| }; | ||
| // Replace escaped characters with plain text, using the "A" character | ||
| // https://www.w3.org/TR/CSS21/syndata.html#characters | ||
| (function() { | ||
| var replaceWithPlainText = function(regex) { | ||
| var matches, i, len, match; | ||
| if (regex.test(selector)) { | ||
| matches = selector.match(regex); | ||
| for (i = 0, len = matches.length; i < len; i += 1) { | ||
| match = matches[i]; | ||
| selector = selector.replace(match, Array(match.length + 1).join('A')); | ||
| } | ||
| } | ||
| }, | ||
| // Matches a backslash followed by six hexadecimal digits followed by an optional single whitespace character | ||
| escapeHexadecimalRegex = /\\[0-9A-Fa-f]{6}\s?/g, | ||
| // Matches a backslash followed by fewer than six hexadecimal digits followed by a mandatory single whitespace character | ||
| escapeHexadecimalRegex2 = /\\[0-9A-Fa-f]{1,5}\s/g, | ||
| // Matches a backslash followed by any character | ||
| escapeSpecialCharacter = /\\./g; | ||
| replaceWithPlainText(escapeHexadecimalRegex); | ||
| replaceWithPlainText(escapeHexadecimalRegex2); | ||
| replaceWithPlainText(escapeSpecialCharacter); | ||
| }()); | ||
| // Remove anything after a left brace in case a user has pasted in a rule, not just a selector | ||
| (function() { | ||
| var regex = /{[^]*/gm, | ||
| matches, i, len, match; | ||
| if (regex.test(selector)) { | ||
| matches = selector.match(regex); | ||
| for (i = 0, len = matches.length; i < len; i += 1) { | ||
| match = matches[i]; | ||
| selector = selector.replace(match, Array(match.length + 1).join(' ')); | ||
| } | ||
| } | ||
| }()); | ||
| // Add attribute selectors to parts collection (type b) | ||
| findMatch(attributeRegex, 'b'); | ||
| // Add ID selectors to parts collection (type a) | ||
| findMatch(idRegex, 'a'); | ||
| // Add class selectors to parts collection (type b) | ||
| findMatch(classRegex, 'b'); | ||
| // Add pseudo-element selectors to parts collection (type c) | ||
| findMatch(pseudoElementRegex, 'c'); | ||
| // Add pseudo-class selectors to parts collection (type b) | ||
| findMatch(pseudoClassWithBracketsRegex, 'b'); | ||
| findMatch(pseudoClassRegex, 'b'); | ||
| // Remove universal selector and separator characters | ||
| selector = selector.replace(/[\*\s\+>~]/g, ' '); | ||
| // Remove any stray dots or hashes which aren't attached to words | ||
| // These may be present if the user is live-editing this selector | ||
| selector = selector.replace(/[#\.]/g, ' '); | ||
| // Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument | ||
| // Remove non-standard :local and :global CSS Module identifiers because they do not effect the specificity | ||
| selector = selector.replace(/:not/g, ' '); | ||
| selector = selector.replace(/:local/g, ' '); | ||
| selector = selector.replace(/:global/g, ' '); | ||
| selector = selector.replace(/[\(\)]/g, ' '); | ||
| // The only things left should be element selectors (type c) | ||
| findMatch(elementRegex, 'c'); | ||
| // Order the parts in the order they appear in the original selector | ||
| // This is neater for external apps to deal with | ||
| parts.sort(function(a, b) { | ||
| return a.index - b.index; | ||
| }); | ||
| return { | ||
| selector: input, | ||
| specificity: '0,' + typeCount.a.toString() + ',' + typeCount.b.toString() + ',' + typeCount.c.toString(), | ||
| specificityArray: [0, typeCount.a, typeCount.b, typeCount.c], | ||
| parts: parts | ||
| }; | ||
| }; | ||
| /** | ||
| * Compares two CSS selectors for specificity | ||
| * Alternatively you can replace one of the CSS selectors with a specificity array | ||
| * | ||
| * - it returns -1 if a has a lower specificity than b | ||
| * - it returns 1 if a has a higher specificity than b | ||
| * - it returns 0 if a has the same specificity than b | ||
| */ | ||
| var compare = function(a, b) { | ||
| var aSpecificity, | ||
| bSpecificity, | ||
| i; | ||
| if (typeof a ==='string') { | ||
| if (a.indexOf(',') !== -1) { | ||
| throw 'Invalid CSS selector'; | ||
| } else { | ||
| aSpecificity = calculateSingle(a)['specificityArray']; | ||
| } | ||
| } else if (Array.isArray(a)) { | ||
| if (a.filter(function(e) { return (typeof e === 'number'); }).length !== 4) { | ||
| throw 'Invalid specificity array'; | ||
| } else { | ||
| aSpecificity = a; | ||
| } | ||
| } else { | ||
| throw 'Invalid CSS selector or specificity array'; | ||
| } | ||
| if (typeof b ==='string') { | ||
| if (b.indexOf(',') !== -1) { | ||
| throw 'Invalid CSS selector'; | ||
| } else { | ||
| bSpecificity = calculateSingle(b)['specificityArray']; | ||
| } | ||
| } else if (Array.isArray(b)) { | ||
| if (b.filter(function(e) { return (typeof e === 'number'); }).length !== 4) { | ||
| throw 'Invalid specificity array'; | ||
| } else { | ||
| bSpecificity = b; | ||
| } | ||
| } else { | ||
| throw 'Invalid CSS selector or specificity array'; | ||
| } | ||
| for (i = 0; i < 4; i += 1) { | ||
| if (aSpecificity[i] < bSpecificity[i]) { | ||
| return -1; | ||
| } else if (aSpecificity[i] > bSpecificity[i]) { | ||
| return 1; | ||
| } | ||
| } | ||
| return 0; | ||
| }; | ||
| export { calculate, compare }; |
| const path = require('path'); | ||
| export default [ | ||
| { | ||
| input: 'specificity.js', | ||
| output: [ | ||
| { | ||
| file: 'dist/specificity.mjs', | ||
| format: 'es', | ||
| }, | ||
| { | ||
| file: 'dist/specificity.js', | ||
| format: 'umd', | ||
| name: 'SPECIFICITY', | ||
| }, | ||
| ], | ||
| }, | ||
| ]; |
| /** | ||
| * Specificity arrays always have 4 numbers (integers) for quick comparison | ||
| * comparing from left to right, the next number only has to be checked if | ||
| * two numbers of the same index are equal. | ||
| */ | ||
| export type SpecificityArray = [number, number, number, number]; | ||
| /** | ||
| * A result of parsing a selector into an array of parts. | ||
| * Calculating a specificity array is a matter of summing | ||
| * over all the parts and adding the values to the right | ||
| * bucket in a specificity array. | ||
| * | ||
| * @interface Part | ||
| */ | ||
| export interface Part { | ||
| selector: string; | ||
| type: 'a' | 'b' | 'c'; | ||
| index: number; | ||
| length: number; | ||
| } | ||
| /** | ||
| * Returned by the calculate function. Represents the results | ||
| * of parsing and calculating the specificity of a selector. | ||
| * | ||
| * @interface Specificity | ||
| */ | ||
| export interface Specificity { | ||
| selector: string; | ||
| specificity: string; | ||
| specificityArray: SpecificityArray; | ||
| parts: Array<Part>; | ||
| } | ||
| /** | ||
| * Calculates the specificity for the given selector string. | ||
| * If the string contains a comma, each selector will be parsed | ||
| * separately. | ||
| * | ||
| * @returns A list of specificity objects one for each selector in the | ||
| * selector string. | ||
| */ | ||
| export function calculate(selector: string): Array<Specificity>; | ||
| /** | ||
| * Compares two selectors. If a string, the string cannot contain a comma. | ||
| * | ||
| * @returns A value less than 0 if selector a is less specific than selector b. | ||
| * A value more than 0 if selector a is more specific than selector b. | ||
| * 0 if the two selectors have the same specificity. | ||
| */ | ||
| export function compare(a: string | SpecificityArray, b: string | SpecificityArray): -1 | 0 | 1; |
-221
| // Calculate the specificity for a selector by dividing it into simple selectors and counting them | ||
| var calculate = function(input) { | ||
| var selectors, | ||
| selector, | ||
| i, | ||
| len, | ||
| results = []; | ||
| // Separate input by commas | ||
| selectors = input.split(','); | ||
| for (i = 0, len = selectors.length; i < len; i += 1) { | ||
| selector = selectors[i]; | ||
| if (selector.length > 0) { | ||
| results.push(calculateSingle(selector)); | ||
| } | ||
| } | ||
| return results; | ||
| }; | ||
| /** | ||
| * Calculates the specificity of CSS selectors | ||
| * http://www.w3.org/TR/css3-selectors/#specificity | ||
| * | ||
| * Returns an object with the following properties: | ||
| * - selector: the input | ||
| * - specificity: e.g. 0,1,0,0 | ||
| * - parts: array with details about each part of the selector that counts towards the specificity | ||
| * - specificityArray: e.g. [0, 1, 0, 0] | ||
| */ | ||
| var calculateSingle = function(input) { | ||
| var selector = input, | ||
| findMatch, | ||
| typeCount = { | ||
| 'a': 0, | ||
| 'b': 0, | ||
| 'c': 0 | ||
| }, | ||
| parts = [], | ||
| // The following regular expressions assume that selectors matching the preceding regular expressions have been removed | ||
| attributeRegex = /(\[[^\]]+\])/g, | ||
| idRegex = /(#[^\#\s\+>~\.\[:\)]+)/g, | ||
| classRegex = /(\.[^\s\+>~\.\[:\)]+)/g, | ||
| pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi, | ||
| // A regex for pseudo classes with brackets - :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-type(), :lang() | ||
| // The negation psuedo class (:not) is filtered out because specificity is calculated on its argument | ||
| // :global and :local are filtered out - they look like psuedo classes but are an identifier for CSS Modules | ||
| pseudoClassWithBracketsRegex = /(:(?!not|global|local)[\w-]+\([^\)]*\))/gi, | ||
| // A regex for other pseudo classes, which don't have brackets | ||
| pseudoClassRegex = /(:(?!not|global|local)[^\s\+>~\.\[:]+)/g, | ||
| elementRegex = /([^\s\+>~\.\[:]+)/g; | ||
| // Find matches for a regular expression in a string and push their details to parts | ||
| // Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements | ||
| findMatch = function(regex, type) { | ||
| var matches, i, len, match, index, length; | ||
| if (regex.test(selector)) { | ||
| matches = selector.match(regex); | ||
| for (i = 0, len = matches.length; i < len; i += 1) { | ||
| typeCount[type] += 1; | ||
| match = matches[i]; | ||
| index = selector.indexOf(match); | ||
| length = match.length; | ||
| parts.push({ | ||
| selector: input.substr(index, length), | ||
| type: type, | ||
| index: index, | ||
| length: length | ||
| }); | ||
| // Replace this simple selector with whitespace so it won't be counted in further simple selectors | ||
| selector = selector.replace(match, Array(length + 1).join(' ')); | ||
| } | ||
| } | ||
| }; | ||
| // Replace escaped characters with plain text, using the "A" character | ||
| // https://www.w3.org/TR/CSS21/syndata.html#characters | ||
| (function() { | ||
| var replaceWithPlainText = function(regex) { | ||
| var matches, i, len, match; | ||
| if (regex.test(selector)) { | ||
| matches = selector.match(regex); | ||
| for (i = 0, len = matches.length; i < len; i += 1) { | ||
| match = matches[i]; | ||
| selector = selector.replace(match, Array(match.length + 1).join('A')); | ||
| } | ||
| } | ||
| }, | ||
| // Matches a backslash followed by six hexadecimal digits followed by an optional single whitespace character | ||
| escapeHexadecimalRegex = /\\[0-9A-Fa-f]{6}\s?/g, | ||
| // Matches a backslash followed by fewer than six hexadecimal digits followed by a mandatory single whitespace character | ||
| escapeHexadecimalRegex2 = /\\[0-9A-Fa-f]{1,5}\s/g, | ||
| // Matches a backslash followed by any character | ||
| escapeSpecialCharacter = /\\./g; | ||
| replaceWithPlainText(escapeHexadecimalRegex); | ||
| replaceWithPlainText(escapeHexadecimalRegex2); | ||
| replaceWithPlainText(escapeSpecialCharacter); | ||
| }()); | ||
| // Remove anything after a left brace in case a user has pasted in a rule, not just a selector | ||
| (function() { | ||
| var regex = /{[^]*/gm, | ||
| matches, i, len, match; | ||
| if (regex.test(selector)) { | ||
| matches = selector.match(regex); | ||
| for (i = 0, len = matches.length; i < len; i += 1) { | ||
| match = matches[i]; | ||
| selector = selector.replace(match, Array(match.length + 1).join(' ')); | ||
| } | ||
| } | ||
| }()); | ||
| // Add attribute selectors to parts collection (type b) | ||
| findMatch(attributeRegex, 'b'); | ||
| // Add ID selectors to parts collection (type a) | ||
| findMatch(idRegex, 'a'); | ||
| // Add class selectors to parts collection (type b) | ||
| findMatch(classRegex, 'b'); | ||
| // Add pseudo-element selectors to parts collection (type c) | ||
| findMatch(pseudoElementRegex, 'c'); | ||
| // Add pseudo-class selectors to parts collection (type b) | ||
| findMatch(pseudoClassWithBracketsRegex, 'b'); | ||
| findMatch(pseudoClassRegex, 'b'); | ||
| // Remove universal selector and separator characters | ||
| selector = selector.replace(/[\*\s\+>~]/g, ' '); | ||
| // Remove any stray dots or hashes which aren't attached to words | ||
| // These may be present if the user is live-editing this selector | ||
| selector = selector.replace(/[#\.]/g, ' '); | ||
| // Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument | ||
| // Remove non-standard :local and :global CSS Module identifiers because they do not effect the specificity | ||
| selector = selector.replace(/:not/g, ' '); | ||
| selector = selector.replace(/:local/g, ' '); | ||
| selector = selector.replace(/:global/g, ' '); | ||
| selector = selector.replace(/[\(\)]/g, ' '); | ||
| // The only things left should be element selectors (type c) | ||
| findMatch(elementRegex, 'c'); | ||
| // Order the parts in the order they appear in the original selector | ||
| // This is neater for external apps to deal with | ||
| parts.sort(function(a, b) { | ||
| return a.index - b.index; | ||
| }); | ||
| return { | ||
| selector: input, | ||
| specificity: '0,' + typeCount.a.toString() + ',' + typeCount.b.toString() + ',' + typeCount.c.toString(), | ||
| specificityArray: [0, typeCount.a, typeCount.b, typeCount.c], | ||
| parts: parts | ||
| }; | ||
| }; | ||
| /** | ||
| * Compares two CSS selectors for specificity | ||
| * Alternatively you can replace one of the CSS selectors with a specificity array | ||
| * | ||
| * - it returns -1 if a has a lower specificity than b | ||
| * - it returns 1 if a has a higher specificity than b | ||
| * - it returns 0 if a has the same specificity than b | ||
| */ | ||
| var compare = function(a, b) { | ||
| var aSpecificity, | ||
| bSpecificity, | ||
| i; | ||
| if (typeof a ==='string') { | ||
| if (a.indexOf(',') !== -1) { | ||
| throw 'Invalid CSS selector'; | ||
| } else { | ||
| aSpecificity = calculateSingle(a)['specificityArray']; | ||
| } | ||
| } else if (Array.isArray(a)) { | ||
| if (a.filter(function(e) { return (typeof e === 'number'); }).length !== 4) { | ||
| throw 'Invalid specificity array'; | ||
| } else { | ||
| aSpecificity = a; | ||
| } | ||
| } else { | ||
| throw 'Invalid CSS selector or specificity array'; | ||
| } | ||
| if (typeof b ==='string') { | ||
| if (b.indexOf(',') !== -1) { | ||
| throw 'Invalid CSS selector'; | ||
| } else { | ||
| bSpecificity = calculateSingle(b)['specificityArray']; | ||
| } | ||
| } else if (Array.isArray(b)) { | ||
| if (b.filter(function(e) { return (typeof e === 'number'); }).length !== 4) { | ||
| throw 'Invalid specificity array'; | ||
| } else { | ||
| bSpecificity = b; | ||
| } | ||
| } else { | ||
| throw 'Invalid CSS selector or specificity array'; | ||
| } | ||
| for (i = 0; i < 4; i += 1) { | ||
| if (aSpecificity[i] < bSpecificity[i]) { | ||
| return -1; | ||
| } else if (aSpecificity[i] > bSpecificity[i]) { | ||
| return 1; | ||
| } | ||
| } | ||
| return 0; | ||
| }; | ||
| export { | ||
| calculate, | ||
| compare | ||
| }; |
-115
| import { describe, it } from 'mocha'; | ||
| import assert from 'assert'; | ||
| import { calculate, compare } from '../specificity'; | ||
| describe('calculate', () => { | ||
| [ | ||
| // http://css-tricks.com/specifics-on-css-specificity/ | ||
| { selector: 'ul#nav li.active a', expected: '0,1,1,3' }, | ||
| { selector: 'body.ie7 .col_3 h2 ~ h2', expected: '0,0,2,3' }, | ||
| { selector: '#footer *:not(nav) li', expected: '0,1,0,2' }, | ||
| { selector: 'ul > li ul li ol li:first-letter', expected: '0,0,0,7' }, | ||
| // http://reference.sitepoint.com/css/specificity | ||
| { selector: 'body#home div#warning p.message', expected: '0,2,1,3' }, | ||
| { selector: '* body#home>div#warning p.message', expected: '0,2,1,3' }, | ||
| { selector: '#home #warning p.message', expected: '0,2,1,1' }, | ||
| { selector: '#warning p.message', expected: '0,1,1,1' }, | ||
| { selector: '#warning p', expected: '0,1,0,1' }, | ||
| { selector: 'p.message', expected: '0,0,1,1' }, | ||
| { selector: 'p', expected: '0,0,0,1' }, | ||
| // Test pseudo-element with uppertestCase letters | ||
| { selector: 'li:bEfoRE', expected: '0,0,0,2' }, | ||
| // Pseudo-class tests | ||
| { selector: 'li:first-child+p', expected: '0,0,1,2'}, | ||
| { selector: 'li:nth-child(even)+p', expected: '0,0,1,2'}, | ||
| { selector: 'li:nth-child(2n+1)+p', expected: '0,0,1,2'}, | ||
| { selector: 'li:nth-child( 2n + 1 )+p', expected: '0,0,1,2'}, | ||
| { selector: 'li:nth-child(2n-1)+p', expected: '0,0,1,2'}, | ||
| { selector: 'li:nth-child(2n-1) p', expected: '0,0,1,2'}, | ||
| { selector: ':lang(nl-be)', expected: '0,0,1,0'}, | ||
| // Tests with CSS escape sequences | ||
| // https://mathiasbynens.be/notes/css-escapes and https://mathiasbynens.be/demo/crazy-class | ||
| { selector: '.\\3A -\\)', expected: '0,0,1,0' }, /* <p class=":-)"></p> */ | ||
| { selector: '.\\3A \\`\\(', expected: '0,0,1,0' }, /* <p class=":`("></p> */ | ||
| { selector: '.\\3A .\\`\\(', expected: '0,0,2,0' }, /* <p class=": `("></p> */ | ||
| { selector: '.\\31 a2b3c', expected: '0,0,1,0' }, /* <p class="1a2b3c"></p> */ | ||
| { selector: '.\\000031a2b3c', expected: '0,0,1,0' }, /* <p class="1a2b3c"></p> */ | ||
| { selector: '.\\000031 a2b3c', expected: '0,0,1,0' }, /* <p class="1a2b3c"></p> */ | ||
| { selector: '#\\#fake-id', expected: '0,1,0,0' }, /* <p id="#fake-id"></p> */ | ||
| { selector: '.\\#fake-id', expected: '0,0,1,0' }, /* <p class="#fake-id"></p> */ | ||
| { selector: '#\\<p\\>', expected: '0,1,0,0' }, /* <p id="<p>"></p> */ | ||
| { selector: '.\\#\\.\\#\\.\\#', expected: '0,0,1,0' }, /* <p class="#.#.#"></p> */ | ||
| { selector: '.foo\\.bar', expected: '0,0,1,0' }, /* <p class="foo.bar"></p> */ | ||
| { selector: '.\\:hover\\:active', expected: '0,0,1,0' }, /* <p class=":hover:active"></p> */ | ||
| { selector: '.\\3A hover\\3A active', expected: '0,0,1,0' }, /* <p class=":hover:active"></p> */ | ||
| { selector: '.\\000031 p', expected: '0,0,1,1' }, /* <p class="1"><p></p></p>" */ | ||
| { selector: '.\\3A \\`\\( .another', expected: '0,0,2,0' }, /* <p class=":`("><p class="another"></p></p> */ | ||
| { selector: '.\\--cool', expected: '0,0,1,0' }, /* <p class="--cool"></p> */ | ||
| { selector: '#home .\\[page\\]', expected: '0,1,1,0' }, /* <p id="home"><p class="[page]"></p></p> */ | ||
| // Test repeated IDs | ||
| // https://github.com/keeganstreet/specificity/issues/29 | ||
| { selector: 'ul#nav#nav-main li.active a', expected: '0,2,1,3' }, | ||
| // Test CSS Modules https://github.com/css-modules/css-modules | ||
| // Whilst they are not part of the CSS spec, this calculator can support them without breaking results for standard selectors | ||
| { selector: '.root :global .text', expected: '0,0,2,0' }, | ||
| { selector: '.localA :global .global-b :local(.local-c) .global-d', expected: '0,0,4,0' }, | ||
| { selector: '.localA :global .global-b .global-c :local(.localD.localE) .global-d', expected: '0,0,6,0' }, | ||
| { selector: '.localA :global(.global-b) .local-b', expected: '0,0,3,0' }, | ||
| { selector: ':local(:nth-child(2n) .test)', expected: '0,0,2,0' }, | ||
| ].forEach(testCase => { | ||
| it(`calculate("${testCase.selector}")`, () => { | ||
| const result = calculate(testCase.selector); | ||
| assert.equal(result[0].specificity, testCase.expected); | ||
| }); | ||
| }); | ||
| }); | ||
| describe('compare', () => { | ||
| [ | ||
| { a: 'div', b: 'span', expected: 0 }, | ||
| { a: '.active', b: ':focus', expected: 0 }, | ||
| { a: '#header', b: '#main', expected: 0 }, | ||
| { a: 'div', b: '.active', expected: -1 }, | ||
| { a: 'div', b: '#header', expected: -1 }, | ||
| { a: '.active', b: '#header', expected: -1 }, | ||
| { a: '.active', b: 'div', expected: 1 }, | ||
| { a: '#main', b: 'div', expected: 1 }, | ||
| { a: '#main', b: ':focus', expected: 1 }, | ||
| { a: 'div p', b: 'span a', expected: 0 }, | ||
| { a: '#main p .active', b: '#main span :focus', expected: 0 }, | ||
| { a: [0, 1, 1, 1], b: '#main span :focus', expected: 0 }, | ||
| { a: '#main p .active', b: [0, 1, 1, 1], expected: 0 }, | ||
| { a: ':focus', b: 'span a', expected: 1 }, | ||
| { a: '#main', b: 'span a:hover', expected: 1 }, | ||
| { a: 'ul > li > a > span:before', b: '.active', expected: -1 }, | ||
| { a: 'a.active:hover', b: '#main', expected: -1 }, | ||
| ].forEach(testCase => { | ||
| it(`compare("${testCase.a}", "${testCase.b}")`, () => { | ||
| const result = compare(testCase.a, testCase.b); | ||
| assert.equal(result, testCase.expected); | ||
| }); | ||
| }); | ||
| }); | ||
| describe('sorting with compare', () => { | ||
| const a = 'div'; | ||
| const b = 'p a'; | ||
| const c = '.active'; | ||
| const d = 'p.active'; | ||
| const e = '.active:focus'; | ||
| const f = '#main'; | ||
| const original = [c, f, a, e, b, d]; | ||
| const sorted = [a, b, c, d, e, f]; | ||
| const result = original.sort(compare); | ||
| it('Array.sort(compare) should sort the array by specificity', () => { | ||
| assert.equal(result.join('|'), sorted.join('|')); | ||
| }); | ||
| }); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
28
180%0
-100%Yes
NaN34067
-9.18%1
Infinity%6
100%393
-48.22%79
-55.37%1
Infinity%+ Added
+ Added
+ Added
+ Added