Skip to content

Commit 91079d6

Browse files
author
Jacob Parker
committed
.
1 parent da43dd2 commit 91079d6

File tree

8 files changed

+920
-138
lines changed

8 files changed

+920
-138
lines changed

.eslintrc.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
11
module.exports = {
2-
"extends": "airbnb"
3-
};
2+
"env": {
3+
"commonjs": true,
4+
"es6": true,
5+
"node": true
6+
},
7+
"extends": "eslint:recommended",
8+
"globals": {
9+
"Atomics": "readonly",
10+
"SharedArrayBuffer": "readonly"
11+
},
12+
"parserOptions": {
13+
"ecmaVersion": 2018
14+
},
15+
"rules": {
16+
}
17+
};

README.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,35 @@
11
# css-class-generator
22

3-
Generates a sequential, valid CSS class, generating the next smallest class names possible.
3+
Maps a given index to a unique valid CSS. Uses the smallest class names possible, with lower indices having smaller class names.
44

55
```bash
66
npm install --save css-class-generator
77
```
88

99
# API
1010

11-
`cssNameGenerator(prefix = '')` -> iterator
11+
`cssClassGenerator(index, prefix = '')` -> string
1212

13-
Will throw if prefix is not a valid class name (unless the prefix is `-`). The exception will be thrown when the first value is being yielded.
13+
Class names are not random - a given index will always return the same class name.
1414

1515
Class names are generated without a leading `.`.
1616

17-
It uses generators, so you'll have to be using a version of node that supports this.
17+
The prefix option gives more optimal class names than `prefix + cssClassGenerator(index)`. It never returns the prefix alone - it's always appended with something. In development, it will `console.warn` if prefix is not a valid class name (unless the prefix is `-`).
18+
19+
Be careful for high values (`2 ** 30` and above), as JavaScript integer vs float quirks can happen.
1820

1921
# Example
2022

2123
```js
22-
const cssNameGenerator = require('css-class-generator');
23-
for (let value of cssNameGenerator()) {
24-
// 'A', 'B', ...
25-
}
26-
for (let value of cssNameGenerator('-')) {
27-
// '-A', '-B', ...
28-
}
29-
for (let value of cssNameGenerator('custom-namespace-')) {
30-
// You get the idea...
31-
}
24+
const cssClassGenerator = require("css-class-generator");
25+
26+
cssClassGenerator(0); // 'a'
27+
cssClassGenerator(1); // 'b'
28+
cssClassGenerator(52); // '_'
29+
cssClassGenerator(53); // '-a'
30+
cssClassGenerator(10000); // K3c
31+
cssClassGenerator(1e9); // CVJ2gb
32+
33+
cssClassGenerator(0, "hello"); // 'helloa'
34+
cssClassGenerator(1, "hello"); // 'hellob'
3235
```

index.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const chars =
2+
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789-";
3+
const numChars = 64; // chars.length
4+
const validFirstChars = 53; // chars.indexOf("_") + 1
5+
const twoCharPermutations = validFirstChars + validFirstChars * numChars;
6+
7+
module.exports = (index, prefix) => {
8+
if (process.env.NODE_ENV !== "production") {
9+
const validIdent = /^-?[_a-z][_a-z0-9-]*$/i;
10+
if (prefix != null && prefix !== "-" && !validIdent.test(prefix)) {
11+
// eslint-disable-next-line
12+
console.warn(`Expected prefix (${prefix}) to be a valid css class name`);
13+
}
14+
}
15+
16+
let i;
17+
let current;
18+
if (prefix == null || prefix === "") {
19+
if (index < validFirstChars) return chars[index];
20+
21+
const _index = index - validFirstChars;
22+
const c = _index % twoCharPermutations;
23+
if (c < validFirstChars) {
24+
current = "-" + chars[c];
25+
} else {
26+
const _c = c - validFirstChars;
27+
const c0 = _c % validFirstChars;
28+
const c1 = (_c / validFirstChars) | 0;
29+
current = chars[c0] + chars[c1];
30+
}
31+
i = (_index / twoCharPermutations) | 0;
32+
} else if (prefix === "-") {
33+
const c = index % validFirstChars;
34+
current = "-" + chars[c];
35+
i = (index / validFirstChars) | 0;
36+
} else {
37+
if (index === 0) return prefix + chars[0];
38+
39+
current = prefix;
40+
i = index;
41+
}
42+
43+
let base = numChars;
44+
while (i > 0) {
45+
const c = i % numChars;
46+
current += chars[c];
47+
i = (i / numChars) | 0;
48+
base = base * numChars;
49+
}
50+
51+
return current;
52+
};

package.json

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
{
22
"name": "css-class-generator",
3-
"version": "1.0.1",
3+
"version": "2.0.0",
44
"description": "Generates a sequential, valid CSS class, generating the next smallest class names possible",
5-
"main": "src/index.js",
5+
"main": "index.js",
66
"dependencies": {},
77
"devDependencies": {
8-
"ava": "^0.14.0",
9-
"eslint": "^2.7.0",
10-
"eslint-config-airbnb-base": "^1.0.3",
11-
"eslint-plugin-import": "^1.5.0"
12-
},
13-
"scripts": {
14-
"test": "ava -T 60s"
8+
"eslint": "^5.16.0",
9+
"prettier": "^1.17.0"
1510
},
1611
"keywords": [
1712
"css",

src/index.js

Lines changed: 0 additions & 71 deletions
This file was deleted.

test.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
const assert = require("assert");
2+
const generate = require(".");
3+
4+
const runTests = ({ num = 1e4, prefix } = {}) => {
5+
const validIdent = /^-?[_a-z][_a-z0-9-]*$/i;
6+
let last = "";
7+
const seen = new Set();
8+
const changes = [];
9+
10+
for (let i = 0; i < num; i += 1) {
11+
const next = generate(i, prefix);
12+
13+
assert(
14+
prefix == null || next.startsWith(prefix),
15+
`Expected ${next} to start with ${prefix}`
16+
);
17+
18+
assert(validIdent.test(next), `Got invalid identifier for ${next} (${i})`);
19+
assert(!next.includes("undefined"), `Got undefined for ${i}`);
20+
21+
const nextLength = next.length;
22+
const lastLength = last.length;
23+
assert(
24+
nextLength >= lastLength,
25+
`Invalid ordering for ${last} vs ${next} (${i})`
26+
);
27+
28+
const hasSeen = seen.has(next);
29+
const seenIndex = hasSeen ? Array.from(seen).indexOf(next) : -1;
30+
assert(!hasSeen, `Already seen ${next} (${seenIndex} vs. ${i})`);
31+
32+
last = next;
33+
if (i !== 0 && nextLength > lastLength) {
34+
changes.push(i);
35+
}
36+
seen.add(next);
37+
}
38+
39+
return [
40+
`Passed tests with ${prefix ? `prefix "${prefix}"` : "no prefix"}`,
41+
` Tested ${num} iterations`,
42+
` Length transitions at: ${changes.join(", ")}`
43+
].join("\n");
44+
};
45+
46+
assert.equal(generate(0), "a");
47+
assert.equal(generate(1), "b");
48+
49+
assert.equal(generate(0, "-"), "-a");
50+
assert.equal(generate(1, "-"), "-b");
51+
52+
assert.equal(generate(0, "test"), "testa");
53+
assert.equal(generate(1, "test"), "testb");
54+
55+
runTests({ num: 1e6 });
56+
runTests({ num: 1e6, prefix: "-" });
57+
runTests({ num: 1e6, prefix: "hello" });

test/index.js

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)