Socket
Socket
Sign inDemoInstall

specificity

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

specificity - npm Package Compare versions

Comparing version 0.3.2 to 0.4.0

dist/specificity.js

12

package.json
{
"name": "specificity",
"version": "0.3.2",
"version": "0.4.0",
"description": "Calculate the specificity of a CSS selector",

@@ -22,3 +22,4 @@ "keywords": [

},
"main": "specificity",
"main": "dist/specificity",
"module": "dist/specificity.mjs",
"bin": {

@@ -28,8 +29,11 @@ "specificity": "./bin/specificity"

"scripts": {
"test": "mocha test/test.js"
"prepare": "rollup --config",
"test": "mocha test/test.js --require esm"
},
"devDependencies": {
"mocha": "2.5.x"
"esm": "^3.0.71",
"mocha": "^5.2.0",
"rollup": "^0.62.0"
},
"types": "specificity.d.ts"
}
# Specificity Calculator
A JavaScript module for calculating and comparing the [specificity of CSS selectors](http://www.w3.org/TR/css3-selectors/#specificity). The module is used on the [Specificity Calculator](http://specificity.keegan.st/) website.
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.
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](http://www.w3.org/TR/css3-selectors/#negation) may only take a simple selector as an argument. Using a psuedo-element or combinator as an argument for `:not()` is invalid CSS3 so Specificity Calculator will return incorrect results.
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.
## Supported runtime environments
## Front-end usage
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:
**Browser**
* Directly loaded ES module
* ES module in a precompiled script (using a bundler like Webpack or Rollup)
* Global variable
**Node.js**
* ES module
* CommonJS module
### 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
SPECIFICITY.calculate('ul#nav li.active a'); // [{ specificity: '0,1,1,3' }]
import { calculate } from 'specificity';
calculate('ul#nav li.active a');
```
## Node.js usage
### Browser usage as a global variable
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
var specificity = require('specificity');
specificity.calculate('ul#nav li.active a'); // [{ specificity: '0,1,1,3' }]
import { calculate } from 'specificity';
calculate('ul#nav li.active a');
```
## Passing in multiple selectors
### Node.js usage as a CommonJS module
You can use comma separation to pass in multiple selectors:
Otherwise, Node.js will use the UMD artefact, which contains a CommonJS module definition.
```js
SPECIFICITY.calculate('ul#nav li.active a, body.ie7 .col_3 h2 ~ h2'); // [{ specificity: '0,1,1,3' }, { specificity: '0,0,2,3' }]
const { calculate } = require('specificity');
calculate('ul#nav li.active a');
```
## Return values
## Calculate function
The `specificity.calculate` function returns an array containing a result object for each selector input. Each result object has the following properties:
The `calculate` function returns an array containing a result object for each selector input. Each result object has the following properties:

@@ -41,9 +88,7 @@ * `selector`: the input

```js
var specificity = require('../'),
result = specificity.calculate('ul#nav li.active a');
calculate('ul#nav li.active a');
console.log(result);
/* result =
[ {
/*
[
{
selector: 'ul#nav li.active a',

@@ -59,9 +104,31 @@ specificity: '0,1,1,3',

]
} ]
}
]
*/
```
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',
...
}
]
*/
```
## Comparing two selectors
Specificity Calculator also exposes a `compare` function. This function accepts two CSS selectors or specificity arrays, `a` and `b`.
Specificity Calculator also exports a `compare` function. This function accepts two CSS selectors or specificity arrays, `a` and `b`.

@@ -73,7 +140,7 @@ * It returns `-1` if `a` has a lower specificity than `b`

```js
SPECIFICITY.compare('div', '.active'); // -1
SPECIFICITY.compare('#main', 'div'); // 1
SPECIFICITY.compare('span', 'div'); // 0
SPECIFICITY.compare('span', [0,0,0,1]); // 0
SPECIFICITY.compare('#main > div', [0,1,0,1]); // 0
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
```

@@ -83,6 +150,8 @@

You can pass the `SPECIFICITY.compare` function to `Array.prototype.sort` to sort an array of CSS selectors by specificity.
You can pass the `compare` function to `Array.prototype.sort` to sort an array of CSS selectors by specificity.
```js
['#main', 'p', '.active'].sort(SPECIFICITY.compare); // ['p', '.active', '#main']
import { compare } from 'specificity';
['#main', 'p', '.active'].sort(compare); // ['p', '.active', '#main']
```

@@ -92,3 +161,3 @@

Run `npm install specificity` to install the module locally, or `npm install -g specificity` for global installation. You may need to elevate permissions by `sudo` for the latter. Run `specificity` without arguments to learn about its 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:

@@ -95,0 +164,0 @@ ```bash

