├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── package.json ├── script └── test ├── src └── index.js └── tests ├── qunit.html ├── remove.js ├── runner.coffee └── tests.js /.travis.yml: -------------------------------------------------------------------------------- 1 | script: script/test 2 | language: node_js 3 | node_js: 4 | - '0.10' 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.2.0 4 | 5 | * Rewrite history for [yola/classlist-polyfill][], branching and rebasing 6 | from [eligrey/classList][]. 7 | * Update fork 8 | * Update package.json to use Unlicense [eligrey#56][] 9 | * Fixes add/remove/toggle in IE10 and IE11 [eligrey#57][] 10 | * IE8 fixes [eligrey#43][] 11 | 12 | [yola/classlist-polyfill]: https://github.com/yola/classlist-polyfill 13 | [eligrey/classList]: https://github.com/eligrey/classList.js 14 | [eligrey#57]: https://github.com/eligrey/classList.js/pull/57 15 | [eligrey#56]: https://github.com/eligrey/classList.js/pull/56 16 | [eligrey#43]: https://github.com/eligrey/classList.js/pull/43 17 | 18 | 19 | ## 1.0.3 20 | 21 | * Add support for missing SVGElement.classList in IE 22 | 23 | 24 | ## 1.0.2 25 | 26 | * Fix issue with `self` not being defined in CommonJS 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # classlist-polyfill 2 | 3 | Polyfill for [`element.classList`][docs]. 4 | 5 | This is a published fork of [classList.js][]. 6 | 7 | [classList.js]:https://github.com/eligrey/classList.js 8 | [docs]: https://developer.mozilla.org/en/DOM/element.classList 9 | 10 | 11 | ## Installation 12 | 13 | Download using [NPM](https://www.npmjs.com/package/classlist-polyfill): 14 | 15 | ```shell 16 | npm install classlist-polyfill 17 | ``` 18 | 19 | Download using [Bower](http://bower.io/): 20 | 21 | ```shell 22 | bower install classlist-polyfill 23 | ``` 24 | 25 | 26 | ## What is the purpose of this repo? 27 | 28 | The upstream maintainer has decided [not to publish][comment]. 29 | 30 | [comment]: https://github.com/eligrey/classList.js/pull/46#issuecomment-189782600 31 | 32 | 33 | ## Contributing 34 | 35 | Preferably all changes are made upstream. 36 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "classlist-polyfill", 3 | "description": "MDN's ClassList Polyfill", 4 | "main": "src/index.js", 5 | "authors": [ 6 | "Eli Grey ", 7 | "Yola Engineering (https://www.yola.com/)" 8 | ], 9 | "license": "Unlicense", 10 | "keywords": [ 11 | "classList", 12 | "polyfill", 13 | "shim", 14 | "cross-browser" 15 | ], 16 | "homepage": "https://github.com/yola/classlist-polyfill", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "test", 22 | "tests" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "classlist-polyfill", 3 | "version": "1.2.0", 4 | "description": "Cross-browser JavaScript shim that fully implements element.classList (referenced on MDN)", 5 | "main": "src/index.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "test": "bash ./script/test" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/yola/classlist-polyfill.git" 15 | }, 16 | "keywords": [ 17 | "classList", 18 | "polyfill", 19 | "shim", 20 | "cross-browser" 21 | ], 22 | "author": "Eli Grey ", 23 | "contributors": [ 24 | "Eli Grey ", 25 | "Yola Engineering (https://www.yola.com/)" 26 | ], 27 | "license": "Unlicense", 28 | "bugs": { 29 | "url": "https://github.com/eligrey/classList.js/issues" 30 | }, 31 | "homepage": "https://github.com/yola/classlist-polyfill" 32 | } 33 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ -t 1 ]; then 5 | red="$(printf "\033[31m")" 6 | brightred="$(printf "\033[31;1m")" 7 | green="$(printf "\033[32m")" 8 | reset="$(printf "\033[m")" 9 | else 10 | red= 11 | brightred= 12 | green= 13 | reset= 14 | fi 15 | 16 | phantomjs tests/runner.coffee tests/qunit.html | sed -E " 17 | # failure line: 18 | s/^(✘.+)/${red}\\1${reset}/ 19 | # failure details: 20 | s/^( .+)/${brightred}\\1${reset}/ 21 | # success marker: 22 | s/(✔︎)/${green}\\1${reset}/ 23 | " 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * classList.js: Cross-browser full element.classList implementation. 3 | * 1.1.20170427 4 | * 5 | * By Eli Grey, http://eligrey.com 6 | * License: Dedicated to the public domain. 7 | * See https://github.com/eligrey/classList.js/blob/master/LICENSE.md 8 | */ 9 | 10 | /*global self, document, DOMException */ 11 | 12 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */ 13 | 14 | if ("document" in window.self) { 15 | 16 | // Full polyfill for browsers with no classList support 17 | // Including IE < Edge missing SVGElement.classList 18 | if (!("classList" in document.createElement("_")) 19 | || document.createElementNS && !("classList" in document.createElementNS("http://www.w3.org/2000/svg","g"))) { 20 | 21 | (function (view) { 22 | 23 | "use strict"; 24 | 25 | if (!('Element' in view)) return; 26 | 27 | var 28 | classListProp = "classList" 29 | , protoProp = "prototype" 30 | , elemCtrProto = view.Element[protoProp] 31 | , objCtr = Object 32 | , strTrim = String[protoProp].trim || function () { 33 | return this.replace(/^\s+|\s+$/g, ""); 34 | } 35 | , arrIndexOf = Array[protoProp].indexOf || function (item) { 36 | var 37 | i = 0 38 | , len = this.length 39 | ; 40 | for (; i < len; i++) { 41 | if (i in this && this[i] === item) { 42 | return i; 43 | } 44 | } 45 | return -1; 46 | } 47 | // Vendors: please allow content code to instantiate DOMExceptions 48 | , DOMEx = function (type, message) { 49 | this.name = type; 50 | this.code = DOMException[type]; 51 | this.message = message; 52 | } 53 | , checkTokenAndGetIndex = function (classList, token) { 54 | if (token === "") { 55 | throw new DOMEx( 56 | "SYNTAX_ERR" 57 | , "An invalid or illegal string was specified" 58 | ); 59 | } 60 | if (/\s/.test(token)) { 61 | throw new DOMEx( 62 | "INVALID_CHARACTER_ERR" 63 | , "String contains an invalid character" 64 | ); 65 | } 66 | return arrIndexOf.call(classList, token); 67 | } 68 | , ClassList = function (elem) { 69 | var 70 | trimmedClasses = strTrim.call(elem.getAttribute("class") || "") 71 | , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [] 72 | , i = 0 73 | , len = classes.length 74 | ; 75 | for (; i < len; i++) { 76 | this.push(classes[i]); 77 | } 78 | this._updateClassName = function () { 79 | elem.setAttribute("class", this.toString()); 80 | }; 81 | } 82 | , classListProto = ClassList[protoProp] = [] 83 | , classListGetter = function () { 84 | return new ClassList(this); 85 | } 86 | ; 87 | // Most DOMException implementations don't allow calling DOMException's toString() 88 | // on non-DOMExceptions. Error's toString() is sufficient here. 89 | DOMEx[protoProp] = Error[protoProp]; 90 | classListProto.item = function (i) { 91 | return this[i] || null; 92 | }; 93 | classListProto.contains = function (token) { 94 | token += ""; 95 | return checkTokenAndGetIndex(this, token) !== -1; 96 | }; 97 | classListProto.add = function () { 98 | var 99 | tokens = arguments 100 | , i = 0 101 | , l = tokens.length 102 | , token 103 | , updated = false 104 | ; 105 | do { 106 | token = tokens[i] + ""; 107 | if (checkTokenAndGetIndex(this, token) === -1) { 108 | this.push(token); 109 | updated = true; 110 | } 111 | } 112 | while (++i < l); 113 | 114 | if (updated) { 115 | this._updateClassName(); 116 | } 117 | }; 118 | classListProto.remove = function () { 119 | var 120 | tokens = arguments 121 | , i = 0 122 | , l = tokens.length 123 | , token 124 | , updated = false 125 | , index 126 | ; 127 | do { 128 | token = tokens[i] + ""; 129 | index = checkTokenAndGetIndex(this, token); 130 | while (index !== -1) { 131 | this.splice(index, 1); 132 | updated = true; 133 | index = checkTokenAndGetIndex(this, token); 134 | } 135 | } 136 | while (++i < l); 137 | 138 | if (updated) { 139 | this._updateClassName(); 140 | } 141 | }; 142 | classListProto.toggle = function (token, force) { 143 | token += ""; 144 | 145 | var 146 | result = this.contains(token) 147 | , method = result ? 148 | force !== true && "remove" 149 | : 150 | force !== false && "add" 151 | ; 152 | 153 | if (method) { 154 | this[method](token); 155 | } 156 | 157 | if (force === true || force === false) { 158 | return force; 159 | } else { 160 | return !result; 161 | } 162 | }; 163 | classListProto.toString = function () { 164 | return this.join(" "); 165 | }; 166 | 167 | if (objCtr.defineProperty) { 168 | var classListPropDesc = { 169 | get: classListGetter 170 | , enumerable: true 171 | , configurable: true 172 | }; 173 | try { 174 | objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 175 | } catch (ex) { // IE 8 doesn't support enumerable:true 176 | // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36 177 | // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected 178 | if (ex.number === undefined || ex.number === -0x7FF5EC54) { 179 | classListPropDesc.enumerable = false; 180 | objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 181 | } 182 | } 183 | } else if (objCtr[protoProp].__defineGetter__) { 184 | elemCtrProto.__defineGetter__(classListProp, classListGetter); 185 | } 186 | 187 | }(window.self)); 188 | 189 | } 190 | 191 | // There is full or partial native classList support, so just check if we need 192 | // to normalize the add/remove and toggle APIs. 193 | 194 | (function () { 195 | "use strict"; 196 | 197 | var testElement = document.createElement("_"); 198 | 199 | testElement.classList.add("c1", "c2"); 200 | 201 | // Polyfill for IE 10/11 and Firefox <26, where classList.add and 202 | // classList.remove exist but support only one argument at a time. 203 | if (!testElement.classList.contains("c2")) { 204 | var createMethod = function(method) { 205 | var original = DOMTokenList.prototype[method]; 206 | 207 | DOMTokenList.prototype[method] = function(token) { 208 | var i, len = arguments.length; 209 | 210 | for (i = 0; i < len; i++) { 211 | token = arguments[i]; 212 | original.call(this, token); 213 | } 214 | }; 215 | }; 216 | createMethod('add'); 217 | createMethod('remove'); 218 | } 219 | 220 | testElement.classList.toggle("c3", false); 221 | 222 | // Polyfill for IE 10 and Firefox <24, where classList.toggle does not 223 | // support the second argument. 224 | if (testElement.classList.contains("c3")) { 225 | var _toggle = DOMTokenList.prototype.toggle; 226 | 227 | DOMTokenList.prototype.toggle = function(token, force) { 228 | if (1 in arguments && !this.contains(token) === !force) { 229 | return force; 230 | } else { 231 | return _toggle.call(this, token); 232 | } 233 | }; 234 | 235 | } 236 | 237 | testElement = null; 238 | }()); 239 | 240 | } 241 | -------------------------------------------------------------------------------- /tests/qunit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Tests 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/remove.js: -------------------------------------------------------------------------------- 1 | QUnit.module("classList.remove"); 2 | 3 | QUnit.test("Removes duplicated instances of class", function(assert) { 4 | var el = document.createElement("p"), cList = el.classList; 5 | el.className = "ho ho ho" 6 | 7 | cList.remove("ho"); 8 | assert.ok(!cList.contains("ho"), "Should remove all instances of 'ho'"); 9 | assert.strictEqual(el.className, "") 10 | }); 11 | -------------------------------------------------------------------------------- /tests/runner.coffee: -------------------------------------------------------------------------------- 1 | urls = require('system').args.slice(1) 2 | page = require('webpage').create() 3 | timeout = 3000 4 | 5 | qunitHooks = -> 6 | window.document.addEventListener 'DOMContentLoaded', -> 7 | for callback in ['log', 'testDone', 'done'] 8 | do (callback) -> 9 | QUnit[callback] (result) -> 10 | window.callPhantom 11 | name: "QUnit.#{callback}" 12 | data: result 13 | 14 | page.onInitialized = -> page.evaluate qunitHooks 15 | 16 | page.onConsoleMessage = (msg) -> console.log msg 17 | 18 | page.onCallback = (event) -> 19 | if event.name is 'QUnit.log' 20 | details = event.data 21 | if details.result is false 22 | console.log "✘ #{details.module}: #{details.name}" 23 | if details.message and details.message isnt "failed" 24 | console.log " #{details.message}" 25 | if "actual" of details 26 | console.log " expected: #{details.expected}" 27 | console.log " actual: #{details.actual}" 28 | else if event.name is 'QUnit.testDone' 29 | result = event.data 30 | unless result.failed 31 | console.log "✔︎ #{result.module}: #{result.name}" 32 | else if event.name is 'QUnit.done' 33 | res = event.data 34 | console.log "#{res.total} tests, #{res.failed} failed. Done in #{res.runtime} ms" 35 | phantom.exit if !res.total or res.failed then 1 else 0 36 | 37 | for url in urls 38 | page.open url, (status) -> 39 | if status isnt 'success' 40 | console.error "failed opening #{url}: #{status}" 41 | phantom.exit 1 42 | else 43 | setTimeout -> 44 | console.error "ERROR: Test execution has timed out" 45 | phantom.exit 1 46 | , timeout 47 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | QUnit.module("classList.toggle"); 2 | 3 | QUnit.test("Adds a class", function(assert) { 4 | var cList = document.createElement("p").classList; 5 | 6 | cList.toggle("c1"); 7 | assert.ok(cList.contains("c1"), "Adds a class that is not present"); 8 | 9 | assert.strictEqual( 10 | cList.toggle("c2"), 11 | true, 12 | "Returns true when class is added" 13 | ); 14 | }); 15 | 16 | QUnit.test("Removes a class", function(assert) { 17 | var cList = document.createElement("p").classList; 18 | 19 | cList.add("c1"); 20 | cList.toggle("c1"); 21 | assert.ok(!cList.contains("c1"), "Removes a class that is present"); 22 | 23 | cList.add("c2"); 24 | assert.strictEqual( 25 | cList.toggle("c2"), 26 | false, 27 | "Return false when class is removed" 28 | ); 29 | }); 30 | 31 | QUnit.test("Adds class with second argument", function(assert) { 32 | var cList = document.createElement("p").classList; 33 | 34 | cList.toggle("c1", true); 35 | assert.ok(cList.contains("c1"), "Adds a class"); 36 | 37 | assert.strictEqual( 38 | cList.toggle("c2", true), 39 | true, 40 | "Returns true when class is added" 41 | ); 42 | 43 | cList.add("c3"); 44 | cList.toggle("c3", true); 45 | assert.ok( 46 | cList.contains("c3"), 47 | "Does not remove a class that is already present" 48 | ); 49 | 50 | cList.add("c4"); 51 | assert.strictEqual( 52 | cList.toggle("c4", true), 53 | true, 54 | "Returns true when class is already present" 55 | ); 56 | }); 57 | 58 | QUnit.test("Removes class with second argument", function(assert) { 59 | var cList = document.createElement("p").classList; 60 | 61 | cList.add("c1"); 62 | cList.toggle("c1", false); 63 | assert.ok(!cList.contains("c1"), "Removes a class"); 64 | 65 | assert.strictEqual( 66 | cList.toggle("c2", false), 67 | false, 68 | "Returns false when class is removed" 69 | ); 70 | 71 | cList.toggle("c3", false); 72 | assert.ok( 73 | !cList.contains("c3"), 74 | "Does not add a class that is not present" 75 | ); 76 | 77 | assert.strictEqual( 78 | cList.toggle("c4", false), 79 | false, 80 | "Returns false when class was not present" 81 | ); 82 | }); 83 | --------------------------------------------------------------------------------