<!DOCTYPE html>
<html>

  <head>
    <script data-require="angular.js@*" data-semver="1.2.5" src="http://code.angularjs.org/1.2.5/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body ng-app="app">
    <h1>Tooltips, various usage scenarios</h1>
    <p>Style guide screenshot:</p>
    <img alt="tooltip design" border=1 src="//dev/test/matthias-dailey/images/lu-styleguide/tooltip.png">
    
    <div class="container">
        
        <button tooltip="#tt0" style="top:50%; left:50%">Hover over me</button>
        <button class="top right" tooltip="#tt1">Hover me</button>
        <!-- covered by the reload button -->
        <button tooltip="#tt2" style="top:50%; left:-60px;">#2 special</button>
        <button tooltip="#tt2" style="top:50%; right:-60px;">#6 special</button>
        <button class="bottom left" tooltip="#tt3">#3 BL</button>
        <button class="bottom right" tooltip="#tt4">#4 BR</button>
        <button class="bottom" tooltip="#tt5" style="right:70px">#5 Wide</button>
        <button class="bottom" tooltip="#tt6" style="right:170px">#6 White</button>
        
        <div id="tt0" class="tooltip">This is a tooltip.<br>It is pretty <b>awesome</b>.<br><img src="//dev/test/dancing-banana.gif"></div>
        <div id="tt1" class="tooltip">Tooltip forced below the element</div>
        <div id="tt2" class="tooltip">Tooltip does not go outside the screen</div>
        <div id="tt3" class="tooltip">s<!-- small --></div>
        <div id="tt4" class="tooltip">Multiline like a boss<br>Line 2<br>Line 3<hr>oops, that was a horizontal rule</div>
        <div id="tt5" class="tooltip">
            <b>This text would have overflowed.</b>
            Wayfarers slow-carb Helvetica cornhole single-origin coffee Williamsburg. Pop-up PBR&B lo-fi Tumblr asymmetrical, readymade skateboard next level. Aesthetic wayfarers plaid vegan Shoreditch. PBR&B locavore kogi Wes Anderson Terry Richardson. Disrupt letterpress keytar, Williamsburg salvia Pitchfork bespoke iPhone pour-over lo-fi fanny pack vegan whatever Intelligentsia chillwave. Polaroid Williamsburg quinoa vinyl Odd Future American Apparel. 8-bit synth fixie, locavore sustainable ennui readymade raw denim food truck.
            http://hipsteripsum.me
        </div>
        <div id="tt6" class="tooltip white">White with <br>no border</div>
        
        
        
    </div>
  </body>

</html>
/*
    
    Tooltip directive
    @author Matthias Dailey | http://zeroriginal.com
    @version 1.0.3
    
Shows a tooltip on mouseover and click. Hides in on mouseout or blur. 
The tooltip is an HTML element with `display:none`.

Positioned at the top by default, and offset to the left a little. If there is 
no space on top, shown on bottom.

Tooltip colors, styles, and padding are controlled by your stylesheet. 

The tooltip arrow is added and positioned automatically, and it matches the 
background color and border color.

Requires browser support of `box-sizing:border-box` and `display:inline-block`. 
Tooltip arrow is not present in IE versions 8 and below.

# Missing features:
 - support for when a tooltip extends past the document body (cannot scroll to it)

Could use improvements:
1. More tooltip open/close detection (touch events)
2. Box-shadow support

Changelog:
1.0.2 - Caret has correct border-width even with `border:none`
1.0.3 - Caret border element is completely hidden when border is 0.

*/

