├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── cjs └── index.js ├── esm └── index.js ├── index.js ├── min.js ├── package.json └── test └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | coverage/ 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/* 2 | node_modules/* 3 | test/* 4 | _config.yml 5 | .DS_Store 6 | .gitignore 7 | .travis.yml 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2018, Andrea Giammarchi, @WebReflection 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated: QuerySelectorObserver 2 | 3 | Please use **[qsa-observer](https://github.com/WebReflection/qsa-observer#readme)** instead, as it's been used in various other libraries, hence it's way more battle-tested, and better, than this initial attempt. 4 | 5 | - - - 6 | 7 | 100% inspired by a [Daniel](https://twitter.com/csuwildcat)'s [hack](http://www.backalleycoder.com/2012/08/06/css-selector-listeners/), this module brings a friendly [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) like API to observe CSS selectors instead. 8 | 9 | ```js 10 | 11 | const so = new QuerySelectorObserver(records => { 12 | for (const record of records) { 13 | if (record.addedNodes.length) { 14 | record.target.textContent = 'Hello via QSO!'; 15 | console.log(record.addedNodes); 16 | } else { 17 | console.log(record.removedNodes); 18 | } 19 | } 20 | }); 21 | 22 | so.observe('.some-selector'); 23 | so.observe('#some-complex > .selector + p.cool'); 24 | 25 | button.onclick = () => so.disconnect(); 26 | ``` 27 | 28 | Compatible with IE11 and other browsers, feel free to [test it live](https://webreflection.github.io/qso/test/). 29 | -------------------------------------------------------------------------------- /cjs/index.js: -------------------------------------------------------------------------------- 1 | /*! (c) Andrea Giammarchi - ISC */ 2 | var QuerySelectorObserver = (function () {'use strict'; 3 | var I = 0; 4 | var IE = typeof Reflect == typeof IE; 5 | var type = ['animationstart', 'webkitAnimationStart', 'MSAnimationStart']; 6 | var prefix = ['', '-webkit-', '-moz-', '-ms-', '-o-']; 7 | var proto = QuerySelectorObserver.prototype; 8 | var wm = new WeakMap; 9 | var query = []; 10 | var mo = null; 11 | Record.prototype.type = "childList"; 12 | proto.connected = true; 13 | proto.handleEvent = function (event) { 14 | if (event.animationName === this.I) { 15 | event.preventDefault(); 16 | event.stopPropagation(); 17 | this.$([new Record(true, [register(this, event.target)], [])]); 18 | } 19 | }; 20 | proto.observe = function (selector) { 21 | if (query.indexOf(selector) < 0) 22 | query.push(selector); 23 | if (!mo) 24 | observe(); 25 | var _ = this._; 26 | var I = this.I; 27 | var P = this.P; 28 | if (!_.length) 29 | connectObserver(this, I, P); 30 | _.push(style(createAnimation(selector, I))); 31 | }; 32 | proto.disconnect = function () { 33 | disconnectObserver(this); 34 | }; 35 | return QuerySelectorObserver; 36 | function QuerySelectorObserver(callback) { 37 | this._ = []; 38 | this.$ = callback; 39 | this.I = 'selector-observer-' + I++; 40 | this.P = IE ? 'outline-color' : ('--' + this.I); 41 | } 42 | function Record(live, addedNodes, removedNodes) { 43 | this.addedNodes = addedNodes; 44 | this.removedNodes = removedNodes; 45 | this.target = live ? addedNodes[0] : removedNodes[0]; 46 | } 47 | function disconnectObserver(so) { 48 | so.connected = false; 49 | for (var i = 0, length = type.length; i < length; i++) 50 | document.removeEventListener(type[i], so, true); 51 | for (var _ = so._.splice(0), i = 0, length = _.length; i < length; i++) { 52 | var style = _[i]; 53 | var parentNode = style.parentNode; 54 | if (parentNode) 55 | parentNode.removeChild(style); 56 | } 57 | } 58 | function connectObserver(so, id, property) { 59 | for (var i = 0, length = type.length; i < length; i++) 60 | document.addEventListener(type[i], so, true); 61 | for (var text = [], i = 0, length = prefix.length; i < length; i++) 62 | text[i] = '@' + prefix[i] + 'keyframes ' + id + [ 63 | '{from{', ':#fff;}to{', ':#000;}}' 64 | ].join(property); 65 | so._.push(style(text.join('\n'))); 66 | } 67 | function createAnimation(selector, id) { 68 | for (var text = [], i = 0, length = prefix.length; i < length; i++) 69 | text[i] = ['', 70 | 'animation-duration:0.001s;', 71 | 'animation-name:' + id 72 | ].join(prefix[i]); 73 | return selector + '{' + text.join(';') + ';}'; 74 | } 75 | function notifyObservers(target) { 76 | var observers = wm.get(target); 77 | if (observers) { 78 | wm.delete(target); 79 | for (var so, i = 0, length = observers.length; i < length; i++) { 80 | so = observers[i]; 81 | if (so.connected) 82 | so.$([new Record(false, [], [target])]); 83 | } 84 | } 85 | } 86 | function observe() { 87 | mo = new MutationObserver(function (records) { 88 | for (var i = 0, length = records.length; i < length; i++) { 89 | for (var 90 | target, 91 | nodes, 92 | removedNodes = records[i].removedNodes, 93 | j = 0, len = removedNodes.length; j < len; j++ 94 | ) { 95 | target = removedNodes[j]; 96 | if (target.nodeType === 1) { 97 | notifyObservers(target); 98 | nodes = target.querySelectorAll(query); 99 | for (var k = 0, l = nodes.length; k < l; k++) { 100 | notifyObservers(nodes[k]); 101 | } 102 | } 103 | } 104 | } 105 | }); 106 | mo.observe(document, {childList: true, subtree: true}); 107 | } 108 | function register(so, target) { 109 | var observers = wm.get(target); 110 | if (observers) { 111 | if (observers.indexOf(so) < 0) 112 | observers.push(so); 113 | } 114 | else 115 | wm.set(target, [so]); 116 | return target; 117 | } 118 | function style(css) { 119 | var d = document; 120 | var s = d.createElement('style'); 121 | s.textContent = css; 122 | return d.head.appendChild(s); 123 | } 124 | }()); 125 | module.exports = QuerySelectorObserver; 126 | -------------------------------------------------------------------------------- /esm/index.js: -------------------------------------------------------------------------------- 1 | /*! (c) Andrea Giammarchi - ISC */ 2 | var QuerySelectorObserver = (function () {'use strict'; 3 | var I = 0; 4 | var IE = typeof Reflect == typeof IE; 5 | var type = ['animationstart', 'webkitAnimationStart', 'MSAnimationStart']; 6 | var prefix = ['', '-webkit-', '-moz-', '-ms-', '-o-']; 7 | var proto = QuerySelectorObserver.prototype; 8 | var wm = new WeakMap; 9 | var query = []; 10 | var mo = null; 11 | Record.prototype.type = "childList"; 12 | proto.connected = true; 13 | proto.handleEvent = function (event) { 14 | if (event.animationName === this.I) { 15 | event.preventDefault(); 16 | event.stopPropagation(); 17 | this.$([new Record(true, [register(this, event.target)], [])]); 18 | } 19 | }; 20 | proto.observe = function (selector) { 21 | if (query.indexOf(selector) < 0) 22 | query.push(selector); 23 | if (!mo) 24 | observe(); 25 | var _ = this._; 26 | var I = this.I; 27 | var P = this.P; 28 | if (!_.length) 29 | connectObserver(this, I, P); 30 | _.push(style(createAnimation(selector, I))); 31 | }; 32 | proto.disconnect = function () { 33 | disconnectObserver(this); 34 | }; 35 | return QuerySelectorObserver; 36 | function QuerySelectorObserver(callback) { 37 | this._ = []; 38 | this.$ = callback; 39 | this.I = 'selector-observer-' + I++; 40 | this.P = IE ? 'outline-color' : ('--' + this.I); 41 | } 42 | function Record(live, addedNodes, removedNodes) { 43 | this.addedNodes = addedNodes; 44 | this.removedNodes = removedNodes; 45 | this.target = live ? addedNodes[0] : removedNodes[0]; 46 | } 47 | function disconnectObserver(so) { 48 | so.connected = false; 49 | for (var i = 0, length = type.length; i < length; i++) 50 | document.removeEventListener(type[i], so, true); 51 | for (var _ = so._.splice(0), i = 0, length = _.length; i < length; i++) { 52 | var style = _[i]; 53 | var parentNode = style.parentNode; 54 | if (parentNode) 55 | parentNode.removeChild(style); 56 | } 57 | } 58 | function connectObserver(so, id, property) { 59 | for (var i = 0, length = type.length; i < length; i++) 60 | document.addEventListener(type[i], so, true); 61 | for (var text = [], i = 0, length = prefix.length; i < length; i++) 62 | text[i] = '@' + prefix[i] + 'keyframes ' + id + [ 63 | '{from{', ':#fff;}to{', ':#000;}}' 64 | ].join(property); 65 | so._.push(style(text.join('\n'))); 66 | } 67 | function createAnimation(selector, id) { 68 | for (var text = [], i = 0, length = prefix.length; i < length; i++) 69 | text[i] = ['', 70 | 'animation-duration:0.001s;', 71 | 'animation-name:' + id 72 | ].join(prefix[i]); 73 | return selector + '{' + text.join(';') + ';}'; 74 | } 75 | function notifyObservers(target) { 76 | var observers = wm.get(target); 77 | if (observers) { 78 | wm.delete(target); 79 | for (var so, i = 0, length = observers.length; i < length; i++) { 80 | so = observers[i]; 81 | if (so.connected) 82 | so.$([new Record(false, [], [target])]); 83 | } 84 | } 85 | } 86 | function observe() { 87 | mo = new MutationObserver(function (records) { 88 | for (var i = 0, length = records.length; i < length; i++) { 89 | for (var 90 | target, 91 | nodes, 92 | removedNodes = records[i].removedNodes, 93 | j = 0, len = removedNodes.length; j < len; j++ 94 | ) { 95 | target = removedNodes[j]; 96 | if (target.nodeType === 1) { 97 | notifyObservers(target); 98 | nodes = target.querySelectorAll(query); 99 | for (var k = 0, l = nodes.length; k < l; k++) { 100 | notifyObservers(nodes[k]); 101 | } 102 | } 103 | } 104 | } 105 | }); 106 | mo.observe(document, {childList: true, subtree: true}); 107 | } 108 | function register(so, target) { 109 | var observers = wm.get(target); 110 | if (observers) { 111 | if (observers.indexOf(so) < 0) 112 | observers.push(so); 113 | } 114 | else 115 | wm.set(target, [so]); 116 | return target; 117 | } 118 | function style(css) { 119 | var d = document; 120 | var s = d.createElement('style'); 121 | s.textContent = css; 122 | return d.head.appendChild(s); 123 | } 124 | }()); 125 | export default QuerySelectorObserver; 126 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! (c) Andrea Giammarchi - ISC */ 2 | var QuerySelectorObserver = (function () {'use strict'; 3 | var I = 0; 4 | var IE = typeof Reflect == typeof IE; 5 | var type = ['animationstart', 'webkitAnimationStart', 'MSAnimationStart']; 6 | var prefix = ['', '-webkit-', '-moz-', '-ms-', '-o-']; 7 | var proto = QuerySelectorObserver.prototype; 8 | var wm = new WeakMap; 9 | var query = []; 10 | var mo = null; 11 | Record.prototype.type = "childList"; 12 | proto.connected = true; 13 | proto.handleEvent = function (event) { 14 | if (event.animationName === this.I) { 15 | event.preventDefault(); 16 | event.stopPropagation(); 17 | this.$([new Record(true, [register(this, event.target)], [])]); 18 | } 19 | }; 20 | proto.observe = function (selector) { 21 | if (query.indexOf(selector) < 0) 22 | query.push(selector); 23 | if (!mo) 24 | observe(); 25 | var _ = this._; 26 | var I = this.I; 27 | var P = this.P; 28 | if (!_.length) 29 | connectObserver(this, I, P); 30 | _.push(style(createAnimation(selector, I))); 31 | }; 32 | proto.disconnect = function () { 33 | disconnectObserver(this); 34 | }; 35 | return QuerySelectorObserver; 36 | function QuerySelectorObserver(callback) { 37 | this._ = []; 38 | this.$ = callback; 39 | this.I = 'selector-observer-' + I++; 40 | this.P = IE ? 'outline-color' : ('--' + this.I); 41 | } 42 | function Record(live, addedNodes, removedNodes) { 43 | this.addedNodes = addedNodes; 44 | this.removedNodes = removedNodes; 45 | this.target = live ? addedNodes[0] : removedNodes[0]; 46 | } 47 | function disconnectObserver(so) { 48 | so.connected = false; 49 | for (var i = 0, length = type.length; i < length; i++) 50 | document.removeEventListener(type[i], so, true); 51 | for (var _ = so._.splice(0), i = 0, length = _.length; i < length; i++) { 52 | var style = _[i]; 53 | var parentNode = style.parentNode; 54 | if (parentNode) 55 | parentNode.removeChild(style); 56 | } 57 | } 58 | function connectObserver(so, id, property) { 59 | for (var i = 0, length = type.length; i < length; i++) 60 | document.addEventListener(type[i], so, true); 61 | for (var text = [], i = 0, length = prefix.length; i < length; i++) 62 | text[i] = '@' + prefix[i] + 'keyframes ' + id + [ 63 | '{from{', ':#fff;}to{', ':#000;}}' 64 | ].join(property); 65 | so._.push(style(text.join('\n'))); 66 | } 67 | function createAnimation(selector, id) { 68 | for (var text = [], i = 0, length = prefix.length; i < length; i++) 69 | text[i] = ['', 70 | 'animation-duration:0.001s;', 71 | 'animation-name:' + id 72 | ].join(prefix[i]); 73 | return selector + '{' + text.join(';') + ';}'; 74 | } 75 | function notifyObservers(target) { 76 | var observers = wm.get(target); 77 | if (observers) { 78 | wm.delete(target); 79 | for (var so, i = 0, length = observers.length; i < length; i++) { 80 | so = observers[i]; 81 | if (so.connected) 82 | so.$([new Record(false, [], [target])]); 83 | } 84 | } 85 | } 86 | function observe() { 87 | mo = new MutationObserver(function (records) { 88 | for (var i = 0, length = records.length; i < length; i++) { 89 | for (var 90 | target, 91 | nodes, 92 | removedNodes = records[i].removedNodes, 93 | j = 0, len = removedNodes.length; j < len; j++ 94 | ) { 95 | target = removedNodes[j]; 96 | if (target.nodeType === 1) { 97 | notifyObservers(target); 98 | nodes = target.querySelectorAll(query); 99 | for (var k = 0, l = nodes.length; k < l; k++) { 100 | notifyObservers(nodes[k]); 101 | } 102 | } 103 | } 104 | } 105 | }); 106 | mo.observe(document, {childList: true, subtree: true}); 107 | } 108 | function register(so, target) { 109 | var observers = wm.get(target); 110 | if (observers) { 111 | if (observers.indexOf(so) < 0) 112 | observers.push(so); 113 | } 114 | else 115 | wm.set(target, [so]); 116 | return target; 117 | } 118 | function style(css) { 119 | var d = document; 120 | var s = d.createElement('style'); 121 | s.textContent = css; 122 | return d.head.appendChild(s); 123 | } 124 | }()); 125 | -------------------------------------------------------------------------------- /min.js: -------------------------------------------------------------------------------- 1 | /*! (c) Andrea Giammarchi - ISC */ 2 | var QuerySelectorObserver=function(){"use strict";var t=0,n=typeof Reflect==typeof n,a=["animationstart","webkitAnimationStart","MSAnimationStart"],s=["","-webkit-","-moz-","-ms-","-o-"],e=o.prototype,i=new WeakMap,f=[],r=null;return h.prototype.type="childList",e.connected=!0,e.handleEvent=function(e){e.animationName===this.I&&(e.preventDefault(),e.stopPropagation(),this.$([new h(!0,[function(e,t){var n=i.get(t);n?n.indexOf(e)<0&&n.push(e):i.set(t,[e]);return t}(this,e.target)],[])]))},e.observe=function(e){f.indexOf(e)<0&&f.push(e),r||(r=new MutationObserver(function(e){for(var t=0,n=e.length;t> cjs/index.js", 11 | "esm": "cp index.js esm/ && echo 'export default QuerySelectorObserver;' >> esm/index.js", 12 | "min": "uglifyjs index.js --support-ie8 --comments=/^!/ -c -m -o min.js", 13 | "size": "cat index.js | wc -c && cat min.js | wc -c && gzip -c9 min.js | wc -c && cat min.js | brotli | wc -c" 14 | }, 15 | "author": "Andrea Giammarchi", 16 | "license": "ISC", 17 | "devDependencies": { 18 | "uglify-js": "^3.4.9" 19 | }, 20 | "directories": { 21 | "test": "test" 22 | }, 23 | "dependencies": {}, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/WebReflection/qso.git" 27 | }, 28 | "keywords": [ 29 | "css", 30 | "selector", 31 | "observer", 32 | "dom" 33 | ], 34 | "bugs": { 35 | "url": "https://github.com/WebReflection/qso/issues" 36 | }, 37 | "homepage": "https://github.com/WebReflection/qso#readme" 38 | } 39 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | QuerySelectorObserver 8 | 9 | 44 | 45 | 46 |
47 | 48 | 49 | --------------------------------------------------------------------------------