├── .gitignore ├── .npmignore ├── History.md ├── Makefile ├── Readme.md ├── dist ├── tipp.js └── tipp.min.js ├── example ├── index.css └── index.js ├── index.js ├── package.json ├── tipp.css └── tipp.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | examples 4 | *.sock 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.0.5 / 2015-09-01 3 | ================== 4 | 5 | * update dist 6 | * support requiring index directly 7 | 8 | 1.0.4 / 2015-09-01 9 | ================== 10 | 11 | * distribute tipp (fixes minification issues) 12 | 13 | 1.0.3 / 2015-09-01 14 | ================== 15 | 16 | * bump adjust 17 | * fix missing context 18 | 19 | 1.0.2 / 2015-08-31 20 | ================== 21 | 22 | * fix element in document check. add multi class support 23 | 24 | 1.0.1 / 2015-08-31 25 | ================== 26 | 27 | * rename everything. better support for animations 28 | 29 | 1.0.0 / 2015-08-31 30 | ================== 31 | 32 | * Initial release 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | example: 2 | @./node_modules/.bin/roo example/index.js example/index.css 3 | 4 | test: 5 | @./node_modules/.bin/mocha \ 6 | --require should \ 7 | --reporter spec 8 | 9 | dist: dist-library dist-minify 10 | 11 | dist-library: 12 | @mkdir -p dist 13 | @./node_modules/.bin/browserify -t browserify-string-to-js index.js -s tipp --outfile dist/tipp.js 14 | 15 | dist-minify: dist/tipp.js 16 | @curl -s \ 17 | -d compilation_level=SIMPLE_OPTIMIZATIONS \ 18 | -d output_format=text \ 19 | -d output_info=compiled_code \ 20 | --data-urlencode "js_code@$<" \ 21 | http://marijnhaverbeke.nl/uglifyjs \ 22 | > $<.tmp 23 | @mv $<.tmp dist/tipp.min.js 24 | 25 | .PHONY: example dist 26 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # tipp 3 | 4 | Tool tips that just work. 5 | 6 | ## Features 7 | 8 | - No jQuery 9 | - No CSS to include 10 | - Infinitely Customizable 11 | - Performant 12 | - Auto-repositioning 13 | - Orientations (top left, bottom, right) 14 | 15 | ## Installation 16 | 17 | npm install tipp 18 | 19 | ## Usage 20 | 21 | 1. Using HTML attributes: 22 | 23 | Markup: 24 | 25 | ```html 26 | 27 | ``` 28 | 29 | Initialize: 30 | 31 | ```js 32 | tipp() 33 | ``` 34 | 35 | 2. Using the API 36 | 37 | Initialize: 38 | 39 | ```js 40 | tipp(button, 'YOLO!', { 41 | orientation: 'top', 42 | class: 'simple' 43 | }) 44 | ``` 45 | 46 | ## License 47 | 48 | MIT 49 | -------------------------------------------------------------------------------- /dist/tipp.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.tipp = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o viewport_position.right && target_position.left - width >= viewport_position.left) { 576 | // flip left 577 | right = target_position.left - offset.x 578 | left = right - width 579 | orientation.x = mirror(orientation.x) 580 | } 581 | 582 | // out of viewport on the top or bottom, 583 | // and we have room on the bottom or top 584 | if (top < viewport_position.top && target_position.bottom + height <= viewport_position.bottom) { 585 | // flip bottom 586 | top = target_position.bottom - offset.y 587 | bottom = top + height 588 | orientation.y = mirror(orientation.y) 589 | } else 590 | if (bottom > viewport_position.bottom && target_position.top - height >= viewport_position.top) { 591 | // flip top 592 | bottom = target_position.top - offset.y 593 | top = bottom - height 594 | orientation.y = mirror(orientation.y) 595 | } 596 | } 597 | 598 | return { 599 | top: top, 600 | left: left, 601 | width: width, 602 | height: height, 603 | right: right, 604 | bottom: bottom, 605 | orientation: expr(orientation) 606 | } 607 | } 608 | } 609 | 610 | },{"./lib/expression":4,"./lib/mirror":5,"object-assign":6}],4:[function(require,module,exports){ 611 | /** 612 | * Export `expression` 613 | * 614 | * @param {String|Object} expr 615 | * @return {Object|String} 616 | */ 617 | 618 | module.exports = function expr (expr) { 619 | return typeof expr === 'string' 620 | ? parse(expr) 621 | : compile(expr) 622 | } 623 | 624 | /** 625 | * Parse the expression 626 | * 627 | * @param {String} expr 628 | * @return {Object} 629 | */ 630 | 631 | function parse(expr) { 632 | var tokens = expr.split(/\s+/) 633 | var out = {} 634 | 635 | // token defaults 636 | if (tokens.length === 1) { 637 | switch(tokens[0]) { 638 | case 'middle': tokens.push('center'); break 639 | case 'bottom': tokens.push('center'); break 640 | case 'center': tokens.push('middle'); break 641 | case 'right': tokens.push('middle'); break 642 | case 'left': tokens.push('middle'); break 643 | case 'top': tokens.push('center'); break 644 | } 645 | } 646 | 647 | // turn strings into numbers 648 | tokens.forEach(function(token, i) { 649 | switch (token) { 650 | case 'center': return out.x = 0.5 651 | case 'middle': return out.y = 0.5 652 | case 'bottom': return out.y = 1 653 | case 'right': return out.x = 1 654 | case 'left': return out.x = 0 655 | case 'top': return out.y = 0 656 | default: 657 | return i % 2 658 | ? out.y = percentage(token) 659 | : out.x = percentage(token) 660 | } 661 | }) 662 | 663 | return out 664 | } 665 | 666 | /** 667 | * Compile an object into a string 668 | * the reverse of parse 669 | * 670 | * @param {Object} n 671 | * @return {String} 672 | */ 673 | 674 | function compile (expr) { 675 | var out = [] 676 | switch (expr.x) { 677 | case 0: out.push('left'); break 678 | case 0.5: out.push('center'); break 679 | case 1: out.push('right'); break 680 | default: out.push(expr.x * 100 + '%') 681 | } 682 | 683 | switch (expr.y) { 684 | case 0: out.push('top'); break 685 | case 0.5: out.push('middle'); break 686 | case 1: out.push('bottom'); break 687 | default: out.push((expr.y * 100) + '%') 688 | } 689 | 690 | return out.join(' ') 691 | } 692 | 693 | /** 694 | * To percentage 695 | * 696 | * @param {String} val 697 | * @return {Number} 698 | */ 699 | 700 | function percentage (val) { 701 | var float = parseFloat(val) 702 | return isNaN(float) ? 0 : float / 100 703 | } 704 | 705 | },{}],5:[function(require,module,exports){ 706 | /** 707 | * Export `mirror` 708 | */ 709 | 710 | module.exports = mirror 711 | 712 | /** 713 | * Get the mirror of the attachment 714 | * 715 | * @param {Number|Object} 716 | * @return {Object} 717 | */ 718 | 719 | function mirror (p) { 720 | if (typeof p === 'number') { 721 | return round(Math.abs(1 - p)) 722 | } 723 | 724 | return { 725 | x: round(Math.abs(1 - p.x)), 726 | y: round(Math.abs(1 - p.y)) 727 | } 728 | } 729 | 730 | /** 731 | * Rounding 732 | * 733 | * @param {Number} n 734 | * @return {Number} 735 | */ 736 | 737 | function round (n) { 738 | return parseFloat(n.toFixed(2)) 739 | } 740 | 741 | },{}],6:[function(require,module,exports){ 742 | 'use strict'; 743 | var propIsEnumerable = Object.prototype.propertyIsEnumerable; 744 | 745 | function ToObject(val) { 746 | if (val == null) { 747 | throw new TypeError('Object.assign cannot be called with null or undefined'); 748 | } 749 | 750 | return Object(val); 751 | } 752 | 753 | function ownEnumerableKeys(obj) { 754 | var keys = Object.getOwnPropertyNames(obj); 755 | 756 | if (Object.getOwnPropertySymbols) { 757 | keys = keys.concat(Object.getOwnPropertySymbols(obj)); 758 | } 759 | 760 | return keys.filter(function (key) { 761 | return propIsEnumerable.call(obj, key); 762 | }); 763 | } 764 | 765 | module.exports = Object.assign || function (target, source) { 766 | var from; 767 | var keys; 768 | var to = ToObject(target); 769 | 770 | for (var s = 1; s < arguments.length; s++) { 771 | from = arguments[s]; 772 | keys = ownEnumerableKeys(Object(from)); 773 | 774 | for (var i = 0; i < keys.length; i++) { 775 | to[keys[i]] = from[keys[i]]; 776 | } 777 | } 778 | 779 | return to; 780 | }; 781 | 782 | },{}],7:[function(require,module,exports){ 783 | /** 784 | * Expose `requestAnimationFrame()`. 785 | */ 786 | 787 | exports = module.exports = window.requestAnimationFrame 788 | || window.webkitRequestAnimationFrame 789 | || window.mozRequestAnimationFrame 790 | || fallback; 791 | 792 | /** 793 | * Fallback implementation. 794 | */ 795 | 796 | var prev = new Date().getTime(); 797 | function fallback(fn) { 798 | var curr = new Date().getTime(); 799 | var ms = Math.max(0, 16 - (curr - prev)); 800 | var req = setTimeout(fn, ms); 801 | prev = curr; 802 | return req; 803 | } 804 | 805 | /** 806 | * Cancel. 807 | */ 808 | 809 | var cancel = window.cancelAnimationFrame 810 | || window.webkitCancelAnimationFrame 811 | || window.mozCancelAnimationFrame 812 | || window.clearTimeout; 813 | 814 | exports.cancel = function(id){ 815 | cancel.call(window, id); 816 | }; 817 | 818 | },{}],8:[function(require,module,exports){ 819 | (function (global){ 820 | module.exports = 821 | global.performance && 822 | global.performance.now ? function now() { 823 | return performance.now() 824 | } : Date.now || function now() { 825 | return +new Date 826 | } 827 | 828 | }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 829 | },{}],9:[function(require,module,exports){ 830 | ;(function () { 831 | var parents = function (node, ps) { 832 | if (node.parentNode === null) { return ps; } 833 | 834 | return parents(node.parentNode, ps.concat([node])); 835 | }; 836 | 837 | var style = function (node, prop) { 838 | return getComputedStyle(node, null).getPropertyValue(prop); 839 | }; 840 | 841 | var overflow = function (node) { 842 | return style(node, "overflow") + style(node, "overflow-y") + style(node, "overflow-x"); 843 | }; 844 | 845 | var scroll = function (node) { 846 | return (/(auto|scroll)/).test(overflow(node)); 847 | }; 848 | 849 | var scrollParent = function (node) { 850 | if (!(node instanceof HTMLElement)) { 851 | return ; 852 | } 853 | 854 | var ps = parents(node.parentNode, []); 855 | 856 | for (var i = 0; i < ps.length; i += 1) { 857 | if (scroll(ps[i])) { 858 | return ps[i]; 859 | } 860 | } 861 | 862 | return window; 863 | }; 864 | 865 | // If common js is defined use it. 866 | if (typeof module === "object" && module !== null) { 867 | module.exports = scrollParent; 868 | } else { 869 | window.Scrollparent = scrollParent; 870 | } 871 | })(); 872 | 873 | },{}],10:[function(require,module,exports){ 874 | 875 | /** 876 | * Module dependencies. 877 | */ 878 | 879 | var transform = require('transform-property'); 880 | var has3d = require('has-translate3d'); 881 | 882 | /** 883 | * Expose `translate`. 884 | */ 885 | 886 | module.exports = translate; 887 | 888 | /** 889 | * Translate `el` by `(x, y)`. 890 | * 891 | * @param {Element} el 892 | * @param {Number} x 893 | * @param {Number} y 894 | * @api public 895 | */ 896 | 897 | function translate(el, x, y){ 898 | if (transform) { 899 | if (has3d) { 900 | el.style[transform] = 'translate3d(' + x + 'px,' + y + 'px, 0)'; 901 | } else { 902 | el.style[transform] = 'translate(' + x + 'px,' + y + 'px)'; 903 | } 904 | } else { 905 | el.style.left = x; 906 | el.style.top = y; 907 | } 908 | }; 909 | 910 | },{"has-translate3d":11,"transform-property":12}],11:[function(require,module,exports){ 911 | 912 | var prop = require('transform-property'); 913 | 914 | // IE <=8 doesn't have `getComputedStyle` 915 | if (!prop || !window.getComputedStyle) { 916 | module.exports = false; 917 | 918 | } else { 919 | var map = { 920 | webkitTransform: '-webkit-transform', 921 | OTransform: '-o-transform', 922 | msTransform: '-ms-transform', 923 | MozTransform: '-moz-transform', 924 | transform: 'transform' 925 | }; 926 | 927 | // from: https://gist.github.com/lorenzopolidori/3794226 928 | var el = document.createElement('div'); 929 | el.style[prop] = 'translate3d(1px,1px,1px)'; 930 | document.body.insertBefore(el, null); 931 | var val = getComputedStyle(el).getPropertyValue(map[prop]); 932 | document.body.removeChild(el); 933 | module.exports = null != val && val.length && 'none' != val; 934 | } 935 | 936 | },{"transform-property":12}],12:[function(require,module,exports){ 937 | 938 | var styles = [ 939 | 'webkitTransform', 940 | 'MozTransform', 941 | 'msTransform', 942 | 'OTransform', 943 | 'transform' 944 | ]; 945 | 946 | var el = document.createElement('p'); 947 | var style; 948 | 949 | for (var i = 0; i < styles.length; i++) { 950 | style = styles[i]; 951 | if (null != el.style[style]) { 952 | module.exports = style; 953 | break; 954 | } 955 | } 956 | 957 | },{}],13:[function(require,module,exports){ 958 | /** 959 | * Module dependencies. 960 | */ 961 | 962 | var index = require('indexof'); 963 | 964 | /** 965 | * Whitespace regexp. 966 | */ 967 | 968 | var re = /\s+/; 969 | 970 | /** 971 | * toString reference. 972 | */ 973 | 974 | var toString = Object.prototype.toString; 975 | 976 | /** 977 | * Wrap `el` in a `ClassList`. 978 | * 979 | * @param {Element} el 980 | * @return {ClassList} 981 | * @api public 982 | */ 983 | 984 | module.exports = function(el){ 985 | return new ClassList(el); 986 | }; 987 | 988 | /** 989 | * Initialize a new ClassList for `el`. 990 | * 991 | * @param {Element} el 992 | * @api private 993 | */ 994 | 995 | function ClassList(el) { 996 | if (!el || !el.nodeType) { 997 | throw new Error('A DOM element reference is required'); 998 | } 999 | this.el = el; 1000 | this.list = el.classList; 1001 | } 1002 | 1003 | /** 1004 | * Add class `name` if not already present. 1005 | * 1006 | * @param {String} name 1007 | * @return {ClassList} 1008 | * @api public 1009 | */ 1010 | 1011 | ClassList.prototype.add = function(name){ 1012 | // classList 1013 | if (this.list) { 1014 | this.list.add(name); 1015 | return this; 1016 | } 1017 | 1018 | // fallback 1019 | var arr = this.array(); 1020 | var i = index(arr, name); 1021 | if (!~i) arr.push(name); 1022 | this.el.className = arr.join(' '); 1023 | return this; 1024 | }; 1025 | 1026 | /** 1027 | * Remove class `name` when present, or 1028 | * pass a regular expression to remove 1029 | * any which match. 1030 | * 1031 | * @param {String|RegExp} name 1032 | * @return {ClassList} 1033 | * @api public 1034 | */ 1035 | 1036 | ClassList.prototype.remove = function(name){ 1037 | if ('[object RegExp]' == toString.call(name)) { 1038 | return this.removeMatching(name); 1039 | } 1040 | 1041 | // classList 1042 | if (this.list) { 1043 | this.list.remove(name); 1044 | return this; 1045 | } 1046 | 1047 | // fallback 1048 | var arr = this.array(); 1049 | var i = index(arr, name); 1050 | if (~i) arr.splice(i, 1); 1051 | this.el.className = arr.join(' '); 1052 | return this; 1053 | }; 1054 | 1055 | /** 1056 | * Remove all classes matching `re`. 1057 | * 1058 | * @param {RegExp} re 1059 | * @return {ClassList} 1060 | * @api private 1061 | */ 1062 | 1063 | ClassList.prototype.removeMatching = function(re){ 1064 | var arr = this.array(); 1065 | for (var i = 0; i < arr.length; i++) { 1066 | if (re.test(arr[i])) { 1067 | this.remove(arr[i]); 1068 | } 1069 | } 1070 | return this; 1071 | }; 1072 | 1073 | /** 1074 | * Toggle class `name`, can force state via `force`. 1075 | * 1076 | * For browsers that support classList, but do not support `force` yet, 1077 | * the mistake will be detected and corrected. 1078 | * 1079 | * @param {String} name 1080 | * @param {Boolean} force 1081 | * @return {ClassList} 1082 | * @api public 1083 | */ 1084 | 1085 | ClassList.prototype.toggle = function(name, force){ 1086 | // classList 1087 | if (this.list) { 1088 | if ("undefined" !== typeof force) { 1089 | if (force !== this.list.toggle(name, force)) { 1090 | this.list.toggle(name); // toggle again to correct 1091 | } 1092 | } else { 1093 | this.list.toggle(name); 1094 | } 1095 | return this; 1096 | } 1097 | 1098 | // fallback 1099 | if ("undefined" !== typeof force) { 1100 | if (!force) { 1101 | this.remove(name); 1102 | } else { 1103 | this.add(name); 1104 | } 1105 | } else { 1106 | if (this.has(name)) { 1107 | this.remove(name); 1108 | } else { 1109 | this.add(name); 1110 | } 1111 | } 1112 | 1113 | return this; 1114 | }; 1115 | 1116 | /** 1117 | * Return an array of classes. 1118 | * 1119 | * @return {Array} 1120 | * @api public 1121 | */ 1122 | 1123 | ClassList.prototype.array = function(){ 1124 | var className = this.el.getAttribute('class') || ''; 1125 | var str = className.replace(/^\s+|\s+$/g, ''); 1126 | var arr = str.split(re); 1127 | if ('' === arr[0]) arr.shift(); 1128 | return arr; 1129 | }; 1130 | 1131 | /** 1132 | * Check if class `name` is present. 1133 | * 1134 | * @param {String} name 1135 | * @return {ClassList} 1136 | * @api public 1137 | */ 1138 | 1139 | ClassList.prototype.has = 1140 | ClassList.prototype.contains = function(name){ 1141 | return this.list 1142 | ? this.list.contains(name) 1143 | : !! ~index(this.array(), name); 1144 | }; 1145 | 1146 | },{"indexof":14}],14:[function(require,module,exports){ 1147 | module.exports = function(arr, obj){ 1148 | if (arr.indexOf) return arr.indexOf(obj); 1149 | for (var i = 0; i < arr.length; ++i) { 1150 | if (arr[i] === obj) return i; 1151 | } 1152 | return -1; 1153 | }; 1154 | },{}],15:[function(require,module,exports){ 1155 | 1156 | /** 1157 | * Module dependencies. 1158 | */ 1159 | 1160 | var events = require('event'); 1161 | var delegate = require('delegate'); 1162 | 1163 | /** 1164 | * Expose `Events`. 1165 | */ 1166 | 1167 | module.exports = Events; 1168 | 1169 | /** 1170 | * Initialize an `Events` with the given 1171 | * `el` object which events will be bound to, 1172 | * and the `obj` which will receive method calls. 1173 | * 1174 | * @param {Object} el 1175 | * @param {Object} obj 1176 | * @api public 1177 | */ 1178 | 1179 | function Events(el, obj) { 1180 | if (!(this instanceof Events)) return new Events(el, obj); 1181 | if (!el) throw new Error('element required'); 1182 | if (!obj) throw new Error('object required'); 1183 | this.el = el; 1184 | this.obj = obj; 1185 | this._events = {}; 1186 | } 1187 | 1188 | /** 1189 | * Subscription helper. 1190 | */ 1191 | 1192 | Events.prototype.sub = function(event, method, cb){ 1193 | this._events[event] = this._events[event] || {}; 1194 | this._events[event][method] = cb; 1195 | }; 1196 | 1197 | /** 1198 | * Bind to `event` with optional `method` name. 1199 | * When `method` is undefined it becomes `event` 1200 | * with the "on" prefix. 1201 | * 1202 | * Examples: 1203 | * 1204 | * Direct event handling: 1205 | * 1206 | * events.bind('click') // implies "onclick" 1207 | * events.bind('click', 'remove') 1208 | * events.bind('click', 'sort', 'asc') 1209 | * 1210 | * Delegated event handling: 1211 | * 1212 | * events.bind('click li > a') 1213 | * events.bind('click li > a', 'remove') 1214 | * events.bind('click a.sort-ascending', 'sort', 'asc') 1215 | * events.bind('click a.sort-descending', 'sort', 'desc') 1216 | * 1217 | * @param {String} event 1218 | * @param {String|function} [method] 1219 | * @return {Function} callback 1220 | * @api public 1221 | */ 1222 | 1223 | Events.prototype.bind = function(event, method){ 1224 | var e = parse(event); 1225 | var el = this.el; 1226 | var obj = this.obj; 1227 | var name = e.name; 1228 | var method = method || 'on' + name; 1229 | var args = [].slice.call(arguments, 2); 1230 | 1231 | // callback 1232 | function cb(){ 1233 | var a = [].slice.call(arguments).concat(args); 1234 | obj[method].apply(obj, a); 1235 | } 1236 | 1237 | // bind 1238 | if (e.selector) { 1239 | cb = delegate.bind(el, e.selector, name, cb); 1240 | } else { 1241 | events.bind(el, name, cb); 1242 | } 1243 | 1244 | // subscription for unbinding 1245 | this.sub(name, method, cb); 1246 | 1247 | return cb; 1248 | }; 1249 | 1250 | /** 1251 | * Unbind a single binding, all bindings for `event`, 1252 | * or all bindings within the manager. 1253 | * 1254 | * Examples: 1255 | * 1256 | * Unbind direct handlers: 1257 | * 1258 | * events.unbind('click', 'remove') 1259 | * events.unbind('click') 1260 | * events.unbind() 1261 | * 1262 | * Unbind delegate handlers: 1263 | * 1264 | * events.unbind('click', 'remove') 1265 | * events.unbind('click') 1266 | * events.unbind() 1267 | * 1268 | * @param {String|Function} [event] 1269 | * @param {String|Function} [method] 1270 | * @api public 1271 | */ 1272 | 1273 | Events.prototype.unbind = function(event, method){ 1274 | if (0 == arguments.length) return this.unbindAll(); 1275 | if (1 == arguments.length) return this.unbindAllOf(event); 1276 | 1277 | // no bindings for this event 1278 | var bindings = this._events[event]; 1279 | if (!bindings) return; 1280 | 1281 | // no bindings for this method 1282 | var cb = bindings[method]; 1283 | if (!cb) return; 1284 | 1285 | events.unbind(this.el, event, cb); 1286 | }; 1287 | 1288 | /** 1289 | * Unbind all events. 1290 | * 1291 | * @api private 1292 | */ 1293 | 1294 | Events.prototype.unbindAll = function(){ 1295 | for (var event in this._events) { 1296 | this.unbindAllOf(event); 1297 | } 1298 | }; 1299 | 1300 | /** 1301 | * Unbind all events for `event`. 1302 | * 1303 | * @param {String} event 1304 | * @api private 1305 | */ 1306 | 1307 | Events.prototype.unbindAllOf = function(event){ 1308 | var bindings = this._events[event]; 1309 | if (!bindings) return; 1310 | 1311 | for (var method in bindings) { 1312 | this.unbind(event, method); 1313 | } 1314 | }; 1315 | 1316 | /** 1317 | * Parse `event`. 1318 | * 1319 | * @param {String} event 1320 | * @return {Object} 1321 | * @api private 1322 | */ 1323 | 1324 | function parse(event) { 1325 | var parts = event.split(/ +/); 1326 | return { 1327 | name: parts.shift(), 1328 | selector: parts.join(' ') 1329 | } 1330 | } 1331 | 1332 | },{"delegate":16,"event":20}],16:[function(require,module,exports){ 1333 | /** 1334 | * Module dependencies. 1335 | */ 1336 | 1337 | var closest = require('closest') 1338 | , event = require('event'); 1339 | 1340 | /** 1341 | * Delegate event `type` to `selector` 1342 | * and invoke `fn(e)`. A callback function 1343 | * is returned which may be passed to `.unbind()`. 1344 | * 1345 | * @param {Element} el 1346 | * @param {String} selector 1347 | * @param {String} type 1348 | * @param {Function} fn 1349 | * @param {Boolean} capture 1350 | * @return {Function} 1351 | * @api public 1352 | */ 1353 | 1354 | exports.bind = function(el, selector, type, fn, capture){ 1355 | return event.bind(el, type, function(e){ 1356 | var target = e.target || e.srcElement; 1357 | e.delegateTarget = closest(target, selector, true, el); 1358 | if (e.delegateTarget) fn.call(el, e); 1359 | }, capture); 1360 | }; 1361 | 1362 | /** 1363 | * Unbind event `type`'s callback `fn`. 1364 | * 1365 | * @param {Element} el 1366 | * @param {String} type 1367 | * @param {Function} fn 1368 | * @param {Boolean} capture 1369 | * @api public 1370 | */ 1371 | 1372 | exports.unbind = function(el, type, fn, capture){ 1373 | event.unbind(el, type, fn, capture); 1374 | }; 1375 | 1376 | },{"closest":17,"event":20}],17:[function(require,module,exports){ 1377 | /** 1378 | * Module Dependencies 1379 | */ 1380 | 1381 | var matches = require('matches-selector') 1382 | 1383 | /** 1384 | * Export `closest` 1385 | */ 1386 | 1387 | module.exports = closest 1388 | 1389 | /** 1390 | * Closest 1391 | * 1392 | * @param {Element} el 1393 | * @param {String} selector 1394 | * @param {Element} scope (optional) 1395 | */ 1396 | 1397 | function closest (el, selector, scope) { 1398 | scope = scope || document.documentElement; 1399 | 1400 | // walk up the dom 1401 | while (el && el !== scope) { 1402 | if (matches(el, selector)) return el; 1403 | el = el.parentNode; 1404 | } 1405 | 1406 | // check scope for match 1407 | return matches(el, selector) ? el : null; 1408 | } 1409 | 1410 | },{"matches-selector":18}],18:[function(require,module,exports){ 1411 | /** 1412 | * Module dependencies. 1413 | */ 1414 | 1415 | var query = require('query'); 1416 | 1417 | /** 1418 | * Element prototype. 1419 | */ 1420 | 1421 | var proto = Element.prototype; 1422 | 1423 | /** 1424 | * Vendor function. 1425 | */ 1426 | 1427 | var vendor = proto.matches 1428 | || proto.webkitMatchesSelector 1429 | || proto.mozMatchesSelector 1430 | || proto.msMatchesSelector 1431 | || proto.oMatchesSelector; 1432 | 1433 | /** 1434 | * Expose `match()`. 1435 | */ 1436 | 1437 | module.exports = match; 1438 | 1439 | /** 1440 | * Match `el` to `selector`. 1441 | * 1442 | * @param {Element} el 1443 | * @param {String} selector 1444 | * @return {Boolean} 1445 | * @api public 1446 | */ 1447 | 1448 | function match(el, selector) { 1449 | if (!el || el.nodeType !== 1) return false; 1450 | if (vendor) return vendor.call(el, selector); 1451 | var nodes = query.all(selector, el.parentNode); 1452 | for (var i = 0; i < nodes.length; ++i) { 1453 | if (nodes[i] == el) return true; 1454 | } 1455 | return false; 1456 | } 1457 | 1458 | },{"query":19}],19:[function(require,module,exports){ 1459 | function one(selector, el) { 1460 | return el.querySelector(selector); 1461 | } 1462 | 1463 | exports = module.exports = function(selector, el){ 1464 | el = el || document; 1465 | return one(selector, el); 1466 | }; 1467 | 1468 | exports.all = function(selector, el){ 1469 | el = el || document; 1470 | return el.querySelectorAll(selector); 1471 | }; 1472 | 1473 | exports.engine = function(obj){ 1474 | if (!obj.one) throw new Error('.one callback required'); 1475 | if (!obj.all) throw new Error('.all callback required'); 1476 | one = obj.one; 1477 | exports.all = obj.all; 1478 | return exports; 1479 | }; 1480 | 1481 | },{}],20:[function(require,module,exports){ 1482 | var bind = window.addEventListener ? 'addEventListener' : 'attachEvent', 1483 | unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent', 1484 | prefix = bind !== 'addEventListener' ? 'on' : ''; 1485 | 1486 | /** 1487 | * Bind `el` event `type` to `fn`. 1488 | * 1489 | * @param {Element} el 1490 | * @param {String} type 1491 | * @param {Function} fn 1492 | * @param {Boolean} capture 1493 | * @return {Function} 1494 | * @api public 1495 | */ 1496 | 1497 | exports.bind = function(el, type, fn, capture){ 1498 | el[bind](prefix + type, fn, capture || false); 1499 | return fn; 1500 | }; 1501 | 1502 | /** 1503 | * Unbind `el` event `type`'s callback `fn`. 1504 | * 1505 | * @param {Element} el 1506 | * @param {String} type 1507 | * @param {Function} fn 1508 | * @param {Boolean} capture 1509 | * @return {Function} 1510 | * @api public 1511 | */ 1512 | 1513 | exports.unbind = function(el, type, fn, capture){ 1514 | el[unbind](prefix + type, fn, capture || false); 1515 | return fn; 1516 | }; 1517 | },{}],21:[function(require,module,exports){ 1518 | 1519 | /** 1520 | * Expose `parse`. 1521 | */ 1522 | 1523 | module.exports = parse; 1524 | 1525 | /** 1526 | * Tests for browser support. 1527 | */ 1528 | 1529 | var innerHTMLBug = false; 1530 | var bugTestDiv; 1531 | if (typeof document !== 'undefined') { 1532 | bugTestDiv = document.createElement('div'); 1533 | // Setup 1534 | bugTestDiv.innerHTML = '
a'; 1535 | // Make sure that link elements get serialized correctly by innerHTML 1536 | // This requires a wrapper element in IE 1537 | innerHTMLBug = !bugTestDiv.getElementsByTagName('link').length; 1538 | bugTestDiv = undefined; 1539 | } 1540 | 1541 | /** 1542 | * Wrap map from jquery. 1543 | */ 1544 | 1545 | var map = { 1546 | legend: [1, '
', '
'], 1547 | tr: [2, '', '
'], 1548 | col: [2, '', '
'], 1549 | // for script/link/style tags to work in IE6-8, you have to wrap 1550 | // in a div with a non-whitespace character in front, ha! 1551 | _default: innerHTMLBug ? [1, 'X
', '
'] : [0, '', ''] 1552 | }; 1553 | 1554 | map.td = 1555 | map.th = [3, '', '
']; 1556 | 1557 | map.option = 1558 | map.optgroup = [1, '']; 1559 | 1560 | map.thead = 1561 | map.tbody = 1562 | map.colgroup = 1563 | map.caption = 1564 | map.tfoot = [1, '', '
']; 1565 | 1566 | map.polyline = 1567 | map.ellipse = 1568 | map.polygon = 1569 | map.circle = 1570 | map.text = 1571 | map.line = 1572 | map.path = 1573 | map.rect = 1574 | map.g = [1, '','']; 1575 | 1576 | /** 1577 | * Parse `html` and return a DOM Node instance, which could be a TextNode, 1578 | * HTML DOM Node of some kind (
for example), or a DocumentFragment 1579 | * instance, depending on the contents of the `html` string. 1580 | * 1581 | * @param {String} html - HTML string to "domify" 1582 | * @param {Document} doc - The `document` instance to create the Node for 1583 | * @return {DOMNode} the TextNode, DOM Node, or DocumentFragment instance 1584 | * @api private 1585 | */ 1586 | 1587 | function parse(html, doc) { 1588 | if ('string' != typeof html) throw new TypeError('String expected'); 1589 | 1590 | // default to the global `document` object 1591 | if (!doc) doc = document; 1592 | 1593 | // tag name 1594 | var m = /<([\w:]+)/.exec(html); 1595 | if (!m) return doc.createTextNode(html); 1596 | 1597 | html = html.replace(/^\s+|\s+$/g, ''); // Remove leading/trailing whitespace 1598 | 1599 | var tag = m[1]; 1600 | 1601 | // body support 1602 | if (tag == 'body') { 1603 | var el = doc.createElement('html'); 1604 | el.innerHTML = html; 1605 | return el.removeChild(el.lastChild); 1606 | } 1607 | 1608 | // wrap map 1609 | var wrap = map[tag] || map._default; 1610 | var depth = wrap[0]; 1611 | var prefix = wrap[1]; 1612 | var suffix = wrap[2]; 1613 | var el = doc.createElement('div'); 1614 | el.innerHTML = prefix + html + suffix; 1615 | while (depth--) el = el.lastChild; 1616 | 1617 | // one element 1618 | if (el.firstChild == el.lastChild) { 1619 | return el.removeChild(el.firstChild); 1620 | } 1621 | 1622 | // several elements 1623 | var fragment = doc.createDocumentFragment(); 1624 | while (el.firstChild) { 1625 | fragment.appendChild(el.removeChild(el.firstChild)); 1626 | } 1627 | 1628 | return fragment; 1629 | } 1630 | 1631 | },{}],22:[function(require,module,exports){ 1632 | 1633 | module.exports = inserted; 1634 | 1635 | /** 1636 | * Watch for removal with a DOM3 Mutation Event. 1637 | * 1638 | * @param {Element} el 1639 | * @param {Function} fn 1640 | * @api private 1641 | */ 1642 | 1643 | function inserted(el, fn) { 1644 | function cb(mutationEvent) { 1645 | var target = mutationEvent.target 1646 | , children = target.getElementsByTagName ? [].slice.call(target.getElementsByTagName('*')) : []; 1647 | 1648 | if (el === target || ~children.indexOf(el)) { 1649 | fn(el); 1650 | document.removeEventListener('DOMNodeInserted', cb); 1651 | } 1652 | } 1653 | 1654 | document.addEventListener('DOMNodeInserted', cb); 1655 | } 1656 | 1657 | },{}],23:[function(require,module,exports){ 1658 | 1659 | /** 1660 | * Module dependencies. 1661 | */ 1662 | 1663 | var withinDoc = require('within-document') 1664 | , Observer = require('mutation-observer'); 1665 | 1666 | /** 1667 | * Expose `inserted`. 1668 | */ 1669 | 1670 | module.exports = inserted; 1671 | 1672 | /** 1673 | * Watched elements. 1674 | * 1675 | * @api private 1676 | */ 1677 | 1678 | var watched = []; 1679 | 1680 | /** 1681 | * Set up observer. 1682 | * 1683 | * @api private 1684 | */ 1685 | 1686 | var observer = new Observer(onchanges); 1687 | 1688 | /** 1689 | * Generic observer callback. 1690 | * 1691 | * @api private 1692 | */ 1693 | 1694 | function onchanges(changes){ 1695 | // keep track of number of found els 1696 | var found = 0; 1697 | 1698 | for (var i = 0, l = changes.length; i < l; i++) { 1699 | if (changes[i].addedNodes.length) { 1700 | // allow for manipulation of `watched` 1701 | // from within the callback 1702 | var w = watched.slice(); 1703 | 1704 | for (var i2 = 0, l2 = w.length; i2 < l2; i2++) { 1705 | var el = w[i2][0]; 1706 | 1707 | // check if the added element is the same 1708 | // or that it's now part of the document 1709 | if (withinDoc(el)) { 1710 | watched.splice(i2 - found++, 1)[0][1](); 1711 | 1712 | // abort if nothing else left to watch 1713 | if (!watched.length) observer.disconnect(); 1714 | } 1715 | } 1716 | 1717 | // we only need to loop through watched els once 1718 | break; 1719 | } 1720 | } 1721 | } 1722 | 1723 | /** 1724 | * Starts observing the DOM. 1725 | * 1726 | * @api private 1727 | */ 1728 | 1729 | function observe(){ 1730 | var html = document.documentElement; 1731 | observer.observe(html, { 1732 | subtree: true, 1733 | childList: true 1734 | }); 1735 | } 1736 | 1737 | /** 1738 | * Watches for insertion of `el` into DOM. 1739 | * 1740 | * @param {Element} el 1741 | * @param {Function} fn 1742 | * @api private 1743 | */ 1744 | 1745 | function inserted(el, fn){ 1746 | // reattach observer if we weren't watching 1747 | if (!watched.length) observe(); 1748 | 1749 | // we add it to the list of elements to check 1750 | watched.push([el, fn]); 1751 | } 1752 | 1753 | },{"mutation-observer":26,"within-document":30}],24:[function(require,module,exports){ 1754 | 1755 | /** 1756 | * Module dependencies. 1757 | */ 1758 | 1759 | var withinDocument = require('within-document'); 1760 | 1761 | /** 1762 | * Expose `inserted`. 1763 | */ 1764 | 1765 | exports = module.exports = inserted; 1766 | 1767 | /** 1768 | * Default interval. 1769 | */ 1770 | 1771 | exports.interval = 200; 1772 | 1773 | /** 1774 | * Watch for removal and invoke `fn(el)`. 1775 | * 1776 | * @param {Element} el 1777 | * @param {Function} fn 1778 | * @api public 1779 | */ 1780 | 1781 | function inserted(el, fn){ 1782 | interval(el, fn); 1783 | } 1784 | 1785 | /** 1786 | * Watch for removal with an interval. 1787 | * 1788 | * @param {Element} el 1789 | * @param {Function} fn 1790 | * @api private 1791 | */ 1792 | 1793 | function interval(el, fn) { 1794 | var id = setInterval(function(){ 1795 | if (!withinDocument(el)) return; 1796 | clearInterval(id); 1797 | fn(el); 1798 | }, exports.interval); 1799 | } 1800 | 1801 | },{"within-document":30}],25:[function(require,module,exports){ 1802 | 1803 | /** 1804 | * Module dependencies. 1805 | */ 1806 | 1807 | var Observer = require('mutation-observer'); 1808 | 1809 | /** 1810 | * Exports the `MutationObserver` based approach 1811 | * or the fallback one depending on UA capabilities. 1812 | */ 1813 | 1814 | module.exports = Observer 1815 | ? require('./dom4') 1816 | : document.addEventListener 1817 | ? require('./dom3') 1818 | : require('./fallback'); 1819 | 1820 | },{"./dom3":22,"./dom4":23,"./fallback":24,"mutation-observer":26}],26:[function(require,module,exports){ 1821 | var MutationObserver = window.MutationObserver 1822 | || window.WebKitMutationObserver 1823 | || window.MozMutationObserver; 1824 | 1825 | /* 1826 | * Copyright 2012 The Polymer Authors. All rights reserved. 1827 | * Use of this source code is goverened by a BSD-style 1828 | * license that can be found in the LICENSE file. 1829 | */ 1830 | 1831 | var WeakMap = window.WeakMap; 1832 | 1833 | if (typeof WeakMap === 'undefined') { 1834 | var defineProperty = Object.defineProperty; 1835 | var counter = Date.now() % 1e9; 1836 | 1837 | WeakMap = function() { 1838 | this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__'); 1839 | }; 1840 | 1841 | WeakMap.prototype = { 1842 | set: function(key, value) { 1843 | var entry = key[this.name]; 1844 | if (entry && entry[0] === key) 1845 | entry[1] = value; 1846 | else 1847 | defineProperty(key, this.name, {value: [key, value], writable: true}); 1848 | return this; 1849 | }, 1850 | get: function(key) { 1851 | var entry; 1852 | return (entry = key[this.name]) && entry[0] === key ? 1853 | entry[1] : undefined; 1854 | }, 1855 | 'delete': function(key) { 1856 | var entry = key[this.name]; 1857 | if (!entry) return false; 1858 | var hasValue = entry[0] === key; 1859 | entry[0] = entry[1] = undefined; 1860 | return hasValue; 1861 | }, 1862 | has: function(key) { 1863 | var entry = key[this.name]; 1864 | if (!entry) return false; 1865 | return entry[0] === key; 1866 | } 1867 | }; 1868 | } 1869 | 1870 | var registrationsTable = new WeakMap(); 1871 | 1872 | // We use setImmediate or postMessage for our future callback. 1873 | var setImmediate = window.msSetImmediate; 1874 | 1875 | // Use post message to emulate setImmediate. 1876 | if (!setImmediate) { 1877 | var setImmediateQueue = []; 1878 | var sentinel = String(Math.random()); 1879 | window.addEventListener('message', function(e) { 1880 | if (e.data === sentinel) { 1881 | var queue = setImmediateQueue; 1882 | setImmediateQueue = []; 1883 | queue.forEach(function(func) { 1884 | func(); 1885 | }); 1886 | } 1887 | }); 1888 | setImmediate = function(func) { 1889 | setImmediateQueue.push(func); 1890 | window.postMessage(sentinel, '*'); 1891 | }; 1892 | } 1893 | 1894 | // This is used to ensure that we never schedule 2 callas to setImmediate 1895 | var isScheduled = false; 1896 | 1897 | // Keep track of observers that needs to be notified next time. 1898 | var scheduledObservers = []; 1899 | 1900 | /** 1901 | * Schedules |dispatchCallback| to be called in the future. 1902 | * @param {MutationObserver} observer 1903 | */ 1904 | function scheduleCallback(observer) { 1905 | scheduledObservers.push(observer); 1906 | if (!isScheduled) { 1907 | isScheduled = true; 1908 | setImmediate(dispatchCallbacks); 1909 | } 1910 | } 1911 | 1912 | function wrapIfNeeded(node) { 1913 | return window.ShadowDOMPolyfill && 1914 | window.ShadowDOMPolyfill.wrapIfNeeded(node) || 1915 | node; 1916 | } 1917 | 1918 | function dispatchCallbacks() { 1919 | // http://dom.spec.whatwg.org/#mutation-observers 1920 | 1921 | isScheduled = false; // Used to allow a new setImmediate call above. 1922 | 1923 | var observers = scheduledObservers; 1924 | scheduledObservers = []; 1925 | // Sort observers based on their creation UID (incremental). 1926 | observers.sort(function(o1, o2) { 1927 | return o1.uid_ - o2.uid_; 1928 | }); 1929 | 1930 | var anyNonEmpty = false; 1931 | observers.forEach(function(observer) { 1932 | 1933 | // 2.1, 2.2 1934 | var queue = observer.takeRecords(); 1935 | // 2.3. Remove all transient registered observers whose observer is mo. 1936 | removeTransientObserversFor(observer); 1937 | 1938 | // 2.4 1939 | if (queue.length) { 1940 | observer.callback_(queue, observer); 1941 | anyNonEmpty = true; 1942 | } 1943 | }); 1944 | 1945 | // 3. 1946 | if (anyNonEmpty) 1947 | dispatchCallbacks(); 1948 | } 1949 | 1950 | function removeTransientObserversFor(observer) { 1951 | observer.nodes_.forEach(function(node) { 1952 | var registrations = registrationsTable.get(node); 1953 | if (!registrations) 1954 | return; 1955 | registrations.forEach(function(registration) { 1956 | if (registration.observer === observer) 1957 | registration.removeTransientObservers(); 1958 | }); 1959 | }); 1960 | } 1961 | 1962 | /** 1963 | * This function is used for the "For each registered observer observer (with 1964 | * observer's options as options) in target's list of registered observers, 1965 | * run these substeps:" and the "For each ancestor ancestor of target, and for 1966 | * each registered observer observer (with options options) in ancestor's list 1967 | * of registered observers, run these substeps:" part of the algorithms. The 1968 | * |options.subtree| is checked to ensure that the callback is called 1969 | * correctly. 1970 | * 1971 | * @param {Node} target 1972 | * @param {function(MutationObserverInit):MutationRecord} callback 1973 | */ 1974 | function forEachAncestorAndObserverEnqueueRecord(target, callback) { 1975 | for (var node = target; node; node = node.parentNode) { 1976 | var registrations = registrationsTable.get(node); 1977 | 1978 | if (registrations) { 1979 | for (var j = 0; j < registrations.length; j++) { 1980 | var registration = registrations[j]; 1981 | var options = registration.options; 1982 | 1983 | // Only target ignores subtree. 1984 | if (node !== target && !options.subtree) 1985 | continue; 1986 | 1987 | var record = callback(options); 1988 | if (record) 1989 | registration.enqueue(record); 1990 | } 1991 | } 1992 | } 1993 | } 1994 | 1995 | var uidCounter = 0; 1996 | 1997 | /** 1998 | * The class that maps to the DOM MutationObserver interface. 1999 | * @param {Function} callback. 2000 | * @constructor 2001 | */ 2002 | function JsMutationObserver(callback) { 2003 | this.callback_ = callback; 2004 | this.nodes_ = []; 2005 | this.records_ = []; 2006 | this.uid_ = ++uidCounter; 2007 | } 2008 | 2009 | JsMutationObserver.prototype = { 2010 | observe: function(target, options) { 2011 | target = wrapIfNeeded(target); 2012 | 2013 | // 1.1 2014 | if (!options.childList && !options.attributes && !options.characterData || 2015 | 2016 | // 1.2 2017 | options.attributeOldValue && !options.attributes || 2018 | 2019 | // 1.3 2020 | options.attributeFilter && options.attributeFilter.length && 2021 | !options.attributes || 2022 | 2023 | // 1.4 2024 | options.characterDataOldValue && !options.characterData) { 2025 | 2026 | throw new SyntaxError(); 2027 | } 2028 | 2029 | var registrations = registrationsTable.get(target); 2030 | if (!registrations) 2031 | registrationsTable.set(target, registrations = []); 2032 | 2033 | // 2 2034 | // If target's list of registered observers already includes a registered 2035 | // observer associated with the context object, replace that registered 2036 | // observer's options with options. 2037 | var registration; 2038 | for (var i = 0; i < registrations.length; i++) { 2039 | if (registrations[i].observer === this) { 2040 | registration = registrations[i]; 2041 | registration.removeListeners(); 2042 | registration.options = options; 2043 | break; 2044 | } 2045 | } 2046 | 2047 | // 3. 2048 | // Otherwise, add a new registered observer to target's list of registered 2049 | // observers with the context object as the observer and options as the 2050 | // options, and add target to context object's list of nodes on which it 2051 | // is registered. 2052 | if (!registration) { 2053 | registration = new Registration(this, target, options); 2054 | registrations.push(registration); 2055 | this.nodes_.push(target); 2056 | } 2057 | 2058 | registration.addListeners(); 2059 | }, 2060 | 2061 | disconnect: function() { 2062 | this.nodes_.forEach(function(node) { 2063 | var registrations = registrationsTable.get(node); 2064 | for (var i = 0; i < registrations.length; i++) { 2065 | var registration = registrations[i]; 2066 | if (registration.observer === this) { 2067 | registration.removeListeners(); 2068 | registrations.splice(i, 1); 2069 | // Each node can only have one registered observer associated with 2070 | // this observer. 2071 | break; 2072 | } 2073 | } 2074 | }, this); 2075 | this.records_ = []; 2076 | }, 2077 | 2078 | takeRecords: function() { 2079 | var copyOfRecords = this.records_; 2080 | this.records_ = []; 2081 | return copyOfRecords; 2082 | } 2083 | }; 2084 | 2085 | /** 2086 | * @param {string} type 2087 | * @param {Node} target 2088 | * @constructor 2089 | */ 2090 | function MutationRecord(type, target) { 2091 | this.type = type; 2092 | this.target = target; 2093 | this.addedNodes = []; 2094 | this.removedNodes = []; 2095 | this.previousSibling = null; 2096 | this.nextSibling = null; 2097 | this.attributeName = null; 2098 | this.attributeNamespace = null; 2099 | this.oldValue = null; 2100 | } 2101 | 2102 | function copyMutationRecord(original) { 2103 | var record = new MutationRecord(original.type, original.target); 2104 | record.addedNodes = original.addedNodes.slice(); 2105 | record.removedNodes = original.removedNodes.slice(); 2106 | record.previousSibling = original.previousSibling; 2107 | record.nextSibling = original.nextSibling; 2108 | record.attributeName = original.attributeName; 2109 | record.attributeNamespace = original.attributeNamespace; 2110 | record.oldValue = original.oldValue; 2111 | return record; 2112 | }; 2113 | 2114 | // We keep track of the two (possibly one) records used in a single mutation. 2115 | var currentRecord, recordWithOldValue; 2116 | 2117 | /** 2118 | * Creates a record without |oldValue| and caches it as |currentRecord| for 2119 | * later use. 2120 | * @param {string} oldValue 2121 | * @return {MutationRecord} 2122 | */ 2123 | function getRecord(type, target) { 2124 | return currentRecord = new MutationRecord(type, target); 2125 | } 2126 | 2127 | /** 2128 | * Gets or creates a record with |oldValue| based in the |currentRecord| 2129 | * @param {string} oldValue 2130 | * @return {MutationRecord} 2131 | */ 2132 | function getRecordWithOldValue(oldValue) { 2133 | if (recordWithOldValue) 2134 | return recordWithOldValue; 2135 | recordWithOldValue = copyMutationRecord(currentRecord); 2136 | recordWithOldValue.oldValue = oldValue; 2137 | return recordWithOldValue; 2138 | } 2139 | 2140 | function clearRecords() { 2141 | currentRecord = recordWithOldValue = undefined; 2142 | } 2143 | 2144 | /** 2145 | * @param {MutationRecord} record 2146 | * @return {boolean} Whether the record represents a record from the current 2147 | * mutation event. 2148 | */ 2149 | function recordRepresentsCurrentMutation(record) { 2150 | return record === recordWithOldValue || record === currentRecord; 2151 | } 2152 | 2153 | /** 2154 | * Selects which record, if any, to replace the last record in the queue. 2155 | * This returns |null| if no record should be replaced. 2156 | * 2157 | * @param {MutationRecord} lastRecord 2158 | * @param {MutationRecord} newRecord 2159 | * @param {MutationRecord} 2160 | */ 2161 | function selectRecord(lastRecord, newRecord) { 2162 | if (lastRecord === newRecord) 2163 | return lastRecord; 2164 | 2165 | // Check if the the record we are adding represents the same record. If 2166 | // so, we keep the one with the oldValue in it. 2167 | if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) 2168 | return recordWithOldValue; 2169 | 2170 | return null; 2171 | } 2172 | 2173 | /** 2174 | * Class used to represent a registered observer. 2175 | * @param {MutationObserver} observer 2176 | * @param {Node} target 2177 | * @param {MutationObserverInit} options 2178 | * @constructor 2179 | */ 2180 | function Registration(observer, target, options) { 2181 | this.observer = observer; 2182 | this.target = target; 2183 | this.options = options; 2184 | this.transientObservedNodes = []; 2185 | } 2186 | 2187 | Registration.prototype = { 2188 | enqueue: function(record) { 2189 | var records = this.observer.records_; 2190 | var length = records.length; 2191 | 2192 | // There are cases where we replace the last record with the new record. 2193 | // For example if the record represents the same mutation we need to use 2194 | // the one with the oldValue. If we get same record (this can happen as we 2195 | // walk up the tree) we ignore the new record. 2196 | if (records.length > 0) { 2197 | var lastRecord = records[length - 1]; 2198 | var recordToReplaceLast = selectRecord(lastRecord, record); 2199 | if (recordToReplaceLast) { 2200 | records[length - 1] = recordToReplaceLast; 2201 | return; 2202 | } 2203 | } else { 2204 | scheduleCallback(this.observer); 2205 | } 2206 | 2207 | records[length] = record; 2208 | }, 2209 | 2210 | addListeners: function() { 2211 | this.addListeners_(this.target); 2212 | }, 2213 | 2214 | addListeners_: function(node) { 2215 | var options = this.options; 2216 | if (options.attributes) 2217 | node.addEventListener('DOMAttrModified', this, true); 2218 | 2219 | if (options.characterData) 2220 | node.addEventListener('DOMCharacterDataModified', this, true); 2221 | 2222 | if (options.childList) 2223 | node.addEventListener('DOMNodeInserted', this, true); 2224 | 2225 | if (options.childList || options.subtree) 2226 | node.addEventListener('DOMNodeRemoved', this, true); 2227 | }, 2228 | 2229 | removeListeners: function() { 2230 | this.removeListeners_(this.target); 2231 | }, 2232 | 2233 | removeListeners_: function(node) { 2234 | var options = this.options; 2235 | if (options.attributes) 2236 | node.removeEventListener('DOMAttrModified', this, true); 2237 | 2238 | if (options.characterData) 2239 | node.removeEventListener('DOMCharacterDataModified', this, true); 2240 | 2241 | if (options.childList) 2242 | node.removeEventListener('DOMNodeInserted', this, true); 2243 | 2244 | if (options.childList || options.subtree) 2245 | node.removeEventListener('DOMNodeRemoved', this, true); 2246 | }, 2247 | 2248 | /** 2249 | * Adds a transient observer on node. The transient observer gets removed 2250 | * next time we deliver the change records. 2251 | * @param {Node} node 2252 | */ 2253 | addTransientObserver: function(node) { 2254 | // Don't add transient observers on the target itself. We already have all 2255 | // the required listeners set up on the target. 2256 | if (node === this.target) 2257 | return; 2258 | 2259 | this.addListeners_(node); 2260 | this.transientObservedNodes.push(node); 2261 | var registrations = registrationsTable.get(node); 2262 | if (!registrations) 2263 | registrationsTable.set(node, registrations = []); 2264 | 2265 | // We know that registrations does not contain this because we already 2266 | // checked if node === this.target. 2267 | registrations.push(this); 2268 | }, 2269 | 2270 | removeTransientObservers: function() { 2271 | var transientObservedNodes = this.transientObservedNodes; 2272 | this.transientObservedNodes = []; 2273 | 2274 | transientObservedNodes.forEach(function(node) { 2275 | // Transient observers are never added to the target. 2276 | this.removeListeners_(node); 2277 | 2278 | var registrations = registrationsTable.get(node); 2279 | for (var i = 0; i < registrations.length; i++) { 2280 | if (registrations[i] === this) { 2281 | registrations.splice(i, 1); 2282 | // Each node can only have one registered observer associated with 2283 | // this observer. 2284 | break; 2285 | } 2286 | } 2287 | }, this); 2288 | }, 2289 | 2290 | handleEvent: function(e) { 2291 | // Stop propagation since we are managing the propagation manually. 2292 | // This means that other mutation events on the page will not work 2293 | // correctly but that is by design. 2294 | e.stopImmediatePropagation(); 2295 | 2296 | switch (e.type) { 2297 | case 'DOMAttrModified': 2298 | // http://dom.spec.whatwg.org/#concept-mo-queue-attributes 2299 | 2300 | var name = e.attrName; 2301 | var namespace = e.relatedNode.namespaceURI; 2302 | var target = e.target; 2303 | 2304 | // 1. 2305 | var record = new getRecord('attributes', target); 2306 | record.attributeName = name; 2307 | record.attributeNamespace = namespace; 2308 | 2309 | // 2. 2310 | var oldValue = 2311 | e.attrChange === MutationEvent.ADDITION ? null : e.prevValue; 2312 | 2313 | forEachAncestorAndObserverEnqueueRecord(target, function(options) { 2314 | // 3.1, 4.2 2315 | if (!options.attributes) 2316 | return; 2317 | 2318 | // 3.2, 4.3 2319 | if (options.attributeFilter && options.attributeFilter.length && 2320 | options.attributeFilter.indexOf(name) === -1 && 2321 | options.attributeFilter.indexOf(namespace) === -1) { 2322 | return; 2323 | } 2324 | // 3.3, 4.4 2325 | if (options.attributeOldValue) 2326 | return getRecordWithOldValue(oldValue); 2327 | 2328 | // 3.4, 4.5 2329 | return record; 2330 | }); 2331 | 2332 | break; 2333 | 2334 | case 'DOMCharacterDataModified': 2335 | // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata 2336 | var target = e.target; 2337 | 2338 | // 1. 2339 | var record = getRecord('characterData', target); 2340 | 2341 | // 2. 2342 | var oldValue = e.prevValue; 2343 | 2344 | 2345 | forEachAncestorAndObserverEnqueueRecord(target, function(options) { 2346 | // 3.1, 4.2 2347 | if (!options.characterData) 2348 | return; 2349 | 2350 | // 3.2, 4.3 2351 | if (options.characterDataOldValue) 2352 | return getRecordWithOldValue(oldValue); 2353 | 2354 | // 3.3, 4.4 2355 | return record; 2356 | }); 2357 | 2358 | break; 2359 | 2360 | case 'DOMNodeRemoved': 2361 | this.addTransientObserver(e.target); 2362 | // Fall through. 2363 | case 'DOMNodeInserted': 2364 | // http://dom.spec.whatwg.org/#concept-mo-queue-childlist 2365 | var target = e.relatedNode; 2366 | var changedNode = e.target; 2367 | var addedNodes, removedNodes; 2368 | if (e.type === 'DOMNodeInserted') { 2369 | addedNodes = [changedNode]; 2370 | removedNodes = []; 2371 | } else { 2372 | 2373 | addedNodes = []; 2374 | removedNodes = [changedNode]; 2375 | } 2376 | var previousSibling = changedNode.previousSibling; 2377 | var nextSibling = changedNode.nextSibling; 2378 | 2379 | // 1. 2380 | var record = getRecord('childList', target); 2381 | record.addedNodes = addedNodes; 2382 | record.removedNodes = removedNodes; 2383 | record.previousSibling = previousSibling; 2384 | record.nextSibling = nextSibling; 2385 | 2386 | forEachAncestorAndObserverEnqueueRecord(target, function(options) { 2387 | // 2.1, 3.2 2388 | if (!options.childList) 2389 | return; 2390 | 2391 | // 2.2, 3.3 2392 | return record; 2393 | }); 2394 | 2395 | } 2396 | 2397 | clearRecords(); 2398 | } 2399 | }; 2400 | 2401 | if (!MutationObserver) { 2402 | MutationObserver = JsMutationObserver; 2403 | } 2404 | 2405 | module.exports = MutationObserver; 2406 | 2407 | },{}],27:[function(require,module,exports){ 2408 | 2409 | /** 2410 | * Module exports. 2411 | */ 2412 | 2413 | module.exports = loadStyles; 2414 | 2415 | /** 2416 | * Injects the CSS into the DOM node. 2417 | * 2418 | * @param {String} css CSS string to add to the