var luStyle = angular.module('luStyle',[]);
luStyle.directive('tooltip', [function(){
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            
            var id = attrs['tooltip'].match(/#?(.+)/)[1];
            if (!id) throw "bad id";
            
            var $tooltip = angular.element(document.getElementById(id));
            if (!$tooltip[0]) throw "tooltip '"+id+"' not found";
            
            // the root html element. For window dimensions
            var html = document.getElementsByTagName('html')[0];
            
            // get the tooltip border width
            var getBorderWidth = function() {
                // IE lte 8 case
                if (!window.getComputedStyle) return 1; // EAT THIS AND LIKE IT, IE.
                
                var s = getComputedStyle($tooltip[0]);
                
                // if the border is none, the border-width is 0
                if (s.getPropertyValue('border') == 'none') {
                    return 0;
                }
                else {
                    return parseInt(s.getPropertyValue('border-width'), 10);
                }
                
            };
            
            // spacing values
            // margin so that the tooltip is not touching any edges
            var margin = 1;
            // tooltip border width
            var bdw = getBorderWidth();
            // the size of the caret
            var caretSize = 5;
            // caret overlap on the target
            var caretOvr = 0;
            if (caretOvr>0) caretOvr = caretOvr + margin + bdw;
            // the left offset from the caret midpoint, if the tooltip is wide enough
            var leftOffset = -30;
            
            $tooltip.show = function() {
                this.css('display', 'inline-block');
            };
            $tooltip.hide = function() {
                this.css('display', 'none');
            };
            $tooltip.hide();
            // position the tooltip and make it visible
            $tooltip.positionAndShow = function() {
                
                // reset tooltip styles
                $tooltip.css({
                   'position': 'fixed',
                   'z-index': '9999999',
                   'box-sizing': 'border-box',
                   'width': 'auto'
                });
                
                // show the tooltip now. Must be visible to get dimensions.
                $tooltip.show();
                
                // get tooltip element's metrics
                var tt = getMetrics($tooltip[0]);
                // get the target element's metrics
                var tgt = angular.copy(element[0].getBoundingClientRect());
                if (!tgt.width) {
                    tgt.width = tgt.right - tgt.left;
                    tgt.height = tgt.top - tgt.bottom;
                }
                tgt.mid_x = tgt.right - (tgt.width/2);
                tgt.mid_y = tgt.bottom - (tgt.height/2);
                
                // the window's screen dimensions
                var screen = {
                    width: html.clientWidth,
                    height: html.clientHeight
                };
                
                // if the target element is wider than the leftOffset,
                if (leftOffset < tgt.width/2) {
                    // make leftOffset bigger
                    leftOffset = -tgt.width/2
                }
                
                // if the tooltip width is too small for the leftOffset
                if (tt.width/2 < -leftOffset) {
                    // reduce leftOffset
                    leftOffset = -tt.width/2;
                }
                
                // Position left
                
                // if the tooltip is too wide for the screen
                if (margin + tt.width + margin > screen.width) {
                    // make the tooltip full-width
                    tt.left = 0 + margin;
                    tt.width = screen.width - margin - margin;
                }
                // if the tooltip is less wide than the screen
                else {
                    // if there isn't enough space to the right
                    if (tgt.mid_x + leftOffset + tt.width + margin > screen.width) {
                        tt.left = screen.width - margin - tt.width;
                    }
                    // if there isn't enough space to the left
                    else if (tgt.mid_x + leftOffset < 0 + margin) {
                        // place tooltip at left edge
                        tt.left = 0 + margin;
                        //tt.width = 'auto';
                    }
                    // if there are no overflow problems
                    else {
                        // place tooltip at middle of target, offset
                        tt.left = tgt.mid_x + leftOffset;
                        //tt.width = 'auto';
                    }
                    
                }
                $tooltip.css({
                    'left': tt.left + 'px',
                    'width': tt.width + 'px'
                })
                // recompute the metrics for the tooltip, since we may have changed its width
                tt = getMetrics($tooltip[0]);
                
                // Position top
                
                // if there's space above
                if (tgt.top > margin + tt.height + caretSize + margin) {
                    // put it above
                    tt.top = tgt.top /* - margin*/ - caretSize - tt.height + caretOvr;
                    $tooltip.css('top', tt.top + 'px');
                    // caret arrow goes below
                    $tooltip.removeClass('arr-above');
                    $tooltip.addClass('arr-below');
                }
                else {
                    // put it below. If there's not enough space there either, then too bad
                    tt.top = tgt.top + tgt.height /* + margin*/ + caretSize - caretOvr;
                    $tooltip.css('top', tt.top + 'px');
                    // caret arrow goes below
                    $tooltip.addClass('arr-above');
                    $tooltip.removeClass('arr-below');
                }
                
                // position the caret at the middle of the target
                setupCaret(tt, -tt.left + tgt.mid_x);
                
            };
            
            element.on('mouseover click', function(){
                //scope.$apply(function(){ // not really necessary yet
                    $tooltip.positionAndShow();
                //});
            })
            element.on('mouseout blur', function(){
                //scope.$apply(function(){ // not really necessary yet
                    $tooltip.hide();
                //});
            });
            
            var getMetrics = function(el) {
                var area = getOffset(el);
                return area;
            };
            
            // caret (can't be css. must be linked to JS for positioning)
            var $caret, $caretborder;
            $tooltip.append($caret).append($caretborder);
            
            // Set the caret styles to match the computedStyle colors from the tooltip
            // @param xpos the position of the caret midpoint, relative to the tooltip
            var setupCaret = function (tt, xpos) {
                if (!window.getComputedStyle) return; // NO CARET FOR YOU, IE lte 8!
                
                // create or grab the carets
                if ($tooltip.find('mark').length == 2) {
                    $caret = $tooltip.find('mark').eq(0);
                    $caretborder = $tooltip.find('mark').eq(1);
                }
                else {
                    $caret = angular.element('<mark class="caret">');
                    $caretborder = angular.element('<mark class="caret-border">');
                }
                
                $tooltip.append($caret).append($caretborder);
                
                // min corner. If the caret is by the corner, enforce this min distance
                var mc = 3;
                
                // make the caret fit on the right side
                if (xpos + caretSize + bdw > tt.width) xpos = tt.width - bdw - caretSize - mc - 1; // -1 for pixel sync
                // make the caret fit on the left side
                if (xpos < caretSize) xpos = caretSize + mc;
                
                var generalStyles = {
                    'display': 'block',
                    'width': 0,
                    'height': 0,
                    'position': 'absolute',
                    'top': 'auto',
                    'left': xpos - caretSize + 'px',
                    'border': caretSize + 'px solid transparent',
                    'background': 'transparent',
                    'z-index': 2
                };
                $caret.css(generalStyles);
                $caretborder.css(generalStyles);
                
                if (bdw == 0) {
                    $caretborder.css('display','none');
                }
                
                var ttstyles = getComputedStyle($tooltip[0]);
                var bgcolor = ttstyles.getPropertyValue('background-color');
                var bordercolor = ttstyles.getPropertyValue('border-color');
                
                // set top absolute position
                if ($tooltip.hasClass('arr-above')) {
                    $caret.css({
                        'top': 0 - caretSize + 'px',
                        'bottom': 'auto',
                        'border-top': 'none',
                        'border-bottom': caretSize + 'px solid ' + bgcolor,
                        'z-index': 3
                    });
                    $caretborder.css({
                        'top': 0 - caretSize - bdw + 'px', // overlap the tooltip border
                        'bottom': 'auto',
                        'border-top': 'none',
                        'border-bottom': caretSize + 'px solid ' + bordercolor
                    });
                }
                else {
                    $caret.css({
                        'top': 'auto',
                        'bottom': 0 - caretSize + 'px',
                        'border-top': caretSize + 'px solid ' + bgcolor,
                        'border-bottom': 'none',
                        'z-index': 3
                    });
                    $caretborder.css({
                        'top': 'auto',
                        'bottom': 0 - caretSize - bdw + 'px', // overlap the tooltip border
                        'border-top': caretSize + 'px solid ' + bordercolor,
                        'border-bottom': 'none'
                    });
                }
            };
            
            var getOffset = function(el) {
                var x = el.offsetLeft, 
                    y = el.offsetTop,
                    w = el.offsetWidth,
                    h = el.offsetHeight;
                while (el = el.offsetParent) {
                    x += el.offsetLeft;
                    y += el.offsetTop;
                }
                return {
                    left: x,
                    top: y,
                    width: w,
                    height: h,
                    mid_x: x + (w/2),
                    mid_y: y + (h/2)
                }
            };
            
        }
    };
}])

