├── .npmignore
├── .travis.yml
├── benchmarks
├── benchmarks.html
├── package.json
├── runChecks.js
├── fixtures.js
├── runSuite.js
├── run.js
└── runInBrowser.js
├── .editorconfig
├── .gitignore
├── bower.json
├── package.json
├── CONTRIBUTING.md
├── LICENSE
├── bind.js
├── index.js
├── HISTORY.md
├── tests
├── dedupe.js
├── index.js
└── bind.js
├── dedupe.js
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | /.*/
2 | /.*
3 | /benchmarks/
4 | /tests/
5 | /bower.json
6 | /CONTRIBUTING.md
7 | /HISTORY.md
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | before_install:
3 | - "npm install npm -g"
4 | node_js:
5 | - "stable"
6 | - "4"
7 | - "5"
8 | - "6"
9 | - "0.10"
10 | - "0.12"
11 |
--------------------------------------------------------------------------------
/benchmarks/benchmarks.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Benchmarks
6 |
7 |
8 |
9 | Wait please…
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 | root = true
4 |
5 | [*]
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = false
9 | insert_final_newline = true
10 | indent_style = tab
11 |
12 | [*.json]
13 | indent_style = space
14 | indent_size = 2
15 |
--------------------------------------------------------------------------------
/benchmarks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "classnames-benchmarks",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "main": "run.js",
7 | "scripts": {
8 | "test": "echo \"Tests should be run in the main classnames package.\" && exit 1"
9 | },
10 | "author": "Jed Watson",
11 | "license": "MIT",
12 | "devDependencies": {
13 | "benchmark": "^1.0.0",
14 | "classnames": "*"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory
14 | coverage
15 |
16 | # Compiled binary addons (http://nodejs.org/api/addons.html)
17 | build/Release
18 |
19 | # Dependency directory
20 | node_modules
21 |
22 | # Users Environment Variables
23 | .lock-wscript
24 |
25 | # Mac OS X DS_Store
26 | .DS_Store
27 |
28 | benchmarks/runInBrowser.bundle.js
29 |
--------------------------------------------------------------------------------
/benchmarks/runChecks.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 |
3 | function sortClasses (str) {
4 | return str.split(' ').sort().join(' ');
5 | }
6 |
7 | function runChecks (local, npm, dedupe, npmDedupe, fixture) {
8 | // sort assertions because dedupe returns results in a different order
9 | assert.equal(sortClasses(local.apply(null, fixture.args)), sortClasses(fixture.expected));
10 | assert.equal(sortClasses(dedupe.apply(null, fixture.args)), sortClasses(fixture.expected));
11 | assert.equal(sortClasses(npm.apply(null, fixture.args)), sortClasses(fixture.expected));
12 | assert.equal(sortClasses(npmDedupe.apply(null, fixture.args)), sortClasses(fixture.expected));
13 | }
14 |
15 | module.exports = runChecks;
16 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "classnames",
3 | "version": "2.2.5",
4 | "description": "A simple utility for conditionally joining classNames together",
5 | "main": [
6 | "index.js",
7 | "bind.js",
8 | "dedupe.js"
9 | ],
10 | "homepage": "https://github.com/JedWatson/classnames",
11 | "authors": [
12 | "Jed Watson"
13 | ],
14 | "moduleType": [
15 | "amd",
16 | "globals",
17 | "node"
18 | ],
19 | "keywords": [
20 | "react",
21 | "css",
22 | "classes",
23 | "classname",
24 | "classnames",
25 | "util",
26 | "utility"
27 | ],
28 | "license": "MIT",
29 | "ignore": [
30 | ".editorconfig",
31 | ".gitignore",
32 | "gulpfile.js",
33 | "package.json",
34 | "node_modules",
35 | "tests.js"
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/benchmarks/fixtures.js:
--------------------------------------------------------------------------------
1 | var fixtures = [
2 | {
3 | description: 'strings',
4 | args: ['one', 'two', 'three'],
5 | expected: 'one two three'
6 | },
7 | {
8 | description: 'object',
9 | args: [{one: true, two: true, three: false}],
10 | expected: 'one two'
11 | },
12 | {
13 | description: 'strings, object',
14 | args: ['one', 'two', {four: true, three: false}],
15 | expected: 'one two four'
16 | },
17 | {
18 | description: 'mix',
19 | args: ['one', {two: true, three: false}, {four: 'four', five: true}, 6, {}],
20 | expected: 'one two four five 6'
21 | },
22 | {
23 | description: 'arrays',
24 | args: [['one', 'two'], ['three'], ['four', ['five']], [{six: true}, {seven: false}]],
25 | expected: 'one two three four five six'
26 | }
27 | ];
28 |
29 | module.exports = fixtures;
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "classnames",
3 | "version": "2.2.5",
4 | "description": "A simple utility for conditionally joining classNames together",
5 | "main": "index.js",
6 | "author": "Jed Watson",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/JedWatson/classnames.git"
11 | },
12 | "scripts": {
13 | "benchmarks": "node ./benchmarks/run",
14 | "benchmarks-browserify": "./node_modules/.bin/browserify ./benchmarks/runInBrowser.js >./benchmarks/runInBrowser.bundle.js",
15 | "benchmarks-in-browser": "./node_modules/.bin/opn ./benchmarks/benchmarks.html",
16 | "test": "mocha tests/*.js"
17 | },
18 | "keywords": [
19 | "react",
20 | "css",
21 | "classes",
22 | "classname",
23 | "classnames",
24 | "util",
25 | "utility"
26 | ],
27 | "devDependencies": {
28 | "benchmark": "^1.0.0",
29 | "browserify": "^14.1.0",
30 | "mocha": "^2.1.0",
31 | "opn-cli": "^3.1.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thanks for your interest in classNames. Issues, PRs and suggestions welcome :)
4 |
5 | Before working on a PR, please consider the following:
6 |
7 | * Speed is a serious concern for this package as it is likely to be called a
8 | significant number of times in any project that uses it. As such, new features
9 | will only be accepted if they improve (or at least do not negatively impact)
10 | performance.
11 | * To demonstrate performance differences please set up a
12 | [JSPerf](http://jsperf.com) test and link to it from your issue / PR.
13 | * Tests must be added for any change or new feature before it will be accepted.
14 |
15 | A benchmark utilitiy is included so that changes may be tested against the
16 | current published version. To run the benchmarks, `npm install` in the
17 | `./benchmarks` directory then run `npm run benchmarks` in the package root.
18 |
19 | Please be aware though that local benchmarks are just a smoke-signal; they will
20 | run in the v8 version that your node/iojs uses, while classNames is _most_
21 | often run across a wide variety of browsers and browser versions.
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Jed Watson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/benchmarks/runSuite.js:
--------------------------------------------------------------------------------
1 | var benchmark = require('benchmark');
2 |
3 | function runSuite (local, npm, dedupe, npmDedupe, fixture, log) {
4 | var suite = new benchmark.Suite();
5 |
6 | suite.add('local#' + fixture.description, function () {
7 | local.apply(null, fixture.args);
8 | });
9 |
10 | suite.add(' npm#' + fixture.description, function () {
11 | npm.apply(null, fixture.args);
12 | });
13 |
14 | suite.add('local/dedupe#' + fixture.description, function () {
15 | dedupe.apply(null, fixture.args);
16 | });
17 |
18 | suite.add(' npm/dedupe#' + fixture.description, function () {
19 | npmDedupe.apply(null, fixture.args);
20 | });
21 |
22 | // after each cycle
23 | suite.on('cycle', function (event) {
24 | log('* ' + String(event.target));
25 | });
26 |
27 | // other handling
28 | suite.on('complete', function () {
29 | log('\n> Fastest is' + (' ' + this.filter('fastest').pluck('name').join(' | ')).replace(/\s+/, ' ') + '\n');
30 | });
31 |
32 | suite.on('error', function (event) {
33 | log(event.target.error.message);
34 | throw event.target.error;
35 | });
36 |
37 | suite.run();
38 | }
39 |
40 | module.exports = runSuite;
41 |
--------------------------------------------------------------------------------
/benchmarks/run.js:
--------------------------------------------------------------------------------
1 | var fixtures = require('./fixtures');
2 | var local = require('../');
3 | var dedupe = require('../dedupe');
4 | var localPackage = require('../package.json');
5 |
6 | function log (message) {
7 | console.log(message);
8 | }
9 |
10 | try {
11 | var npm = require('classnames');
12 | var npmDedupe = require('classnames/dedupe');
13 | var npmPackage = require('./node_modules/classnames/package.json');
14 | } catch (e) {
15 | log('There was an error loading the benchmark classnames package.\n' +
16 | 'Please make sure you have run `npm install` in ./benchmarks\n');
17 | process.exit(0);
18 | }
19 |
20 | if (localPackage.version !== npmPackage.version) {
21 | log('Your local version (' + localPackage.version + ') does not match the installed version (' + npmPackage.version + ')\n\n' +
22 | 'Please run `npm update` in ./benchmarks to ensure you are benchmarking\n' +
23 | 'the latest version of this package.\n');
24 | process.exit(0);
25 | }
26 |
27 | var runChecks = require('./runChecks');
28 | var runSuite = require('./runSuite');
29 |
30 | fixtures.forEach(function (f) {
31 | runChecks(local, npm, dedupe, npmDedupe, f);
32 | runSuite(local, npm, dedupe, npmDedupe, f, log);
33 | });
34 |
--------------------------------------------------------------------------------
/bind.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Copyright (c) 2017 Jed Watson.
3 | Licensed under the MIT License (MIT), see
4 | http://jedwatson.github.io/classnames
5 | */
6 | /* global define */
7 |
8 | (function () {
9 | 'use strict';
10 |
11 | var hasOwn = {}.hasOwnProperty;
12 |
13 | function classNames () {
14 | var classes = [];
15 |
16 | for (var i = 0; i < arguments.length; i++) {
17 | var arg = arguments[i];
18 | if (!arg) continue;
19 |
20 | var argType = typeof arg;
21 |
22 | if (argType === 'string' || argType === 'number') {
23 | classes.push(this && this[arg] || arg);
24 | } else if (Array.isArray(arg)) {
25 | classes.push(classNames.apply(this, arg));
26 | } else if (argType === 'object') {
27 | for (var key in arg) {
28 | if (hasOwn.call(arg, key) && arg[key]) {
29 | classes.push(this && this[key] || key);
30 | }
31 | }
32 | }
33 | }
34 |
35 | return classes.join(' ');
36 | }
37 |
38 | if (typeof module !== 'undefined' && module.exports) {
39 | module.exports = classNames;
40 | } else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
41 | // register as 'classnames', consistent with npm package name
42 | define('classnames', [], function () {
43 | return classNames;
44 | });
45 | } else {
46 | window.classNames = classNames;
47 | }
48 | }());
49 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Copyright (c) 2017 Jed Watson.
3 | Licensed under the MIT License (MIT), see
4 | http://jedwatson.github.io/classnames
5 | */
6 | /* global define */
7 |
8 | (function () {
9 | 'use strict';
10 |
11 | var hasOwn = {}.hasOwnProperty;
12 |
13 | function classNames () {
14 | var classes = [];
15 |
16 | for (var i = 0; i < arguments.length; i++) {
17 | var arg = arguments[i];
18 | if (!arg) continue;
19 |
20 | var argType = typeof arg;
21 |
22 | if (argType === 'string' || argType === 'number') {
23 | classes.push(arg);
24 | } else if (Array.isArray(arg) && arg.length) {
25 | var inner = classNames.apply(null, arg);
26 | if (inner) {
27 | classes.push(inner);
28 | }
29 | } else if (argType === 'object') {
30 | if (hasOwn.call(arg, 'toString') && typeof arg.toString === 'function') {
31 | classes.push(arg.toString());
32 | } else {
33 | for (var key in arg) {
34 | if (hasOwn.call(arg, key) && arg[key]) {
35 | classes.push(key);
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
42 | return classes.join(' ');
43 | }
44 |
45 | if (typeof module !== 'undefined' && module.exports) {
46 | classNames.default = classNames;
47 | module.exports = classNames;
48 | } else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
49 | // register as 'classnames', consistent with npm package name
50 | define('classnames', [], function () {
51 | return classNames;
52 | });
53 | } else {
54 | window.classNames = classNames;
55 | }
56 | }());
57 |
--------------------------------------------------------------------------------
/benchmarks/runInBrowser.js:
--------------------------------------------------------------------------------
1 | var fixtures = require('./fixtures');
2 | var local = require('../');
3 | var dedupe = require('../dedupe');
4 | var localPackage = require('../package.json');
5 |
6 | var npm = require('classnames');
7 | var npmDedupe = require('classnames/dedupe');
8 | var npmPackage = require('./node_modules/classnames/package.json');
9 |
10 | function log (message) {
11 | console.log(message);
12 | var results = document.getElementById('results');
13 | //noinspection InnerHTMLJS
14 | results.innerHTML += (message + '\n').replace(/\n/g, '
');
15 | }
16 |
17 | if (localPackage.version !== npmPackage.version) {
18 | log('Your local version (' + localPackage.version + ') does not match the installed version (' + npmPackage.version + ')\n\n' +
19 | 'Please run `npm update` in ./benchmarks to ensure you are benchmarking\n' +
20 | 'the latest version of this package.\n');
21 | return;
22 | }
23 |
24 | function iterate (array, iterator, i, callback) {
25 | if (i >= 0 && i < array.length) {
26 | iterator(array[i], i, array);
27 | setTimeout(iterate.bind(null, array, iterator, i + 1, callback), 1);
28 | } else if (callback) {
29 | callback();
30 | }
31 | }
32 |
33 | function deferredForEach (array, iterator, callback) {
34 | iterate(array, iterator, 0, callback);
35 | }
36 |
37 | var runSuite = require('./runSuite');
38 |
39 | window.onload = function () {
40 | //noinspection PlatformDetectionJS
41 | log(navigator.userAgent);
42 | setTimeout(function () {
43 | deferredForEach(fixtures, function (f) {
44 | runSuite(local, npm, dedupe, npmDedupe, f, log);
45 | }, function () {
46 | log('Finished');
47 | document.getElementById('loader').style.display = 'none';
48 | });
49 | }, 100);
50 | };
51 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## v2.2.5 / 2016-05-02
4 |
5 | * Improved performance of `dedupe` variant even further, thanks [Andres Suarez](https://github.com/zertosh)
6 |
7 | ## v2.2.4 / 2016-04-25
8 |
9 | * Improved performance of `dedupe` variant by about 2x, thanks [Bartosz Gościński](https://github.com/bgoscinski)
10 |
11 | ## v2.2.3 / 2016-01-05
12 |
13 | * Updated `bind` variant to use `[].join(' ')` as per the main script in 2.2.2
14 |
15 | ## v2.2.2 / 2016-01-04
16 |
17 | * Switched from string concatenation to `[].join(' ')` for a slight performance gain in the main function.
18 |
19 | ## v2.2.1 / 2015-11-26
20 |
21 | * Add deps parameter to the AMD module, fixes an issue using the Dojo loader, thanks [Chris Jordan](https://github.com/flipperkid)
22 |
23 | ## v2.2.0 / 2015-10-18
24 |
25 | * added a new `bind` variant for use with [css-modules](https://github.com/css-modules/css-modules) and similar abstractions, thanks to [Kirill Yakovenko](https://github.com/blia)
26 |
27 | ## v2.1.5 / 2015-09-30
28 |
29 | * reverted a new usage of `Object.keys` in `dedupe.js` that slipped through in the last release
30 |
31 | ## v2.1.4 / 2015-09-30
32 |
33 | * new case added to benchmarks
34 | * safer `hasOwnProperty` check
35 | * AMD module is now named, so you can do the following:
36 |
37 | ```
38 | define(["classnames"], function (classNames) {
39 | var style = classNames("foo", "bar");
40 | // ...
41 | });
42 | ```
43 |
44 | ## v2.1.3 / 2015-07-02
45 |
46 | * updated UMD wrapper to support AMD and CommonJS on the same pacge
47 |
48 | ## v2.1.2 / 2015-05-28
49 |
50 | * added a proper UMD wrapper
51 |
52 | ## v2.1.1 / 2015-05-06
53 |
54 | * minor performance improvement thanks to type caching
55 | * improved benchmarking and results output
56 |
57 | ## v2.1.0 / 2015-05-05
58 |
59 | * added alternate `dedupe` version of classNames, which is slower (10x) but ensures that if a class is added then overridden by a falsy value in a subsequent argument, it is excluded from the result.
60 |
61 | ## v2.0.0 / 2015-05-03
62 |
63 | * performance improvement; switched to `Array.isArray` for type detection, which is much faster in modern browsers. A polyfill is now required for IE8 support, see the Readme for details.
64 |
65 | ## v1.2.2 / 2015-04-28
66 |
67 | * license comment updates to simiplify certain build scenarios
68 |
69 | ## v1.2.1 / 2015-04-22
70 |
71 | * added safe exporting for requireJS usage
72 | * clarified Bower usage and instructions
73 |
74 | ## v1.2.0 / 2015-03-17
75 |
76 | * added comprehensive support for array arguments, including nested arrays
77 | * simplified code slightly
78 |
79 | ## Previous
80 |
81 | Please see the git history for the details of previous versions.
82 |
--------------------------------------------------------------------------------
/tests/dedupe.js:
--------------------------------------------------------------------------------
1 | /* global describe, it */
2 |
3 | var assert = require('assert');
4 | var dedupe = require('../dedupe');
5 |
6 | describe('dedupe', function () {
7 | it('keeps object keys with truthy values', function () {
8 | assert.equal(dedupe({
9 | a: true,
10 | b: false,
11 | c: 0,
12 | d: null,
13 | e: undefined,
14 | f: 1
15 | }), 'a f');
16 | });
17 |
18 | it('should dedupe dedupe', function () {
19 | assert.equal(dedupe('foo', 'bar', 'foo', 'bar', { foo: true }), 'foo bar');
20 | });
21 |
22 | it('should make sure subsequent objects can remove/add classes', function () {
23 | assert.equal(dedupe('foo', { foo: false }, { foo: true, bar: true }), 'foo bar');
24 | });
25 |
26 | it('should make sure object with falsy value wipe out previous classes', function () {
27 | assert.equal(dedupe('foo foo', 0, null, undefined, true, 1, 'b', { 'foo': false }), '1 b');
28 | assert.equal(dedupe('foo', 'foobar', 'bar', { foo: false }), 'foobar bar');
29 | assert.equal(dedupe('foo', 'foo-bar', 'bar', { foo: false }), 'foo-bar bar');
30 | assert.equal(dedupe('foo', '-moz-foo-bar', 'bar', { foo: false }), '-moz-foo-bar bar');
31 | });
32 |
33 | it('joins arrays of class names and ignore falsy values', function () {
34 | assert.equal(dedupe('a', 0, null, undefined, true, 1, 'b'), '1 a b');
35 | });
36 |
37 | it('supports heterogenous arguments', function () {
38 | assert.equal(dedupe({a: true}, 'b', 0), 'a b');
39 | });
40 |
41 | it('should be trimmed', function () {
42 | assert.equal(dedupe('', 'b', {}, ''), 'b');
43 | });
44 |
45 | it('returns an empty string for an empty configuration', function () {
46 | assert.equal(dedupe({}), '');
47 | });
48 |
49 | it('supports an array of class names', function () {
50 | assert.equal(dedupe(['a', 'b']), 'a b');
51 | });
52 |
53 | it('joins array arguments with string arguments', function () {
54 | assert.equal(dedupe(['a', 'b'], 'c'), 'a b c');
55 | assert.equal(dedupe('c', ['a', 'b']), 'c a b');
56 | });
57 |
58 | it('handles multiple array arguments', function () {
59 | assert.equal(dedupe(['a', 'b'], ['c', 'd']), 'a b c d');
60 | });
61 |
62 | it('handles arrays that include falsy and true values', function () {
63 | assert.equal(dedupe(['a', 0, null, undefined, false, true, 'b']), 'a b');
64 | });
65 |
66 | it('handles arrays that include arrays', function () {
67 | assert.equal(dedupe(['a', ['b', 'c']]), 'a b c');
68 | });
69 |
70 | it('handles arrays that include objects', function () {
71 | assert.equal(dedupe(['a', {b: true, c: false}]), 'a b');
72 | });
73 |
74 | it('handles deep array recursion', function () {
75 | assert.equal(dedupe(['a', ['b', ['c', {d: true}]]]), 'a b c d');
76 | });
77 | });
78 |
--------------------------------------------------------------------------------
/tests/index.js:
--------------------------------------------------------------------------------
1 | /* global describe, it */
2 |
3 | var assert = require('assert');
4 | var classNames = require('../');
5 |
6 | describe('classNames', function () {
7 | it('keeps object keys with truthy values', function () {
8 | assert.equal(classNames({
9 | a: true,
10 | b: false,
11 | c: 0,
12 | d: null,
13 | e: undefined,
14 | f: 1
15 | }), 'a f');
16 | });
17 |
18 | it('joins arrays of class names and ignore falsy values', function () {
19 | assert.equal(classNames('a', 0, null, undefined, true, 1, 'b'), 'a 1 b');
20 | });
21 |
22 | it('supports heterogenous arguments', function () {
23 | assert.equal(classNames({a: true}, 'b', 0), 'a b');
24 | });
25 |
26 | it('should be trimmed', function () {
27 | assert.equal(classNames('', 'b', {}, ''), 'b');
28 | });
29 |
30 | it('returns an empty string for an empty configuration', function () {
31 | assert.equal(classNames({}), '');
32 | });
33 |
34 | it('supports an array of class names', function () {
35 | assert.equal(classNames(['a', 'b']), 'a b');
36 | });
37 |
38 | it('joins array arguments with string arguments', function () {
39 | assert.equal(classNames(['a', 'b'], 'c'), 'a b c');
40 | assert.equal(classNames('c', ['a', 'b']), 'c a b');
41 | });
42 |
43 | it('handles multiple array arguments', function () {
44 | assert.equal(classNames(['a', 'b'], ['c', 'd']), 'a b c d');
45 | });
46 |
47 | it('handles arrays that include falsy and true values', function () {
48 | assert.equal(classNames(['a', 0, null, undefined, false, true, 'b']), 'a b');
49 | });
50 |
51 | it('handles arrays that include arrays', function () {
52 | assert.equal(classNames(['a', ['b', 'c']]), 'a b c');
53 | });
54 |
55 | it('handles arrays that include objects', function () {
56 | assert.equal(classNames(['a', {b: true, c: false}]), 'a b');
57 | });
58 |
59 | it('handles deep array recursion', function () {
60 | assert.equal(classNames(['a', ['b', ['c', {d: true}]]]), 'a b c d');
61 | });
62 |
63 | it('handles arrays that are empty', function () {
64 | assert.equal(classNames('a', []), 'a');
65 | });
66 |
67 | it('handles nested arrays that have empty nested arrays', function () {
68 | assert.equal(classNames('a', [[]]), 'a');
69 | });
70 |
71 | it('handles all types of truthy and falsy property values as expected', function () {
72 | assert.equal(classNames({
73 | // falsy:
74 | null: null,
75 | emptyString: "",
76 | noNumber: NaN,
77 | zero: 0,
78 | negativeZero: -0,
79 | false: false,
80 | undefined: undefined,
81 |
82 | // truthy (literally anything else):
83 | nonEmptyString: "foobar",
84 | whitespace: ' ',
85 | function: Object.prototype.toString,
86 | emptyObject: {},
87 | nonEmptyObject: {a: 1, b: 2},
88 | emptyList: [],
89 | nonEmptyList: [1, 2, 3],
90 | greaterZero: 1
91 | }), 'nonEmptyString whitespace function emptyObject nonEmptyObject emptyList nonEmptyList greaterZero');
92 | });
93 | });
94 |
--------------------------------------------------------------------------------
/dedupe.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Copyright (c) 2017 Jed Watson.
3 | Licensed under the MIT License (MIT), see
4 | http://jedwatson.github.io/classnames
5 | */
6 | /* global define */
7 |
8 | (function () {
9 | 'use strict';
10 |
11 | var classNames = (function () {
12 | // don't inherit from Object so we can skip hasOwnProperty check later
13 | // http://stackoverflow.com/questions/15518328/creating-js-object-with-object-createnull#answer-21079232
14 | function StorageObject() {}
15 | StorageObject.prototype = Object.create(null);
16 |
17 | function _parseArray (resultSet, array) {
18 | var length = array.length;
19 |
20 | for (var i = 0; i < length; ++i) {
21 | _parse(resultSet, array[i]);
22 | }
23 | }
24 |
25 | var hasOwn = {}.hasOwnProperty;
26 |
27 | function _parseNumber (resultSet, num) {
28 | resultSet[num] = true;
29 | }
30 |
31 | function _parseObject (resultSet, object) {
32 | for (var k in object) {
33 | if (hasOwn.call(object, k)) {
34 | // set value to false instead of deleting it to avoid changing object structure
35 | // https://www.smashingmagazine.com/2012/11/writing-fast-memory-efficient-javascript/#de-referencing-misconceptions
36 | resultSet[k] = !!object[k];
37 | }
38 | }
39 | }
40 |
41 | var SPACE = /\s+/;
42 | function _parseString (resultSet, str) {
43 | var array = str.split(SPACE);
44 | var length = array.length;
45 |
46 | for (var i = 0; i < length; ++i) {
47 | resultSet[array[i]] = true;
48 | }
49 | }
50 |
51 | function _parse (resultSet, arg) {
52 | if (!arg) return;
53 | var argType = typeof arg;
54 |
55 | // 'foo bar'
56 | if (argType === 'string') {
57 | _parseString(resultSet, arg);
58 |
59 | // ['foo', 'bar', ...]
60 | } else if (Array.isArray(arg)) {
61 | _parseArray(resultSet, arg);
62 |
63 | // { 'foo': true, ... }
64 | } else if (argType === 'object') {
65 | _parseObject(resultSet, arg);
66 |
67 | // '130'
68 | } else if (argType === 'number') {
69 | _parseNumber(resultSet, arg);
70 | }
71 | }
72 |
73 | function _classNames () {
74 | // don't leak arguments
75 | // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
76 | var len = arguments.length;
77 | var args = Array(len);
78 | for (var i = 0; i < len; i++) {
79 | args[i] = arguments[i];
80 | }
81 |
82 | var classSet = new StorageObject();
83 | _parseArray(classSet, args);
84 |
85 | var list = [];
86 |
87 | for (var k in classSet) {
88 | if (classSet[k]) {
89 | list.push(k)
90 | }
91 | }
92 |
93 | return list.join(' ');
94 | }
95 |
96 | return _classNames;
97 | })();
98 |
99 | if (typeof module !== 'undefined' && module.exports) {
100 | module.exports = classNames;
101 | } else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
102 | // register as 'classnames', consistent with npm package name
103 | define('classnames', [], function () {
104 | return classNames;
105 | });
106 | } else {
107 | window.classNames = classNames;
108 | }
109 | }());
110 |
--------------------------------------------------------------------------------
/tests/bind.js:
--------------------------------------------------------------------------------
1 | /* global describe, it */
2 |
3 | var assert = require('assert');
4 | var classNames = require('../bind');
5 |
6 | var cssModulesMock = {
7 | a: "#a",
8 | b: "#b",
9 | c: "#c",
10 | d: "#d",
11 | e: "#e",
12 | f: "#f"
13 | };
14 |
15 | var classNamesBound = classNames.bind(cssModulesMock);
16 |
17 | describe('bind', function () {
18 | describe('classNames', function () {
19 | it('keeps object keys with truthy values', function () {
20 | assert.equal(classNames({
21 | a: true,
22 | b: false,
23 | c: 0,
24 | d: null,
25 | e: undefined,
26 | f: 1
27 | }), 'a f');
28 | });
29 |
30 | it('joins arrays of class names and ignore falsy values', function () {
31 | assert.equal(classNames('a', 0, null, undefined, true, 1, 'b'), 'a 1 b');
32 | });
33 |
34 | it('supports heterogenous arguments', function () {
35 | assert.equal(classNames({a: true}, 'b', 0), 'a b');
36 | });
37 |
38 | it('should be trimmed', function () {
39 | assert.equal(classNames('', 'b', {}, ''), 'b');
40 | });
41 |
42 | it('returns an empty string for an empty configuration', function () {
43 | assert.equal(classNames({}), '');
44 | });
45 |
46 | it('supports an array of class names', function () {
47 | assert.equal(classNames(['a', 'b']), 'a b');
48 | });
49 |
50 | it('joins array arguments with string arguments', function () {
51 | assert.equal(classNames(['a', 'b'], 'c'), 'a b c');
52 | assert.equal(classNames('c', ['a', 'b']), 'c a b');
53 | });
54 |
55 | it('handles multiple array arguments', function () {
56 | assert.equal(classNames(['a', 'b'], ['c', 'd']), 'a b c d');
57 | });
58 |
59 | it('handles arrays that include falsy and true values', function () {
60 | assert.equal(classNames(['a', 0, null, undefined, false, true, 'b']), 'a b');
61 | });
62 |
63 | it('handles arrays that include arrays', function () {
64 | assert.equal(classNames(['a', ['b', 'c']]), 'a b c');
65 | });
66 |
67 | it('handles arrays that include objects', function () {
68 | assert.equal(classNames(['a', {b: true, c: false}]), 'a b');
69 | });
70 |
71 | it('handles deep array recursion', function () {
72 | assert.equal(classNames(['a', ['b', ['c', {d: true}]]]), 'a b c d');
73 | });
74 | });
75 |
76 | describe('classNamesBound', function () {
77 | it('keeps object keys with truthy values', function () {
78 | assert.equal(classNamesBound({
79 | a: true,
80 | b: false,
81 | c: 0,
82 | d: null,
83 | e: undefined,
84 | f: 1
85 | }), '#a #f');
86 | });
87 | it('keeps class names undefined in bound hash', function () {
88 | assert.equal(classNamesBound({
89 | a: true,
90 | b: false,
91 | c: 0,
92 | d: null,
93 | e: undefined,
94 | f: 1,
95 | x: true,
96 | y: null,
97 | z: 1
98 | }), '#a #f x z');
99 | })
100 | it('joins arrays of class names and ignore falsy values', function () {
101 | assert.equal(classNamesBound('a', 0, null, undefined, true, 1, 'b'), '#a 1 #b');
102 | });
103 |
104 | it('supports heterogenous arguments', function () {
105 | assert.equal(classNamesBound({a: true}, 'b', 0), '#a #b');
106 | });
107 |
108 | it('should be trimmed', function () {
109 | assert.equal(classNamesBound('', 'b', {}, ''), '#b');
110 | });
111 |
112 | it('returns an empty string for an empty configuration', function () {
113 | assert.equal(classNamesBound({}), '');
114 | });
115 |
116 | it('supports an array of class names', function () {
117 | assert.equal(classNamesBound(['a', 'b']), '#a #b');
118 | });
119 |
120 | it('joins array arguments with string arguments', function () {
121 | assert.equal(classNamesBound(['a', 'b'], 'c'), '#a #b #c');
122 | assert.equal(classNamesBound('c', ['a', 'b']), '#c #a #b');
123 | });
124 |
125 | it('handles multiple array arguments', function () {
126 | assert.equal(classNamesBound(['a', 'b'], ['c', 'd']), '#a #b #c #d');
127 | });
128 |
129 | it('handles arrays that include falsy and true values', function () {
130 | assert.equal(classNamesBound(['a', 0, null, undefined, false, true, 'b']), '#a #b');
131 | });
132 |
133 | it('handles arrays that include arrays', function () {
134 | assert.equal(classNamesBound(['a', ['b', 'c']]), '#a #b #c');
135 | });
136 |
137 | it('handles arrays that include objects', function () {
138 | assert.equal(classNamesBound(['a', {b: true, c: false}]), '#a #b');
139 | });
140 |
141 | it('handles deep array recursion', function () {
142 | assert.equal(classNamesBound(['a', ['b', ['c', {d: true}]]]), '#a #b #c #d');
143 | });
144 | });
145 |
146 | })
147 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Classnames
2 | ===========
3 |
4 | [](https://www.npmjs.org/package/classnames)
5 | [](https://travis-ci.org/JedWatson/classnames)
6 | [](http://thinkmill.com.au/?utm_source=github&utm_medium=badge&utm_campaign=classnames)
7 |
8 | A simple JavaScript utility for conditionally joining classNames together.
9 |
10 | Install with [npm](https://www.npmjs.com/), [Bower](https://bower.io/), or [Yarn](https://yarnpkg.com/):
11 |
12 | npm:
13 | ```sh
14 | npm install classnames --save
15 | ```
16 |
17 | Bower:
18 | ```sh
19 | bower install classnames --save
20 | ```
21 |
22 | Yarn (note that `yarn add` automatically saves the package to the `dependencies` in `package.json`):
23 | ```sh
24 | yarn add classnames
25 | ```
26 |
27 | Use with [Node.js](https://nodejs.org/en/), [Browserify](http://browserify.org/), or [webpack](https://webpack.github.io/):
28 |
29 | ```js
30 | var classNames = require('classnames');
31 | classNames('foo', 'bar'); // => 'foo bar'
32 | ```
33 |
34 | Alternatively, you can simply include `index.js` on your page with a standalone `