@@ -1,56 +0,53 @@

declare module "specificity" {
export = specificity;
namespace 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.
*/
type SpecificityArray = [number, number, number, number];
/**
* 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
*/
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
*/
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.
*/
function calculate(selector: string): Array<Specificity>;
/**
* 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;
}
/**
* 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.
*/
function compare(a: string | SpecificityArray, b: string | SpecificityArray): -1 | 0 | 1;
}
}
/**
* 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;

@@ -1,232 +0,220 @@

var SPECIFICITY = (function() {
var calculate,
calculateSingle,
compare;
// 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 = [];
// Calculate the specificity for a selector by dividing it into simple selectors and counting them
calculate = function(input) {
var selectors,
selector,
i,
len,
results = [];
// Separate input by commas
selectors = input.split(',');
// 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));
}
for (i = 0, len = selectors.length; i < len; i += 1) {
selector = selectors[i];
if (selector.length > 0) {
results.push(calculateSingle(selector));
}
}
return results;
};
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]
*/
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()
pseudoClassWithBracketsRegex = /(:[\w-]+\([^\)]*\))/gi,
// A regex for other pseudo classes, which don't have brackets
pseudoClassRegex = /(:[^\s\+>~\.\[:]+)/g,
elementRegex = /([^\s\+>~\.\[:]+)/g;
/**
* 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()
pseudoClassWithBracketsRegex = /(:[\w-]+\([^\)]*\))/gi,
// A regex for other pseudo classes, which don't have brackets
pseudoClassRegex = /(:[^\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(' '));
}
// 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'));
}
// 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;
}
},
// 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);
}());
replaceWithPlainText(escapeHexadecimalRegex);
replaceWithPlainText(escapeHexadecimalRegex2);
replaceWithPlainText(escapeSpecialCharacter);
}());
// Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument
(function() {
var regex = /:not\(([^\)]*)\)/g;
if (regex.test(selector)) {
selector = selector.replace(regex, ' $1 ');
}
}());
// Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument
(function() {
var regex = /:not\(([^\)]*)\)/g;
if (regex.test(selector)) {
selector = selector.replace(regex, ' $1 ');
}
}());
// 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(' '));
}
// 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 attribute selectors to parts collection (type b)
findMatch(attributeRegex, 'b');
// Add ID selectors to parts collection (type a)
findMatch(idRegex, 'a');
// Add ID selectors to parts collection (type a)
findMatch(idRegex, 'a');
// Add class selectors to parts collection (type b)
findMatch(classRegex, 'b');
// 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-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');
// 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 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 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, ' ');
// The only things left should be element selectors (type c)
findMatch(elementRegex, 'c');
// 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;
});
// 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
};
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
*/
compare = function(a, b) {
var aSpecificity,
bSpecificity,
i;
/**
* 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;
}
if (typeof a ==='string') {
if (a.indexOf(',') !== -1) {
throw 'Invalid CSS selector';
} else {
throw 'Invalid CSS selector or specificity array';
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;
}
if (typeof b ==='string') {
if (b.indexOf(',') !== -1) {
throw 'Invalid CSS selector';
} else {
throw 'Invalid CSS selector or specificity array';
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;
}
for (i = 0; i < 4; i += 1) {
if (aSpecificity[i] < bSpecificity[i]) {
return -1;
} else if (aSpecificity[i] > bSpecificity[i]) {
return 1;
}
}
return 0;
};
return 0;
};
return {
calculate: calculate,
compare: compare
};
}());
// Export for Node JS
if (typeof exports !== 'undefined') {
exports.calculate = SPECIFICITY.calculate;
exports.compare = SPECIFICITY.compare;
}
export {
calculate,
compare
};

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc