├── .editorconfig ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist ├── hint.css ├── hint.js ├── hint.min.css └── hint.min.js ├── gulpfile.js ├── package-lock.json ├── package.json ├── resources └── hint.png └── src ├── events.js ├── hint.js ├── hint.styl └── tooltip.styl /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | resources 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v3.0.0 Change of Name 2 | 3 | - Renamed to `@bevacqua/hint` 4 | 5 | # v2.0.0 Change of Stylus 6 | 7 | - Changed Stylus variables `ht-brown` and `ht-pink` into `ht-background` and `ht-border` respectively 8 | 9 | # v1.5.0 Boarding Pass 10 | 11 | - Remove `data-hint-before` in favor of reusing `data-hint` and the `hint-before` class 12 | - Remove `opacity` transitions in favor of `display: none` and `display: block`, fixing a content `overflow` bug 13 | - Introduced a JavaScript enhancement that allows hints to be constrained to the visible viewport 14 | - Moved `opacity` transition animation to JavaScript for better progressive enhancement 15 | 16 | # v1.3.1 Boring Panda 17 | 18 | - No delays on hide 19 | 20 | # v1.3.0 Obnoxious Boar 21 | 22 | - Introduced a `1s` delay before popping up hints 23 | 24 | # v1.2.2 Before Dawn 25 | 26 | - Introduce ability to add `:before` hints using `aria-label` 27 | 28 | # v1.2.1 Hot Pink 29 | 30 | - Changed prefix of color variables 31 | 32 | # v1.2.0 101 Dalmatians 33 | 34 | - Using `top: 101%` is better most of the time 35 | 36 | # v1.1.15 Nudity Is Bad 37 | 38 | - Include `nib` during stand-alone builds for cross-browser compatibility 39 | 40 | # v1.1.0 Accessible King 41 | 42 | - `aria-label` attributes also displayed as hints 43 | - Slight change to make responsiveness better around breakpoint 44 | 45 | # v1.0.2 Take a Hint 46 | 47 | - Initial Public Release 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2014 Nicolas Bevacqua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @bevacqua/hint 2 | 3 | > Awesome pure CSS tooltips at your fingertips 4 | 5 | # Install 6 | 7 | Using Bower 8 | 9 | ```shell 10 | bower install -S @bevacqua/hint 11 | ``` 12 | 13 | Using `npm` 14 | 15 | ```shell 16 | npm install -S @bevacqua/hint 17 | ``` 18 | 19 | # Usage 20 | 21 | Just give your elements a nice tooltip in HTML. When hovered, the hint will appear. 22 | 23 | ```html 24 | Foo Bar 25 | ``` 26 | 27 | You'll get a nice little tooltip. Remember to include the CSS in your styles! 28 | 29 | ![hint.png][1] 30 | 31 | By default, the `:after` pseudo-selector is used. This means you can use the `:before` pseudo-selector for your own purposes. If you want the `data-hint` to use `:before`, then you must use the `hint-before` class on the element as well. 32 | 33 | ```html 34 | Foo Bar 35 | ``` 36 | 37 | You can also use the [`aria-label`][2] attribute. 38 | 39 | ```html 40 | Foo Bar 41 | ``` 42 | 43 | If you want the `aria-label` hint to use `:before`, then you must use the `hint-before` class on the element as well. 44 | 45 | ```html 46 | Foo Bar 47 | ``` 48 | 49 | Hints have a `z-index` of `5000`. 50 | 51 | # JavaScript 52 | 53 | The CSS will only get us so far, and we must add a tiny bit of JavaScript if we want a little functionality. This is not critical to `@bevacqua/hint`, and is therefore considered an optional feature. The JavaScript code enables the following features. 54 | 55 | - Hints are docked to the visible viewport so that they aren't cut off when they're near the edge 56 | - If hints are even wider than the viewport itself, then they are rendered in multi-line, setting the max width to the viewport width 57 | - You can define a maximum width to avoid hard-to-read long hints in wide viewports 58 | - When the JavaScript snippet is used, hints transition into view a second after the target element is hovered 59 | 60 | To include the JavaScript, just use the following snippet if you're using CommonJS, or refer to the `dist` directory for the compiled distributions. 61 | 62 | ```js 63 | require('@bevacqua/hint'); 64 | ``` 65 | 66 | To set the maximum hint width, do: 67 | 68 | ```js 69 | require('@bevacqua/hint').maximumWidth = 650; 70 | ``` 71 | 72 | You can also set it to 'auto', which means the full viewport size will be used if the tooltip exceeds the viewport size in length. In practice, `'auto'` means `Infinity` will be used. The default `maximumWidth` value is `650` pixels wide. 73 | 74 | # License 75 | 76 | MIT 77 | 78 | [1]: http://i.imgur.com/EFP5j4E.png 79 | [2]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute 80 | -------------------------------------------------------------------------------- /dist/hint.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @bevacqua/hint - Awesome tooltips at your fingertips 3 | * @version v3.0.3 4 | * @link https://github.com/bevacqua/hint 5 | * @license MIT 6 | */ 7 | [aria-label], 8 | [data-hint] { 9 | position: relative; 10 | } 11 | [aria-label]:not(.hint-before):after, 12 | [data-hint]:not(.hint-before):after, 13 | [aria-label].hint-before:before, 14 | [data-hint].hint-before:before { 15 | display: none; 16 | position: absolute; 17 | top: 101%; 18 | left: 6px; 19 | z-index: 5000; 20 | pointer-events: none; 21 | padding: 8px 10px; 22 | line-height: 15px; 23 | white-space: nowrap; 24 | text-decoration: none; 25 | text-indent: 0; 26 | overflow: visible; 27 | font-size: 12px; 28 | font-weight: normal; 29 | color: #fff; 30 | text-shadow: 1px 0px 1px #888; 31 | background-color: #412917; 32 | border-left: 6px solid #d37092; 33 | border-radius: 2px; 34 | -webkit-box-shadow: 1px 2px 6px rgba(0,0,0,0.3); 35 | box-shadow: 1px 2px 6px rgba(0,0,0,0.3); 36 | } 37 | [aria-label]:not(.hint-before):hover:after, 38 | [data-hint]:not(.hint-before):hover:after, 39 | [aria-label].hint-before:hover:before, 40 | [data-hint].hint-before:hover:before { 41 | display: block; 42 | -webkit-transform: translateY(8px); 43 | -moz-transform: translateY(8px); 44 | -o-transform: translateY(8px); 45 | -ms-transform: translateY(8px); 46 | transform: translateY(8px); 47 | } 48 | [aria-label]:not(.hint-before):after, 49 | [aria-label].hint-before:before { 50 | content: attr(aria-label); 51 | } 52 | [data-hint]:not(.hint-before):after { 53 | content: attr(data-hint); 54 | } 55 | [data-hint].hint-before:before { 56 | content: attr(data-hint-before); 57 | } 58 | @media only print { 59 | [aria-label]:not(.hint-before):after, 60 | [data-hint]:not(.hint-before):after, 61 | [aria-label].hint-before:before, 62 | [data-hint].hint-before:before { 63 | display: none; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /dist/hint.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @bevacqua/hint - Awesome tooltips at your fingertips 3 | * @version v3.0.3 4 | * @link https://github.com/bevacqua/hint 5 | * @license MIT 6 | */ 7 | (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.hint = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i>> 0; 18 | if (typeof fn !== 'function') { 19 | throw new TypeError(); 20 | } 21 | 22 | var res = new Array(len); 23 | var ctx = arguments.length >= 2 ? arguments[1] : void 0; 24 | var i; 25 | for (i = 0; i < len; i++) { 26 | if (i in t) { 27 | res[i] = fn.call(ctx, t[i], i, t); 28 | } 29 | } 30 | 31 | return res; 32 | }; 33 | } 34 | 35 | },{}],2:[function(require,module,exports){ 36 | 'use strict'; 37 | 38 | require('./object-keys'); 39 | require('./array-map'); 40 | 41 | var camel = /([a-z])([A-Z])/g; 42 | var hyphens = '$1-$2'; 43 | var contexts = {}; 44 | 45 | function parseStyles (styles) { 46 | if (typeof styles === 'string') { 47 | return styles; 48 | } 49 | if (Object.prototype.toString.call(styles) !== '[object Object]') { 50 | return ''; 51 | } 52 | return Object.keys(styles).map(function (key) { 53 | var prop = key.replace(camel, hyphens).toLowerCase(); 54 | return prop + ':' + styles[key]; 55 | }).join(';'); 56 | } 57 | 58 | function context (name) { 59 | if (contexts[name]) { 60 | return contexts[name]; 61 | } 62 | var cache; 63 | var rules; 64 | var remove; 65 | 66 | function getStylesheet () { 67 | if (cache) { 68 | return cache; 69 | } 70 | var style = document.createElement('style'); 71 | document.body.appendChild(style); 72 | style.setAttribute('data-context', name); 73 | cache = document.styleSheets[document.styleSheets.length - 1]; 74 | rules = cache.cssRules ? 'cssRules' : 'rules'; 75 | remove = cache.removeRule ? 'removeRule' : 'deleteRule'; 76 | return cache; 77 | } 78 | 79 | function add (selector, styles) { 80 | var css = parseStyles(styles); 81 | var sheet = getStylesheet(); 82 | var len = sheet[rules].length; 83 | if (sheet.insertRule) { 84 | sheet.insertRule(selector + '{' + css + '}', len); 85 | } else if (sheet.addRule) { 86 | sheet.addRule(selector, css, len); 87 | } 88 | } 89 | 90 | function remove (selector) { 91 | var sheet = getStylesheet(); 92 | var length = sheet[rules].length; 93 | var i; 94 | for (i = length - 1; i >= 0; i--) { 95 | if (sheet[rules][i].selectorText === selector) { 96 | sheet[remove](i); 97 | } 98 | } 99 | } 100 | 101 | function clear () { 102 | var sheet = getStylesheet(); 103 | while (sheet[rules].length) { 104 | sheet[remove](0); 105 | } 106 | } 107 | 108 | add.clear = clear; 109 | add.remove = remove; 110 | contexts[name] = add; 111 | return contexts[name]; 112 | } 113 | 114 | var ctx = context('default'); 115 | ctx.context = context; 116 | module.exports = ctx; 117 | 118 | },{"./array-map":1,"./object-keys":3}],3:[function(require,module,exports){ 119 | if (!Object.keys) { 120 | Object.keys = (function () { 121 | 'use strict'; 122 | var hasOwnProperty = Object.prototype.hasOwnProperty; 123 | var hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'); 124 | var dontEnums = [ 125 | 'toString', 126 | 'toLocaleString', 127 | 'valueOf', 128 | 'hasOwnProperty', 129 | 'isPrototypeOf', 130 | 'propertyIsEnumerable', 131 | 'constructor' 132 | ]; 133 | var dontEnumsLength = dontEnums.length; 134 | 135 | return function (obj) { 136 | if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) { 137 | throw new TypeError('Object.keys called on non-object'); 138 | } 139 | 140 | var result = []; 141 | var prop; 142 | var i; 143 | 144 | for (prop in obj) { 145 | if (hasOwnProperty.call(obj, prop)) { 146 | result.push(prop); 147 | } 148 | } 149 | 150 | if (hasDontEnumBug) { 151 | for (i = 0; i < dontEnumsLength; i++) { 152 | if (hasOwnProperty.call(obj, dontEnums[i])) { 153 | result.push(dontEnums[i]); 154 | } 155 | } 156 | } 157 | return result; 158 | }; 159 | }()); 160 | } 161 | 162 | },{}],4:[function(require,module,exports){ 163 | 'use strict'; 164 | 165 | var addEvent = addEventEasy; 166 | var removeEvent = removeEventEasy; 167 | 168 | if (!window.addEventListener) { 169 | addEvent = addEventHard; 170 | } 171 | 172 | if (!window.removeEventListener) { 173 | removeEvent = removeEventHard; 174 | } 175 | 176 | function addEventEasy (element, evt, fn) { 177 | return element.addEventListener(evt, fn); 178 | } 179 | 180 | function addEventHard (element, evt, fn) { 181 | return element.attachEvent('on' + evt, function (e) { 182 | e = e || window.event; 183 | e.target = e.target || e.srcElement; 184 | e.preventDefault = e.preventDefault || function preventDefault () { e.returnValue = false; }; 185 | e.stopPropagation = e.stopPropagation || function stopPropagation () { e.cancelBubble = true; }; 186 | fn.call(element, e); 187 | }); 188 | } 189 | 190 | function removeEventEasy (element, evt, fn) { 191 | return element.removeEventListener(evt, fn); 192 | } 193 | 194 | function removeEventHard (element, evt, fn) { 195 | return element.detachEvent('on' + evt, fn); 196 | } 197 | 198 | module.exports = { 199 | add: addEvent, 200 | remove: removeEvent 201 | }; 202 | 203 | },{}],5:[function(require,module,exports){ 204 | 'use strict'; 205 | 206 | var insertRule = require('insert-rule').context('hint'); 207 | var events = require('./events'); 208 | var expando = 'hint-' + Date.now(); 209 | var api = { 210 | maximumWidth: 650 211 | }; 212 | 213 | events.add(document.body, 'mouseover', enter); 214 | events.add(document.body, 'mouseout', leave); 215 | 216 | function enter (e) { 217 | var prev = scan(e.fromElement); 218 | var elem = scan(e.target); 219 | if (elem && elem !== prev) { 220 | move(elem); 221 | } 222 | } 223 | 224 | function leave (e) { 225 | var next = scan(e.toElement); 226 | var elem = scan(e.target); 227 | if (elem && elem !== next) { 228 | clear(elem); 229 | } 230 | } 231 | 232 | function scan (elem) { 233 | while (elem) { 234 | if (probe(elem)) { 235 | return elem; 236 | } 237 | elem = elem.parentElement; 238 | } 239 | } 240 | 241 | function probe (elem) { 242 | return elem.getAttribute('aria-label') || elem.getAttribute('data-hint'); 243 | } 244 | 245 | function expandex () { 246 | return expando + Date.now(); 247 | } 248 | 249 | function i (px) { 250 | var raw = px.replace('px', ''); 251 | return parseInt(raw, 10); 252 | } 253 | 254 | function getPseudo (elem) { 255 | return elem.className.indexOf('hint-before') !== -1 ? ':before' : ':after'; 256 | } 257 | 258 | function move (elem) { 259 | var totalWidth = innerWidth; 260 | var pseudo = getPseudo(elem); 261 | var c = getComputedStyle(elem, pseudo); 262 | var existed = elem.getAttribute('id'); 263 | var id = existed || expandex(); 264 | if (id.indexOf(expando) === 0) { 265 | elem.setAttribute('id', id); 266 | } 267 | 268 | var selector = '#' + id + pseudo; 269 | var rect = elem.getBoundingClientRect(); 270 | var paddings = i(c.paddingLeft) + i(c.paddingRight) + i(c.marginLeft) + i(c.marginRight); 271 | var width; 272 | var left; 273 | var right = rect.left + i(c.width) + i(c.left) + paddings; 274 | var collapse; 275 | var offset; 276 | var margin = 20; 277 | var max = api.maximumWidth === 'auto' ? Infinity : api.maximumWidth; 278 | 279 | if (i(c.width) + margin > totalWidth) { 280 | collapse = totalWidth - margin > max; 281 | width = collapse ? max : totalWidth - margin; 282 | offset = collapse ? totalWidth - max - margin : 0; 283 | left = -(rect.left + paddings) + offset; 284 | 285 | insertRule(selector, { 286 | width: width + 'px', 287 | left: left - 15 + 'px', 288 | whiteSpace: 'inherit' 289 | }); 290 | } else if (right + margin > totalWidth) { 291 | insertRule(selector, { 292 | left: totalWidth - right + margin + 'px' 293 | }); 294 | } 295 | transparent(); 296 | setTimeout(opaque, 1000); 297 | 298 | function transparent () { 299 | insertRule(selector, { opacity: 0 }); 300 | } 301 | function opaque () { 302 | insertRule(selector, transition({ 303 | opacity: 1 304 | })); 305 | } 306 | function transition (css) { 307 | var rule = 'transition'; 308 | var prefixes = ['', '-webkit-', '-moz-', '-o-', '-ms-']; 309 | function add (prefix) { 310 | css[prefix + rule] = 'opacity 0.3s ease-in-out'; 311 | } 312 | prefixes.forEach(add); 313 | return css; 314 | } 315 | } 316 | 317 | function clear (elem) { 318 | var pseudo = getPseudo(elem); 319 | var id = elem.id; 320 | if (id.indexOf(expando) === 0) { 321 | elem.removeAttribute('id'); 322 | } 323 | var selector = '#' + id + pseudo; 324 | insertRule.remove(selector); 325 | } 326 | 327 | module.exports = api; 328 | 329 | },{"./events":4,"insert-rule":2}]},{},[5])(5) 330 | }); 331 | 332 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJub2RlX21vZHVsZXMvaW5zZXJ0LXJ1bGUvc3JjL2FycmF5LW1hcC5qcyIsIm5vZGVfbW9kdWxlcy9pbnNlcnQtcnVsZS9zcmMvaW5zZXJ0UnVsZS5qcyIsIm5vZGVfbW9kdWxlcy9pbnNlcnQtcnVsZS9zcmMvb2JqZWN0LWtleXMuanMiLCJzcmMvZXZlbnRzLmpzIiwic3JjL2hpbnQuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2pGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMxQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdkNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbigpe2Z1bmN0aW9uIHIoZSxuLHQpe2Z1bmN0aW9uIG8oaSxmKXtpZighbltpXSl7aWYoIWVbaV0pe3ZhciBjPVwiZnVuY3Rpb25cIj09dHlwZW9mIHJlcXVpcmUmJnJlcXVpcmU7aWYoIWYmJmMpcmV0dXJuIGMoaSwhMCk7aWYodSlyZXR1cm4gdShpLCEwKTt2YXIgYT1uZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK2krXCInXCIpO3Rocm93IGEuY29kZT1cIk1PRFVMRV9OT1RfRk9VTkRcIixhfXZhciBwPW5baV09e2V4cG9ydHM6e319O2VbaV1bMF0uY2FsbChwLmV4cG9ydHMsZnVuY3Rpb24ocil7dmFyIG49ZVtpXVsxXVtyXTtyZXR1cm4gbyhufHxyKX0scCxwLmV4cG9ydHMscixlLG4sdCl9cmV0dXJuIG5baV0uZXhwb3J0c31mb3IodmFyIHU9XCJmdW5jdGlvblwiPT10eXBlb2YgcmVxdWlyZSYmcmVxdWlyZSxpPTA7aTx0Lmxlbmd0aDtpKyspbyh0W2ldKTtyZXR1cm4gb31yZXR1cm4gcn0pKCkiLCJpZiAoIUFycmF5LnByb3RvdHlwZS5tYXApIHtcbiAgQXJyYXkucHJvdG90eXBlLm1hcCA9IGZ1bmN0aW9uIChmbikge1xuICAgICd1c2Ugc3RyaWN0JztcblxuICAgIGlmICh0aGlzID09PSB2b2lkIDAgfHwgdGhpcyA9PT0gbnVsbCkge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcigpO1xuICAgIH1cblxuICAgIHZhciB0ID0gT2JqZWN0KHRoaXMpO1xuICAgIHZhciBsZW4gPSB0Lmxlbmd0aCA+Pj4gMDtcbiAgICBpZiAodHlwZW9mIGZuICE9PSAnZnVuY3Rpb24nKSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCk7XG4gICAgfVxuXG4gICAgdmFyIHJlcyA9IG5ldyBBcnJheShsZW4pO1xuICAgIHZhciBjdHggPSBhcmd1bWVudHMubGVuZ3RoID49IDIgPyBhcmd1bWVudHNbMV0gOiB2b2lkIDA7XG4gICAgdmFyIGk7XG4gICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICBpZiAoaSBpbiB0KSB7XG4gICAgICAgIHJlc1tpXSA9IGZuLmNhbGwoY3R4LCB0W2ldLCBpLCB0KTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gcmVzO1xuICB9O1xufVxuIiwiJ3VzZSBzdHJpY3QnO1xuXG5yZXF1aXJlKCcuL29iamVjdC1rZXlzJyk7XG5yZXF1aXJlKCcuL2FycmF5LW1hcCcpO1xuXG52YXIgY2FtZWwgPSAvKFthLXpdKShbQS1aXSkvZztcbnZhciBoeXBoZW5zID0gJyQxLSQyJztcbnZhciBjb250ZXh0cyA9IHt9O1xuXG5mdW5jdGlvbiBwYXJzZVN0eWxlcyAoc3R5bGVzKSB7XG4gIGlmICh0eXBlb2Ygc3R5bGVzID09PSAnc3RyaW5nJykge1xuICAgIHJldHVybiBzdHlsZXM7XG4gIH1cbiAgaWYgKE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChzdHlsZXMpICE9PSAnW29iamVjdCBPYmplY3RdJykge1xuICAgIHJldHVybiAnJztcbiAgfVxuICByZXR1cm4gT2JqZWN0LmtleXMoc3R5bGVzKS5tYXAoZnVuY3Rpb24gKGtleSkge1xuICAgIHZhciBwcm9wID0ga2V5LnJlcGxhY2UoY2FtZWwsIGh5cGhlbnMpLnRvTG93ZXJDYXNlKCk7XG4gICAgcmV0dXJuIHByb3AgKyAnOicgKyBzdHlsZXNba2V5XTtcbiAgfSkuam9pbignOycpO1xufVxuXG5mdW5jdGlvbiBjb250ZXh0IChuYW1lKSB7XG4gIGlmIChjb250ZXh0c1tuYW1lXSkge1xuICAgIHJldHVybiBjb250ZXh0c1tuYW1lXTtcbiAgfVxuICB2YXIgY2FjaGU7XG4gIHZhciBydWxlcztcbiAgdmFyIHJlbW92ZTtcblxuICBmdW5jdGlvbiBnZXRTdHlsZXNoZWV0ICgpIHtcbiAgICBpZiAoY2FjaGUpIHtcbiAgICAgIHJldHVybiBjYWNoZTtcbiAgICB9XG4gICAgdmFyIHN0eWxlID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3R5bGUnKTtcbiAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHN0eWxlKTtcbiAgICBzdHlsZS5zZXRBdHRyaWJ1dGUoJ2RhdGEtY29udGV4dCcsIG5hbWUpO1xuICAgIGNhY2hlID0gZG9jdW1lbnQuc3R5bGVTaGVldHNbZG9jdW1lbnQuc3R5bGVTaGVldHMubGVuZ3RoIC0gMV07XG4gICAgcnVsZXMgPSBjYWNoZS5jc3NSdWxlcyA/ICdjc3NSdWxlcycgOiAncnVsZXMnO1xuICAgIHJlbW92ZSA9IGNhY2hlLnJlbW92ZVJ1bGUgPyAncmVtb3ZlUnVsZScgOiAnZGVsZXRlUnVsZSc7XG4gICAgcmV0dXJuIGNhY2hlO1xuICB9XG5cbiAgZnVuY3Rpb24gYWRkIChzZWxlY3Rvciwgc3R5bGVzKSB7XG4gICAgdmFyIGNzcyA9IHBhcnNlU3R5bGVzKHN0eWxlcyk7XG4gICAgdmFyIHNoZWV0ID0gZ2V0U3R5bGVzaGVldCgpO1xuICAgIHZhciBsZW4gPSBzaGVldFtydWxlc10ubGVuZ3RoO1xuICAgIGlmIChzaGVldC5pbnNlcnRSdWxlKSB7XG4gICAgICBzaGVldC5pbnNlcnRSdWxlKHNlbGVjdG9yICsgJ3snICsgY3NzICsgJ30nLCBsZW4pO1xuICAgIH0gZWxzZSBpZiAoc2hlZXQuYWRkUnVsZSkge1xuICAgICAgc2hlZXQuYWRkUnVsZShzZWxlY3RvciwgY3NzLCBsZW4pO1xuICAgIH1cbiAgfVxuXG4gIGZ1bmN0aW9uIHJlbW92ZSAoc2VsZWN0b3IpIHtcbiAgICB2YXIgc2hlZXQgPSBnZXRTdHlsZXNoZWV0KCk7XG4gICAgdmFyIGxlbmd0aCA9IHNoZWV0W3J1bGVzXS5sZW5ndGg7XG4gICAgdmFyIGk7XG4gICAgZm9yIChpID0gbGVuZ3RoIC0gMTsgaSA+PSAwOyBpLS0pIHtcbiAgICAgIGlmIChzaGVldFtydWxlc11baV0uc2VsZWN0b3JUZXh0ID09PSBzZWxlY3Rvcikge1xuICAgICAgICBzaGVldFtyZW1vdmVdKGkpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIGZ1bmN0aW9uIGNsZWFyICgpIHtcbiAgICB2YXIgc2hlZXQgPSBnZXRTdHlsZXNoZWV0KCk7XG4gICAgd2hpbGUgKHNoZWV0W3J1bGVzXS5sZW5ndGgpIHtcbiAgICAgIHNoZWV0W3JlbW92ZV0oMCk7XG4gICAgfVxuICB9XG5cbiAgYWRkLmNsZWFyID0gY2xlYXI7XG4gIGFkZC5yZW1vdmUgPSByZW1vdmU7XG4gIGNvbnRleHRzW25hbWVdID0gYWRkO1xuICByZXR1cm4gY29udGV4dHNbbmFtZV07XG59XG5cbnZhciBjdHggPSBjb250ZXh0KCdkZWZhdWx0Jyk7XG5jdHguY29udGV4dCA9IGNvbnRleHQ7XG5tb2R1bGUuZXhwb3J0cyA9IGN0eDtcbiIsImlmICghT2JqZWN0LmtleXMpIHtcbiAgT2JqZWN0LmtleXMgPSAoZnVuY3Rpb24gKCkge1xuICAgICd1c2Ugc3RyaWN0JztcbiAgICB2YXIgaGFzT3duUHJvcGVydHkgPSBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5O1xuICAgIHZhciBoYXNEb250RW51bUJ1ZyA9ICEoe3RvU3RyaW5nOiBudWxsfSkucHJvcGVydHlJc0VudW1lcmFibGUoJ3RvU3RyaW5nJyk7XG4gICAgdmFyIGRvbnRFbnVtcyA9IFtcbiAgICAgICd0b1N0cmluZycsXG4gICAgICAndG9Mb2NhbGVTdHJpbmcnLFxuICAgICAgJ3ZhbHVlT2YnLFxuICAgICAgJ2hhc093blByb3BlcnR5JyxcbiAgICAgICdpc1Byb3RvdHlwZU9mJyxcbiAgICAgICdwcm9wZXJ0eUlzRW51bWVyYWJsZScsXG4gICAgICAnY29uc3RydWN0b3InXG4gICAgXTtcbiAgICB2YXIgZG9udEVudW1zTGVuZ3RoID0gZG9udEVudW1zLmxlbmd0aDtcblxuICAgIHJldHVybiBmdW5jdGlvbiAob2JqKSB7XG4gICAgICBpZiAodHlwZW9mIG9iaiAhPT0gJ29iamVjdCcgJiYgKHR5cGVvZiBvYmogIT09ICdmdW5jdGlvbicgfHwgb2JqID09PSBudWxsKSkge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdPYmplY3Qua2V5cyBjYWxsZWQgb24gbm9uLW9iamVjdCcpO1xuICAgICAgfVxuXG4gICAgICB2YXIgcmVzdWx0ID0gW107XG4gICAgICB2YXIgcHJvcDtcbiAgICAgIHZhciBpO1xuXG4gICAgICBmb3IgKHByb3AgaW4gb2JqKSB7XG4gICAgICAgIGlmIChoYXNPd25Qcm9wZXJ0eS5jYWxsKG9iaiwgcHJvcCkpIHtcbiAgICAgICAgICByZXN1bHQucHVzaChwcm9wKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBpZiAoaGFzRG9udEVudW1CdWcpIHtcbiAgICAgICAgZm9yIChpID0gMDsgaSA8IGRvbnRFbnVtc0xlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgaWYgKGhhc093blByb3BlcnR5LmNhbGwob2JqLCBkb250RW51bXNbaV0pKSB7XG4gICAgICAgICAgICByZXN1bHQucHVzaChkb250RW51bXNbaV0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9O1xuICB9KCkpO1xufVxuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgYWRkRXZlbnQgPSBhZGRFdmVudEVhc3k7XG52YXIgcmVtb3ZlRXZlbnQgPSByZW1vdmVFdmVudEVhc3k7XG5cbmlmICghd2luZG93LmFkZEV2ZW50TGlzdGVuZXIpIHtcbiAgYWRkRXZlbnQgPSBhZGRFdmVudEhhcmQ7XG59XG5cbmlmICghd2luZG93LnJlbW92ZUV2ZW50TGlzdGVuZXIpIHtcbiAgcmVtb3ZlRXZlbnQgPSByZW1vdmVFdmVudEhhcmQ7XG59XG5cbmZ1bmN0aW9uIGFkZEV2ZW50RWFzeSAoZWxlbWVudCwgZXZ0LCBmbikge1xuICByZXR1cm4gZWxlbWVudC5hZGRFdmVudExpc3RlbmVyKGV2dCwgZm4pO1xufVxuXG5mdW5jdGlvbiBhZGRFdmVudEhhcmQgKGVsZW1lbnQsIGV2dCwgZm4pIHtcbiAgcmV0dXJuIGVsZW1lbnQuYXR0YWNoRXZlbnQoJ29uJyArIGV2dCwgZnVuY3Rpb24gKGUpIHtcbiAgICBlID0gZSB8fCB3aW5kb3cuZXZlbnQ7XG4gICAgZS50YXJnZXQgPSBlLnRhcmdldCB8fCBlLnNyY0VsZW1lbnQ7XG4gICAgZS5wcmV2ZW50RGVmYXVsdCAgPSBlLnByZXZlbnREZWZhdWx0ICB8fCBmdW5jdGlvbiBwcmV2ZW50RGVmYXVsdCAoKSB7IGUucmV0dXJuVmFsdWUgPSBmYWxzZTsgfTtcbiAgICBlLnN0b3BQcm9wYWdhdGlvbiA9IGUuc3RvcFByb3BhZ2F0aW9uIHx8IGZ1bmN0aW9uIHN0b3BQcm9wYWdhdGlvbiAoKSB7IGUuY2FuY2VsQnViYmxlID0gdHJ1ZTsgfTtcbiAgICBmbi5jYWxsKGVsZW1lbnQsIGUpO1xuICB9KTtcbn1cblxuZnVuY3Rpb24gcmVtb3ZlRXZlbnRFYXN5IChlbGVtZW50LCBldnQsIGZuKSB7XG4gIHJldHVybiBlbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoZXZ0LCBmbik7XG59XG5cbmZ1bmN0aW9uIHJlbW92ZUV2ZW50SGFyZCAoZWxlbWVudCwgZXZ0LCBmbikge1xuICByZXR1cm4gZWxlbWVudC5kZXRhY2hFdmVudCgnb24nICsgZXZ0LCBmbik7XG59XG5cbm1vZHVsZS5leHBvcnRzID0ge1xuICBhZGQ6IGFkZEV2ZW50LFxuICByZW1vdmU6IHJlbW92ZUV2ZW50XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgaW5zZXJ0UnVsZSA9IHJlcXVpcmUoJ2luc2VydC1ydWxlJykuY29udGV4dCgnaGludCcpO1xudmFyIGV2ZW50cyA9IHJlcXVpcmUoJy4vZXZlbnRzJyk7XG52YXIgZXhwYW5kbyA9ICdoaW50LScgKyBEYXRlLm5vdygpO1xudmFyIGFwaSA9IHtcbiAgbWF4aW11bVdpZHRoOiA2NTBcbn07XG5cbmV2ZW50cy5hZGQoZG9jdW1lbnQuYm9keSwgJ21vdXNlb3ZlcicsIGVudGVyKTtcbmV2ZW50cy5hZGQoZG9jdW1lbnQuYm9keSwgJ21vdXNlb3V0JywgbGVhdmUpO1xuXG5mdW5jdGlvbiBlbnRlciAoZSkge1xuICB2YXIgcHJldiA9IHNjYW4oZS5mcm9tRWxlbWVudCk7XG4gIHZhciBlbGVtID0gc2NhbihlLnRhcmdldCk7XG4gIGlmIChlbGVtICYmIGVsZW0gIT09IHByZXYpIHtcbiAgICBtb3ZlKGVsZW0pO1xuICB9XG59XG5cbmZ1bmN0aW9uIGxlYXZlIChlKSB7XG4gIHZhciBuZXh0ID0gc2NhbihlLnRvRWxlbWVudCk7XG4gIHZhciBlbGVtID0gc2NhbihlLnRhcmdldCk7XG4gIGlmIChlbGVtICYmIGVsZW0gIT09IG5leHQpIHtcbiAgICBjbGVhcihlbGVtKTtcbiAgfVxufVxuXG5mdW5jdGlvbiBzY2FuIChlbGVtKSB7XG4gIHdoaWxlIChlbGVtKSB7XG4gICAgaWYgKHByb2JlKGVsZW0pKSB7XG4gICAgICByZXR1cm4gZWxlbTtcbiAgICB9XG4gICAgZWxlbSA9IGVsZW0ucGFyZW50RWxlbWVudDtcbiAgfVxufVxuXG5mdW5jdGlvbiBwcm9iZSAoZWxlbSkge1xuICByZXR1cm4gZWxlbS5nZXRBdHRyaWJ1dGUoJ2FyaWEtbGFiZWwnKSB8fCBlbGVtLmdldEF0dHJpYnV0ZSgnZGF0YS1oaW50Jyk7XG59XG5cbmZ1bmN0aW9uIGV4cGFuZGV4ICgpIHtcbiAgcmV0dXJuIGV4cGFuZG8gKyBEYXRlLm5vdygpO1xufVxuXG5mdW5jdGlvbiBpIChweCkge1xuICB2YXIgcmF3ID0gcHgucmVwbGFjZSgncHgnLCAnJyk7XG4gIHJldHVybiBwYXJzZUludChyYXcsIDEwKTtcbn1cblxuZnVuY3Rpb24gZ2V0UHNldWRvIChlbGVtKSB7XG4gIHJldHVybiBlbGVtLmNsYXNzTmFtZS5pbmRleE9mKCdoaW50LWJlZm9yZScpICE9PSAtMSA/ICc6YmVmb3JlJyA6ICc6YWZ0ZXInO1xufVxuXG5mdW5jdGlvbiBtb3ZlIChlbGVtKSB7XG4gIHZhciB0b3RhbFdpZHRoID0gaW5uZXJXaWR0aDtcbiAgdmFyIHBzZXVkbyA9IGdldFBzZXVkbyhlbGVtKTtcbiAgdmFyIGMgPSBnZXRDb21wdXRlZFN0eWxlKGVsZW0sIHBzZXVkbyk7XG4gIHZhciBleGlzdGVkID0gZWxlbS5nZXRBdHRyaWJ1dGUoJ2lkJyk7XG4gIHZhciBpZCA9IGV4aXN0ZWQgfHwgZXhwYW5kZXgoKTtcbiAgaWYgKGlkLmluZGV4T2YoZXhwYW5kbykgPT09IDApIHtcbiAgICBlbGVtLnNldEF0dHJpYnV0ZSgnaWQnLCBpZCk7XG4gIH1cblxuICB2YXIgc2VsZWN0b3IgPSAnIycgKyBpZCArIHBzZXVkbztcbiAgdmFyIHJlY3QgPSBlbGVtLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICB2YXIgcGFkZGluZ3MgPSBpKGMucGFkZGluZ0xlZnQpICsgaShjLnBhZGRpbmdSaWdodCkgKyBpKGMubWFyZ2luTGVmdCkgKyBpKGMubWFyZ2luUmlnaHQpO1xuICB2YXIgd2lkdGg7XG4gIHZhciBsZWZ0O1xuICB2YXIgcmlnaHQgPSByZWN0LmxlZnQgKyBpKGMud2lkdGgpICsgaShjLmxlZnQpICsgcGFkZGluZ3M7XG4gIHZhciBjb2xsYXBzZTtcbiAgdmFyIG9mZnNldDtcbiAgdmFyIG1hcmdpbiA9IDIwO1xuICB2YXIgbWF4ID0gYXBpLm1heGltdW1XaWR0aCA9PT0gJ2F1dG8nID8gSW5maW5pdHkgOiBhcGkubWF4aW11bVdpZHRoO1xuXG4gIGlmIChpKGMud2lkdGgpICsgbWFyZ2luID4gdG90YWxXaWR0aCkge1xuICAgIGNvbGxhcHNlID0gdG90YWxXaWR0aCAtIG1hcmdpbiA+IG1heDtcbiAgICB3aWR0aCA9IGNvbGxhcHNlID8gbWF4IDogdG90YWxXaWR0aCAtIG1hcmdpbjtcbiAgICBvZmZzZXQgPSBjb2xsYXBzZSA/IHRvdGFsV2lkdGggLSBtYXggLSBtYXJnaW4gOiAwO1xuICAgIGxlZnQgPSAtKHJlY3QubGVmdCArIHBhZGRpbmdzKSArIG9mZnNldDtcblxuICAgIGluc2VydFJ1bGUoc2VsZWN0b3IsIHtcbiAgICAgIHdpZHRoOiB3aWR0aCArICdweCcsXG4gICAgICBsZWZ0OiBsZWZ0IC0gMTUgKyAncHgnLFxuICAgICAgd2hpdGVTcGFjZTogJ2luaGVyaXQnXG4gICAgfSk7XG4gIH0gZWxzZSBpZiAocmlnaHQgKyBtYXJnaW4gPiB0b3RhbFdpZHRoKSB7XG4gICAgaW5zZXJ0UnVsZShzZWxlY3Rvciwge1xuICAgICAgbGVmdDogdG90YWxXaWR0aCAtIHJpZ2h0ICsgbWFyZ2luICsgJ3B4J1xuICAgIH0pO1xuICB9XG4gIHRyYW5zcGFyZW50KCk7XG4gIHNldFRpbWVvdXQob3BhcXVlLCAxMDAwKTtcblxuICBmdW5jdGlvbiB0cmFuc3BhcmVudCAoKSB7XG4gICAgaW5zZXJ0UnVsZShzZWxlY3RvciwgeyBvcGFjaXR5OiAwIH0pO1xuICB9XG4gIGZ1bmN0aW9uIG9wYXF1ZSAoKSB7XG4gICAgaW5zZXJ0UnVsZShzZWxlY3RvciwgdHJhbnNpdGlvbih7XG4gICAgICBvcGFjaXR5OiAxXG4gICAgfSkpO1xuICB9XG4gIGZ1bmN0aW9uIHRyYW5zaXRpb24gKGNzcykge1xuICAgIHZhciBydWxlID0gJ3RyYW5zaXRpb24nO1xuICAgIHZhciBwcmVmaXhlcyA9IFsnJywgJy13ZWJraXQtJywgJy1tb3otJywgJy1vLScsICctbXMtJ107XG4gICAgZnVuY3Rpb24gYWRkIChwcmVmaXgpIHtcbiAgICAgIGNzc1twcmVmaXggKyBydWxlXSA9ICdvcGFjaXR5IDAuM3MgZWFzZS1pbi1vdXQnO1xuICAgIH1cbiAgICBwcmVmaXhlcy5mb3JFYWNoKGFkZCk7XG4gICAgcmV0dXJuIGNzcztcbiAgfVxufVxuXG5mdW5jdGlvbiBjbGVhciAoZWxlbSkge1xuICB2YXIgcHNldWRvID0gZ2V0UHNldWRvKGVsZW0pO1xuICB2YXIgaWQgPSBlbGVtLmlkO1xuICBpZiAoaWQuaW5kZXhPZihleHBhbmRvKSA9PT0gMCkge1xuICAgIGVsZW0ucmVtb3ZlQXR0cmlidXRlKCdpZCcpO1xuICB9XG4gIHZhciBzZWxlY3RvciA9ICcjJyArIGlkICsgcHNldWRvO1xuICBpbnNlcnRSdWxlLnJlbW92ZShzZWxlY3Rvcik7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYXBpO1xuIl19 333 | -------------------------------------------------------------------------------- /dist/hint.min.css: -------------------------------------------------------------------------------- 1 | /* @bevacqua/hint@v3.0.3, MIT licensed. https://github.com/bevacqua/hint */ 2 | [aria-label],[data-hint]{position:relative}[aria-label].hint-before:before,[aria-label]:not(.hint-before):after,[data-hint].hint-before:before,[data-hint]:not(.hint-before):after{display:none;position:absolute;top:101%;left:6px;z-index:5000;pointer-events:none;padding:8px 10px;line-height:15px;white-space:nowrap;text-decoration:none;text-indent:0;overflow:visible;font-size:12px;font-weight:400;color:#fff;text-shadow:1px 0 1px #888;background-color:#412917;border-left:6px solid #d37092;border-radius:2px;-webkit-box-shadow:1px 2px 6px rgba(0,0,0,.3);box-shadow:1px 2px 6px rgba(0,0,0,.3)}[aria-label].hint-before:hover:before,[aria-label]:not(.hint-before):hover:after,[data-hint].hint-before:hover:before,[data-hint]:not(.hint-before):hover:after{display:block;-webkit-transform:translateY(8px);-moz-transform:translateY(8px);-o-transform:translateY(8px);-ms-transform:translateY(8px);transform:translateY(8px)}[aria-label].hint-before:before,[aria-label]:not(.hint-before):after{content:attr(aria-label)}[data-hint]:not(.hint-before):after{content:attr(data-hint)}[data-hint].hint-before:before{content:attr(data-hint-before)}@media only print{[aria-label].hint-before:before,[aria-label]:not(.hint-before):after,[data-hint].hint-before:before,[data-hint]:not(.hint-before):after{display:none}} -------------------------------------------------------------------------------- /dist/hint.min.js: -------------------------------------------------------------------------------- 1 | // @bevacqua/hint@v3.0.3, MIT licensed. https://github.com/bevacqua/hint 2 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).hint=e()}}(function(){return function i(u,a,f){function c(t,e){if(!a[t]){if(!u[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(l)return l(t,!0);var r=new Error("Cannot find module '"+t+"'");throw r.code="MODULE_NOT_FOUND",r}var o=a[t]={exports:{}};u[t][0].call(o.exports,function(e){return c(u[t][1][e]||e)},o,o.exports,i,u,a,f)}return a[t].exports}for(var l="function"==typeof require&&require,e=0;e>>0;if("function"!=typeof e)throw new TypeError;var r,o=new Array(n),i=2<=arguments.length?arguments[1]:void 0;for(r=0;rt?(i=(a=p - <%= pkg.description %>', 20 | ' * @version v<%= pkg.version %>', 21 | ' * @link <%= pkg.homepage %>', 22 | ' * @license <%= pkg.license %>', 23 | ' */', 24 | '' 25 | ].join('\n'); 26 | 27 | var succint = ' <%= pkg.name %>@v<%= pkg.version %>, <%= pkg.license %> licensed. <%= pkg.homepage %>'; 28 | var succjs = '//' + succint + '\n'; 29 | var succss = '/*' + succint + ' */\n'; 30 | 31 | gulp.task('clean', function () { 32 | gulp.src('./dist', { read: false }) 33 | .pipe(clean()); 34 | }); 35 | 36 | gulp.task('build', ['styles'], function () { 37 | var pkg = require('./package.json'); 38 | 39 | return browserify('./src/hint.js', { debug: true, standalone: 'hint' }) 40 | .bundle() 41 | .pipe(source('hint.js')) 42 | .pipe(streamify(header(extended, { pkg : pkg } ))) 43 | .pipe(gulp.dest('./dist')) 44 | .pipe(streamify(rename('hint.min.js'))) 45 | .pipe(streamify(uglify())) 46 | .pipe(streamify(header(succjs, { pkg : pkg } ))) 47 | .pipe(streamify(size())) 48 | .pipe(gulp.dest('./dist')); 49 | }); 50 | 51 | gulp.task('styles', ['clean', 'bump'], function () { 52 | var pkg = require('./package.json'); 53 | 54 | return gulp.src('./src/hint.styl') 55 | .pipe(stylus({ 56 | import: path.resolve('node_modules/nib/index') 57 | })) 58 | .pipe(header(extended, { pkg : pkg } )) 59 | .pipe(gulp.dest('./dist')) 60 | .pipe(rename('hint.min.css')) 61 | .pipe(minifyCSS()) 62 | .pipe(size()) 63 | .pipe(header(succss, { pkg : pkg } )) 64 | .pipe(gulp.dest('./dist')); 65 | }); 66 | 67 | gulp.task('bump', function () { 68 | var bumpType = process.env.BUMP || 'patch'; // major.minor.patch 69 | 70 | return gulp.src(['./package.json']) 71 | .pipe(bump({ type: bumpType })) 72 | .pipe(gulp.dest('./')); 73 | }); 74 | 75 | gulp.task('tag', ['build'], function (done) { 76 | var pkg = require('./package.json'); 77 | var v = 'v' + pkg.version; 78 | var message = 'Release ' + v; 79 | 80 | gulp.src('./') 81 | .pipe(git.commit(message)) 82 | .pipe(gulp.dest('./')) 83 | .on('end', tag); 84 | 85 | function tag () { 86 | git.tag(v, message); 87 | git.push('origin', 'master', { args: '--tags' }).end(); 88 | done(); 89 | } 90 | }); 91 | 92 | gulp.task('npm', ['tag'], function (done) { 93 | var child = require('child_process').exec('npm publish', {}, function () { 94 | done(); 95 | }); 96 | 97 | child.stdout.pipe(process.stdout); 98 | child.stderr.pipe(process.stderr); 99 | child.on('error', function () { 100 | throw new Error('unable to publish'); 101 | }); 102 | }); 103 | 104 | gulp.task('release', ['npm']); 105 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bevacqua/hint", 3 | "description": "Awesome tooltips at your fingertips", 4 | "version": "3.0.3", 5 | "homepage": "https://github.com/bevacqua/hint", 6 | "author": { 7 | "name": "Nicolas Bevacqua", 8 | "email": "hello@ponyfoo.com", 9 | "url": "https://ponyfoo.com" 10 | }, 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/bevacqua/hint.git" 15 | }, 16 | "main": "src/hint.js", 17 | "devDependencies": { 18 | "browserify": "16.2.2", 19 | "gulp": "^3.6.2", 20 | "gulp-bump": "^0.1.8", 21 | "gulp-clean": "^0.2.4", 22 | "gulp-git": "^0.4.2", 23 | "gulp-header": "^1.0.2", 24 | "gulp-minify-css": "^0.3.4", 25 | "gulp-rename": "^1.2.0", 26 | "gulp-size": "^0.3.1", 27 | "gulp-streamify": "1.0.2", 28 | "gulp-stylus": "^1.0.0", 29 | "gulp-uglify": "3.0.0", 30 | "vinyl-source-stream": "2.0.0" 31 | }, 32 | "dependencies": { 33 | "insert-rule": "^2.1.0", 34 | "nib": "^1.0.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /resources/hint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevacqua/hint/82081c9d6367a2900d98a167ef47d0fe270580cb/resources/hint.png -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var addEvent = addEventEasy; 4 | var removeEvent = removeEventEasy; 5 | 6 | if (!window.addEventListener) { 7 | addEvent = addEventHard; 8 | } 9 | 10 | if (!window.removeEventListener) { 11 | removeEvent = removeEventHard; 12 | } 13 | 14 | function addEventEasy (element, evt, fn) { 15 | return element.addEventListener(evt, fn); 16 | } 17 | 18 | function addEventHard (element, evt, fn) { 19 | return element.attachEvent('on' + evt, function (e) { 20 | e = e || window.event; 21 | e.target = e.target || e.srcElement; 22 | e.preventDefault = e.preventDefault || function preventDefault () { e.returnValue = false; }; 23 | e.stopPropagation = e.stopPropagation || function stopPropagation () { e.cancelBubble = true; }; 24 | fn.call(element, e); 25 | }); 26 | } 27 | 28 | function removeEventEasy (element, evt, fn) { 29 | return element.removeEventListener(evt, fn); 30 | } 31 | 32 | function removeEventHard (element, evt, fn) { 33 | return element.detachEvent('on' + evt, fn); 34 | } 35 | 36 | module.exports = { 37 | add: addEvent, 38 | remove: removeEvent 39 | }; 40 | -------------------------------------------------------------------------------- /src/hint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var insertRule = require('insert-rule').context('hint'); 4 | var events = require('./events'); 5 | var expando = 'hint-' + Date.now(); 6 | var api = { 7 | maximumWidth: 650 8 | }; 9 | 10 | events.add(document.body, 'mouseover', enter); 11 | events.add(document.body, 'mouseout', leave); 12 | 13 | function enter (e) { 14 | var prev = scan(e.fromElement); 15 | var elem = scan(e.target); 16 | if (elem && elem !== prev) { 17 | move(elem); 18 | } 19 | } 20 | 21 | function leave (e) { 22 | var next = scan(e.toElement); 23 | var elem = scan(e.target); 24 | if (elem && elem !== next) { 25 | clear(elem); 26 | } 27 | } 28 | 29 | function scan (elem) { 30 | while (elem) { 31 | if (probe(elem)) { 32 | return elem; 33 | } 34 | elem = elem.parentElement; 35 | } 36 | } 37 | 38 | function probe (elem) { 39 | return elem.getAttribute('aria-label') || elem.getAttribute('data-hint'); 40 | } 41 | 42 | function expandex () { 43 | return expando + Date.now(); 44 | } 45 | 46 | function i (px) { 47 | var raw = px.replace('px', ''); 48 | return parseInt(raw, 10); 49 | } 50 | 51 | function getPseudo (elem) { 52 | return elem.className.indexOf('hint-before') !== -1 ? ':before' : ':after'; 53 | } 54 | 55 | function move (elem) { 56 | var totalWidth = innerWidth; 57 | var pseudo = getPseudo(elem); 58 | var c = getComputedStyle(elem, pseudo); 59 | var existed = elem.getAttribute('id'); 60 | var id = existed || expandex(); 61 | if (id.indexOf(expando) === 0) { 62 | elem.setAttribute('id', id); 63 | } 64 | 65 | var selector = '#' + id + pseudo; 66 | var rect = elem.getBoundingClientRect(); 67 | var paddings = i(c.paddingLeft) + i(c.paddingRight) + i(c.marginLeft) + i(c.marginRight); 68 | var width; 69 | var left; 70 | var right = rect.left + i(c.width) + i(c.left) + paddings; 71 | var collapse; 72 | var offset; 73 | var margin = 20; 74 | var max = api.maximumWidth === 'auto' ? Infinity : api.maximumWidth; 75 | 76 | if (i(c.width) + margin > totalWidth) { 77 | collapse = totalWidth - margin > max; 78 | width = collapse ? max : totalWidth - margin; 79 | offset = collapse ? totalWidth - max - margin : 0; 80 | left = -(rect.left + paddings) + offset; 81 | 82 | insertRule(selector, { 83 | width: width + 'px', 84 | left: left - 15 + 'px', 85 | whiteSpace: 'inherit' 86 | }); 87 | } else if (right + margin > totalWidth) { 88 | insertRule(selector, { 89 | left: totalWidth - right + margin + 'px' 90 | }); 91 | } 92 | transparent(); 93 | setTimeout(opaque, 1000); 94 | 95 | function transparent () { 96 | insertRule(selector, { opacity: 0 }); 97 | } 98 | function opaque () { 99 | insertRule(selector, transition({ 100 | opacity: 1 101 | })); 102 | } 103 | function transition (css) { 104 | var rule = 'transition'; 105 | var prefixes = ['', '-webkit-', '-moz-', '-o-', '-ms-']; 106 | function add (prefix) { 107 | css[prefix + rule] = 'opacity 0.3s ease-in-out'; 108 | } 109 | prefixes.forEach(add); 110 | return css; 111 | } 112 | } 113 | 114 | function clear (elem) { 115 | var pseudo = getPseudo(elem); 116 | var id = elem.id; 117 | if (id.indexOf(expando) === 0) { 118 | elem.removeAttribute('id'); 119 | } 120 | var selector = '#' + id + pseudo; 121 | insertRule.remove(selector); 122 | } 123 | 124 | module.exports = api; 125 | -------------------------------------------------------------------------------- /src/hint.styl: -------------------------------------------------------------------------------- 1 | @import 'tooltip' 2 | 3 | z-hint = 5000 4 | 5 | [aria-label], 6 | [data-hint] 7 | position relative 8 | 9 | [aria-label]:not(.hint-before):after, 10 | [data-hint]:not(.hint-before):after, 11 | [aria-label].hint-before:before, 12 | [data-hint].hint-before:before 13 | display none 14 | position absolute 15 | top 101% 16 | left 6px 17 | 18 | z-index z-hint 19 | pointer-events none 20 | 21 | tooltip() 22 | 23 | [aria-label]:not(.hint-before):hover:after, 24 | [data-hint]:not(.hint-before):hover:after, 25 | [aria-label].hint-before:hover:before, 26 | [data-hint].hint-before:hover:before 27 | display block 28 | transform translateY(8px) 29 | 30 | [aria-label]:not(.hint-before):after, 31 | [aria-label].hint-before:before 32 | content attr(aria-label) 33 | 34 | [data-hint]:not(.hint-before):after 35 | content attr(data-hint) 36 | 37 | [data-hint].hint-before:before 38 | content attr(data-hint-before) 39 | 40 | @media only print 41 | [aria-label]:not(.hint-before):after, 42 | [data-hint]:not(.hint-before):after, 43 | [aria-label].hint-before:before, 44 | [data-hint].hint-before:before 45 | display none 46 | -------------------------------------------------------------------------------- /src/tooltip.styl: -------------------------------------------------------------------------------- 1 | ht-background = #412917 2 | ht-border = #d37092 3 | 4 | tooltip() 5 | padding 8px 10px 6 | line-height 15px 7 | white-space nowrap 8 | text-decoration none 9 | text-indent 0 10 | overflow visible 11 | 12 | font-size 12px 13 | font-weight normal 14 | color #fff 15 | text-shadow 1px 0px 1px #888 16 | background-color ht-background 17 | border-left 6px solid ht-border 18 | border-radius 2px 19 | 20 | box-shadow 1px 2px 6px rgba(#000, 0.3) 21 | --------------------------------------------------------------------------------