angular.module('app', ['luStyle']);
/* Styles go here */

body {
    font-size: 12px;
    line-height:1;
}

.container {
    /*border: 1px solid blue;*/
    position: absolute;
    top:0; left:0; right:0; bottom:0;
    
}

.container button {
    position: absolute;
}

.top {top:0;}
.bottom {bottom:0;}
.left {left:0;}
.right {right:0;}

.tooltip {
    position: relative; /* for arrow positioning */
    background: #ffffcb;
    border: 1px solid #c0c09a;
    color: #666666;
    border-radius: 1px;
    font-family: 'HelveticaNeue','Helvetica',sans-serif;
    font-size: 1em;
    line-height: 1.3;
    padding: 0.3em 0.5em;
    z-index: 5;
}

.tooltip.white {
    background: #fff;
    border: none;
    border-radius: .5em;
    box-shadow: .1em .1em .6em #bbb;
}
`Version 1.0.3`

Shows a tooltip on mouseover and click. Hides on mouseout or blur. 
The tooltip is an HTML element with `display:none`.

Positioned at the top by default, and offset to the left a little. If there is 
no space on top, shown on bottom.

Tooltip colors, styles, and padding are controlled by your stylesheet. 

The tooltip arrow is added and positioned automatically, and it matches the 
background color and border color.

Requires browser support of `box-sizing:border-box` and `display:inline-block`. 
Tooltip arrow is not present in IE versions 8 and below.

[Demo page](http://embed.plnkr.co/vV02YH/preview)

&nbsp;

# Missing features:
 - support for when a tooltip extends past the document body (cannot scroll to it)

# Improvements (unplanned):
1. More tooltip open/close detection (touch events)
2. Box-shadow support

# Changelog:
- 1.0.2 - Caret has correct border-width even with `border:none`
- 1.0.3 - Caret border element is completely hidden when border is 0.