1515 lines
60 KiB
JavaScript
1515 lines
60 KiB
JavaScript
/*
|
|
* angular-ui-bootstrap
|
|
* http://angular-ui.github.io/bootstrap/
|
|
|
|
* Version: 2.5.0 - 2017-01-28
|
|
* License: MIT
|
|
*/angular.module("ui.bootstrap-custom", ["ui.bootstrap-custom.tpls","ui.bootstrap-custom.popover","ui.bootstrap-custom.tooltip","ui.bootstrap-custom.position","ui.bootstrap-custom.stackedMap"]);
|
|
angular.module("ui.bootstrap-custom.tpls", ["uib/template/popover/popover-html.html","uib/template/popover/popover-template.html","uib/template/popover/popover.html","uib/template/tooltip/tooltip-html-popup.html","uib/template/tooltip/tooltip-popup.html","uib/template/tooltip/tooltip-template-popup.html"]);
|
|
/**
|
|
* The following features are still outstanding: popup delay, animation as a
|
|
* function, placement as a function, inside, support for more triggers than
|
|
* just mouse enter/leave, and selector delegatation.
|
|
*/
|
|
angular.module('ui.bootstrap-custom.popover', ['ui.bootstrap-custom.tooltip'])
|
|
|
|
.directive('uibPopoverTemplatePopup', function() {
|
|
return {
|
|
restrict: 'A',
|
|
scope: { uibTitle: '@', contentExp: '&', originScope: '&' },
|
|
templateUrl: 'uib/template/popover/popover-template.html'
|
|
};
|
|
})
|
|
|
|
.directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
|
|
return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
|
|
useContentExp: true
|
|
});
|
|
}])
|
|
|
|
.directive('uibPopoverHtmlPopup', function() {
|
|
return {
|
|
restrict: 'A',
|
|
scope: { contentExp: '&', uibTitle: '@' },
|
|
templateUrl: 'uib/template/popover/popover-html.html'
|
|
};
|
|
})
|
|
|
|
.directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
|
|
return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
|
|
useContentExp: true
|
|
});
|
|
}])
|
|
|
|
.directive('uibPopoverPopup', function() {
|
|
return {
|
|
restrict: 'A',
|
|
scope: { uibTitle: '@', content: '@' },
|
|
templateUrl: 'uib/template/popover/popover.html'
|
|
};
|
|
})
|
|
|
|
.directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
|
|
return $uibTooltip('uibPopover', 'popover', 'click');
|
|
}]);
|
|
|
|
/**
|
|
* The following features are still outstanding: animation as a
|
|
* function, placement as a function, inside, support for more triggers than
|
|
* just mouse enter/leave, html tooltips, and selector delegation.
|
|
*/
|
|
angular.module('ui.bootstrap-custom.tooltip', ['ui.bootstrap-custom.position', 'ui.bootstrap-custom.stackedMap'])
|
|
|
|
/**
|
|
* The $tooltip service creates tooltip- and popover-like directives as well as
|
|
* houses global options for them.
|
|
*/
|
|
.provider('$uibTooltip', function() {
|
|
// The default options tooltip and popover.
|
|
var defaultOptions = {
|
|
placement: 'top',
|
|
placementClassPrefix: '',
|
|
animation: true,
|
|
popupDelay: 0,
|
|
popupCloseDelay: 0,
|
|
useContentExp: false
|
|
};
|
|
|
|
// Default hide triggers for each show trigger
|
|
var triggerMap = {
|
|
'mouseenter': 'mouseleave',
|
|
'click': 'click',
|
|
'outsideClick': 'outsideClick',
|
|
'focus': 'blur',
|
|
'none': ''
|
|
};
|
|
|
|
// The options specified to the provider globally.
|
|
var globalOptions = {};
|
|
|
|
/**
|
|
* `options({})` allows global configuration of all tooltips in the
|
|
* application.
|
|
*
|
|
* var app = angular.module( 'App', ['ui.bootstrap-custom.tooltip'], function( $tooltipProvider ) {
|
|
* // place tooltips left instead of top by default
|
|
* $tooltipProvider.options( { placement: 'left' } );
|
|
* });
|
|
*/
|
|
this.options = function(value) {
|
|
angular.extend(globalOptions, value);
|
|
};
|
|
|
|
/**
|
|
* This allows you to extend the set of trigger mappings available. E.g.:
|
|
*
|
|
* $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } );
|
|
*/
|
|
this.setTriggers = function setTriggers(triggers) {
|
|
angular.extend(triggerMap, triggers);
|
|
};
|
|
|
|
/**
|
|
* This is a helper function for translating camel-case to snake_case.
|
|
*/
|
|
function snake_case(name) {
|
|
var regexp = /[A-Z]/g;
|
|
var separator = '-';
|
|
return name.replace(regexp, function(letter, pos) {
|
|
return (pos ? separator : '') + letter.toLowerCase();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns the actual instance of the $tooltip service.
|
|
* TODO support multiple triggers
|
|
*/
|
|
this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
|
|
var openedTooltips = $$stackedMap.createNew();
|
|
$document.on('keyup', keypressListener);
|
|
|
|
$rootScope.$on('$destroy', function() {
|
|
$document.off('keyup', keypressListener);
|
|
});
|
|
|
|
function keypressListener(e) {
|
|
if (e.which === 27) {
|
|
var last = openedTooltips.top();
|
|
if (last) {
|
|
last.value.close();
|
|
last = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
|
|
options = angular.extend({}, defaultOptions, globalOptions, options);
|
|
|
|
/**
|
|
* Returns an object of show and hide triggers.
|
|
*
|
|
* If a trigger is supplied,
|
|
* it is used to show the tooltip; otherwise, it will use the `trigger`
|
|
* option passed to the `$tooltipProvider.options` method; else it will
|
|
* default to the trigger supplied to this directive factory.
|
|
*
|
|
* The hide trigger is based on the show trigger. If the `trigger` option
|
|
* was passed to the `$tooltipProvider.options` method, it will use the
|
|
* mapped trigger from `triggerMap` or the passed trigger if the map is
|
|
* undefined; otherwise, it uses the `triggerMap` value of the show
|
|
* trigger; else it will just use the show trigger.
|
|
*/
|
|
function getTriggers(trigger) {
|
|
var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
|
|
var hide = show.map(function(trigger) {
|
|
return triggerMap[trigger] || trigger;
|
|
});
|
|
return {
|
|
show: show,
|
|
hide: hide
|
|
};
|
|
}
|
|
|
|
var directiveName = snake_case(ttType);
|
|
|
|
var startSym = $interpolate.startSymbol();
|
|
var endSym = $interpolate.endSymbol();
|
|
var template =
|
|
'<div '+ directiveName + '-popup ' +
|
|
'uib-title="' + startSym + 'title' + endSym + '" ' +
|
|
(options.useContentExp ?
|
|
'content-exp="contentExp()" ' :
|
|
'content="' + startSym + 'content' + endSym + '" ') +
|
|
'origin-scope="origScope" ' +
|
|
'class="uib-position-measure ' + prefix + '" ' +
|
|
'tooltip-animation-class="fade"' +
|
|
'uib-tooltip-classes ' +
|
|
'ng-class="{ in: isOpen }" ' +
|
|
'>' +
|
|
'</div>';
|
|
|
|
return {
|
|
compile: function(tElem, tAttrs) {
|
|
var tooltipLinker = $compile(template);
|
|
|
|
return function link(scope, element, attrs, tooltipCtrl) {
|
|
var tooltip;
|
|
var tooltipLinkedScope;
|
|
var transitionTimeout;
|
|
var showTimeout;
|
|
var hideTimeout;
|
|
var positionTimeout;
|
|
var adjustmentTimeout;
|
|
var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
|
|
var triggers = getTriggers(undefined);
|
|
var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
|
|
var ttScope = scope.$new(true);
|
|
var repositionScheduled = false;
|
|
var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
|
|
var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
|
|
var observers = [];
|
|
var lastPlacement;
|
|
|
|
var positionTooltip = function() {
|
|
// check if tooltip exists and is not empty
|
|
if (!tooltip || !tooltip.html()) { return; }
|
|
|
|
if (!positionTimeout) {
|
|
positionTimeout = $timeout(function() {
|
|
var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
|
|
var initialHeight = angular.isDefined(tooltip.offsetHeight) ? tooltip.offsetHeight : tooltip.prop('offsetHeight');
|
|
var elementPos = appendToBody ? $position.offset(element) : $position.position(element);
|
|
tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' });
|
|
var placementClasses = ttPosition.placement.split('-');
|
|
|
|
if (!tooltip.hasClass(placementClasses[0])) {
|
|
tooltip.removeClass(lastPlacement.split('-')[0]);
|
|
tooltip.addClass(placementClasses[0]);
|
|
}
|
|
|
|
if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) {
|
|
tooltip.removeClass(options.placementClassPrefix + lastPlacement);
|
|
tooltip.addClass(options.placementClassPrefix + ttPosition.placement);
|
|
}
|
|
|
|
adjustmentTimeout = $timeout(function() {
|
|
var currentHeight = angular.isDefined(tooltip.offsetHeight) ? tooltip.offsetHeight : tooltip.prop('offsetHeight');
|
|
var adjustment = $position.adjustTop(placementClasses, elementPos, initialHeight, currentHeight);
|
|
if (adjustment) {
|
|
tooltip.css(adjustment);
|
|
}
|
|
adjustmentTimeout = null;
|
|
}, 0, false);
|
|
|
|
// first time through tt element will have the
|
|
// uib-position-measure class or if the placement
|
|
// has changed we need to position the arrow.
|
|
if (tooltip.hasClass('uib-position-measure')) {
|
|
$position.positionArrow(tooltip, ttPosition.placement);
|
|
tooltip.removeClass('uib-position-measure');
|
|
} else if (lastPlacement !== ttPosition.placement) {
|
|
$position.positionArrow(tooltip, ttPosition.placement);
|
|
}
|
|
lastPlacement = ttPosition.placement;
|
|
|
|
positionTimeout = null;
|
|
}, 0, false);
|
|
}
|
|
};
|
|
|
|
// Set up the correct scope to allow transclusion later
|
|
ttScope.origScope = scope;
|
|
|
|
// By default, the tooltip is not open.
|
|
// TODO add ability to start tooltip opened
|
|
ttScope.isOpen = false;
|
|
|
|
function toggleTooltipBind() {
|
|
if (!ttScope.isOpen) {
|
|
showTooltipBind();
|
|
} else {
|
|
hideTooltipBind();
|
|
}
|
|
}
|
|
|
|
// Show the tooltip with delay if specified, otherwise show it immediately
|
|
function showTooltipBind() {
|
|
if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
|
|
return;
|
|
}
|
|
|
|
cancelHide();
|
|
prepareTooltip();
|
|
|
|
if (ttScope.popupDelay) {
|
|
// Do nothing if the tooltip was already scheduled to pop-up.
|
|
// This happens if show is triggered multiple times before any hide is triggered.
|
|
if (!showTimeout) {
|
|
showTimeout = $timeout(show, ttScope.popupDelay, false);
|
|
}
|
|
} else {
|
|
show();
|
|
}
|
|
}
|
|
|
|
function hideTooltipBind() {
|
|
cancelShow();
|
|
|
|
if (ttScope.popupCloseDelay) {
|
|
if (!hideTimeout) {
|
|
hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
|
|
}
|
|
} else {
|
|
hide();
|
|
}
|
|
}
|
|
|
|
// Show the tooltip popup element.
|
|
function show() {
|
|
cancelShow();
|
|
cancelHide();
|
|
|
|
// Don't show empty tooltips.
|
|
if (!ttScope.content) {
|
|
return angular.noop;
|
|
}
|
|
|
|
createTooltip();
|
|
|
|
// And show the tooltip.
|
|
ttScope.$evalAsync(function() {
|
|
ttScope.isOpen = true;
|
|
assignIsOpen(true);
|
|
positionTooltip();
|
|
});
|
|
}
|
|
|
|
function cancelShow() {
|
|
if (showTimeout) {
|
|
$timeout.cancel(showTimeout);
|
|
showTimeout = null;
|
|
}
|
|
|
|
if (positionTimeout) {
|
|
$timeout.cancel(positionTimeout);
|
|
positionTimeout = null;
|
|
}
|
|
}
|
|
|
|
// Hide the tooltip popup element.
|
|
function hide() {
|
|
if (!ttScope) {
|
|
return;
|
|
}
|
|
|
|
// First things first: we don't show it anymore.
|
|
ttScope.$evalAsync(function() {
|
|
if (ttScope) {
|
|
ttScope.isOpen = false;
|
|
assignIsOpen(false);
|
|
// And now we remove it from the DOM. However, if we have animation, we
|
|
// need to wait for it to expire beforehand.
|
|
// FIXME: this is a placeholder for a port of the transitions library.
|
|
// The fade transition in TWBS is 150ms.
|
|
if (ttScope.animation) {
|
|
if (!transitionTimeout) {
|
|
transitionTimeout = $timeout(removeTooltip, 150, false);
|
|
}
|
|
} else {
|
|
removeTooltip();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function cancelHide() {
|
|
if (hideTimeout) {
|
|
$timeout.cancel(hideTimeout);
|
|
hideTimeout = null;
|
|
}
|
|
|
|
if (transitionTimeout) {
|
|
$timeout.cancel(transitionTimeout);
|
|
transitionTimeout = null;
|
|
}
|
|
}
|
|
|
|
function createTooltip() {
|
|
// There can only be one tooltip element per directive shown at once.
|
|
if (tooltip) {
|
|
return;
|
|
}
|
|
|
|
tooltipLinkedScope = ttScope.$new();
|
|
tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
|
|
if (appendToBody) {
|
|
$document.find('body').append(tooltip);
|
|
} else {
|
|
element.after(tooltip);
|
|
}
|
|
});
|
|
|
|
openedTooltips.add(ttScope, {
|
|
close: hide
|
|
});
|
|
|
|
prepObservers();
|
|
}
|
|
|
|
function removeTooltip() {
|
|
cancelShow();
|
|
cancelHide();
|
|
unregisterObservers();
|
|
|
|
if (tooltip) {
|
|
tooltip.remove();
|
|
|
|
tooltip = null;
|
|
if (adjustmentTimeout) {
|
|
$timeout.cancel(adjustmentTimeout);
|
|
}
|
|
}
|
|
|
|
openedTooltips.remove(ttScope);
|
|
|
|
if (tooltipLinkedScope) {
|
|
tooltipLinkedScope.$destroy();
|
|
tooltipLinkedScope = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the initial scope values. Once
|
|
* the tooltip is created, the observers
|
|
* will be added to keep things in sync.
|
|
*/
|
|
function prepareTooltip() {
|
|
ttScope.title = attrs[prefix + 'Title'];
|
|
if (contentParse) {
|
|
ttScope.content = contentParse(scope);
|
|
} else {
|
|
ttScope.content = attrs[ttType];
|
|
}
|
|
|
|
ttScope.popupClass = attrs[prefix + 'Class'];
|
|
ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
|
|
var placement = $position.parsePlacement(ttScope.placement);
|
|
lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0];
|
|
|
|
var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
|
|
var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
|
|
ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
|
|
ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
|
|
}
|
|
|
|
function assignIsOpen(isOpen) {
|
|
if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
|
|
isOpenParse.assign(scope, isOpen);
|
|
}
|
|
}
|
|
|
|
ttScope.contentExp = function() {
|
|
return ttScope.content;
|
|
};
|
|
|
|
/**
|
|
* Observe the relevant attributes.
|
|
*/
|
|
attrs.$observe('disabled', function(val) {
|
|
if (val) {
|
|
cancelShow();
|
|
}
|
|
|
|
if (val && ttScope.isOpen) {
|
|
hide();
|
|
}
|
|
});
|
|
|
|
if (isOpenParse) {
|
|
scope.$watch(isOpenParse, function(val) {
|
|
if (ttScope && !val === ttScope.isOpen) {
|
|
toggleTooltipBind();
|
|
}
|
|
});
|
|
}
|
|
|
|
function prepObservers() {
|
|
observers.length = 0;
|
|
|
|
if (contentParse) {
|
|
observers.push(
|
|
scope.$watch(contentParse, function(val) {
|
|
ttScope.content = val;
|
|
if (!val && ttScope.isOpen) {
|
|
hide();
|
|
}
|
|
})
|
|
);
|
|
|
|
observers.push(
|
|
tooltipLinkedScope.$watch(function() {
|
|
if (!repositionScheduled) {
|
|
repositionScheduled = true;
|
|
tooltipLinkedScope.$$postDigest(function() {
|
|
repositionScheduled = false;
|
|
if (ttScope && ttScope.isOpen) {
|
|
positionTooltip();
|
|
}
|
|
});
|
|
}
|
|
})
|
|
);
|
|
} else {
|
|
observers.push(
|
|
attrs.$observe(ttType, function(val) {
|
|
ttScope.content = val;
|
|
if (!val && ttScope.isOpen) {
|
|
hide();
|
|
} else {
|
|
positionTooltip();
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
observers.push(
|
|
attrs.$observe(prefix + 'Title', function(val) {
|
|
ttScope.title = val;
|
|
if (ttScope.isOpen) {
|
|
positionTooltip();
|
|
}
|
|
})
|
|
);
|
|
|
|
observers.push(
|
|
attrs.$observe(prefix + 'Placement', function(val) {
|
|
ttScope.placement = val ? val : options.placement;
|
|
if (ttScope.isOpen) {
|
|
positionTooltip();
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
function unregisterObservers() {
|
|
if (observers.length) {
|
|
angular.forEach(observers, function(observer) {
|
|
observer();
|
|
});
|
|
observers.length = 0;
|
|
}
|
|
}
|
|
|
|
// hide tooltips/popovers for outsideClick trigger
|
|
function bodyHideTooltipBind(e) {
|
|
if (!ttScope || !ttScope.isOpen || !tooltip) {
|
|
return;
|
|
}
|
|
// make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
|
|
if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
|
|
hideTooltipBind();
|
|
}
|
|
}
|
|
|
|
// KeyboardEvent handler to hide the tooltip on Escape key press
|
|
function hideOnEscapeKey(e) {
|
|
if (e.which === 27) {
|
|
hideTooltipBind();
|
|
}
|
|
}
|
|
|
|
var unregisterTriggers = function() {
|
|
triggers.show.forEach(function(trigger) {
|
|
if (trigger === 'outsideClick') {
|
|
element.off('click', toggleTooltipBind);
|
|
} else {
|
|
element.off(trigger, showTooltipBind);
|
|
element.off(trigger, toggleTooltipBind);
|
|
}
|
|
element.off('keypress', hideOnEscapeKey);
|
|
});
|
|
triggers.hide.forEach(function(trigger) {
|
|
if (trigger === 'outsideClick') {
|
|
$document.off('click', bodyHideTooltipBind);
|
|
} else {
|
|
element.off(trigger, hideTooltipBind);
|
|
}
|
|
});
|
|
};
|
|
|
|
function prepTriggers() {
|
|
var showTriggers = [], hideTriggers = [];
|
|
var val = scope.$eval(attrs[prefix + 'Trigger']);
|
|
unregisterTriggers();
|
|
|
|
if (angular.isObject(val)) {
|
|
Object.keys(val).forEach(function(key) {
|
|
showTriggers.push(key);
|
|
hideTriggers.push(val[key]);
|
|
});
|
|
triggers = {
|
|
show: showTriggers,
|
|
hide: hideTriggers
|
|
};
|
|
} else {
|
|
triggers = getTriggers(val);
|
|
}
|
|
|
|
if (triggers.show !== 'none') {
|
|
triggers.show.forEach(function(trigger, idx) {
|
|
if (trigger === 'outsideClick') {
|
|
element.on('click', toggleTooltipBind);
|
|
$document.on('click', bodyHideTooltipBind);
|
|
} else if (trigger === triggers.hide[idx]) {
|
|
element.on(trigger, toggleTooltipBind);
|
|
} else if (trigger) {
|
|
element.on(trigger, showTooltipBind);
|
|
element.on(triggers.hide[idx], hideTooltipBind);
|
|
}
|
|
element.on('keypress', hideOnEscapeKey);
|
|
});
|
|
}
|
|
}
|
|
|
|
prepTriggers();
|
|
|
|
var animation = scope.$eval(attrs[prefix + 'Animation']);
|
|
ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
|
|
|
|
var appendToBodyVal;
|
|
var appendKey = prefix + 'AppendToBody';
|
|
if (appendKey in attrs && attrs[appendKey] === undefined) {
|
|
appendToBodyVal = true;
|
|
} else {
|
|
appendToBodyVal = scope.$eval(attrs[appendKey]);
|
|
}
|
|
|
|
appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
|
|
|
|
// Make sure tooltip is destroyed and removed.
|
|
scope.$on('$destroy', function onDestroyTooltip() {
|
|
unregisterTriggers();
|
|
removeTooltip();
|
|
ttScope = null;
|
|
});
|
|
};
|
|
}
|
|
};
|
|
};
|
|
}];
|
|
})
|
|
|
|
// This is mostly ngInclude code but with a custom scope
|
|
.directive('uibTooltipTemplateTransclude', [
|
|
'$animate', '$sce', '$compile', '$templateRequest',
|
|
function ($animate, $sce, $compile, $templateRequest) {
|
|
return {
|
|
link: function(scope, elem, attrs) {
|
|
var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
|
|
|
|
var changeCounter = 0,
|
|
currentScope,
|
|
previousElement,
|
|
currentElement;
|
|
|
|
var cleanupLastIncludeContent = function() {
|
|
if (previousElement) {
|
|
previousElement.remove();
|
|
previousElement = null;
|
|
}
|
|
|
|
if (currentScope) {
|
|
currentScope.$destroy();
|
|
currentScope = null;
|
|
}
|
|
|
|
if (currentElement) {
|
|
$animate.leave(currentElement).then(function() {
|
|
previousElement = null;
|
|
});
|
|
previousElement = currentElement;
|
|
currentElement = null;
|
|
}
|
|
};
|
|
|
|
scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
|
|
var thisChangeId = ++changeCounter;
|
|
|
|
if (src) {
|
|
//set the 2nd param to true to ignore the template request error so that the inner
|
|
//contents and scope can be cleaned up.
|
|
$templateRequest(src, true).then(function(response) {
|
|
if (thisChangeId !== changeCounter) { return; }
|
|
var newScope = origScope.$new();
|
|
var template = response;
|
|
|
|
var clone = $compile(template)(newScope, function(clone) {
|
|
cleanupLastIncludeContent();
|
|
$animate.enter(clone, elem);
|
|
});
|
|
|
|
currentScope = newScope;
|
|
currentElement = clone;
|
|
|
|
currentScope.$emit('$includeContentLoaded', src);
|
|
}, function() {
|
|
if (thisChangeId === changeCounter) {
|
|
cleanupLastIncludeContent();
|
|
scope.$emit('$includeContentError', src);
|
|
}
|
|
});
|
|
scope.$emit('$includeContentRequested', src);
|
|
} else {
|
|
cleanupLastIncludeContent();
|
|
}
|
|
});
|
|
|
|
scope.$on('$destroy', cleanupLastIncludeContent);
|
|
}
|
|
};
|
|
}])
|
|
|
|
/**
|
|
* Note that it's intentional that these classes are *not* applied through $animate.
|
|
* They must not be animated as they're expected to be present on the tooltip on
|
|
* initialization.
|
|
*/
|
|
.directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {
|
|
return {
|
|
restrict: 'A',
|
|
link: function(scope, element, attrs) {
|
|
// need to set the primary position so the
|
|
// arrow has space during position measure.
|
|
// tooltip.positionTooltip()
|
|
if (scope.placement) {
|
|
// // There are no top-left etc... classes
|
|
// // in TWBS, so we need the primary position.
|
|
var position = $uibPosition.parsePlacement(scope.placement);
|
|
element.addClass(position[0]);
|
|
}
|
|
|
|
if (scope.popupClass) {
|
|
element.addClass(scope.popupClass);
|
|
}
|
|
|
|
if (scope.animation) {
|
|
element.addClass(attrs.tooltipAnimationClass);
|
|
}
|
|
}
|
|
};
|
|
}])
|
|
|
|
.directive('uibTooltipPopup', function() {
|
|
return {
|
|
restrict: 'A',
|
|
scope: { content: '@' },
|
|
templateUrl: 'uib/template/tooltip/tooltip-popup.html'
|
|
};
|
|
})
|
|
|
|
.directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
|
|
return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
|
|
}])
|
|
|
|
.directive('uibTooltipTemplatePopup', function() {
|
|
return {
|
|
restrict: 'A',
|
|
scope: { contentExp: '&', originScope: '&' },
|
|
templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
|
|
};
|
|
})
|
|
|
|
.directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
|
|
return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
|
|
useContentExp: true
|
|
});
|
|
}])
|
|
|
|
.directive('uibTooltipHtmlPopup', function() {
|
|
return {
|
|
restrict: 'A',
|
|
scope: { contentExp: '&' },
|
|
templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
|
|
};
|
|
})
|
|
|
|
.directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
|
|
return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
|
|
useContentExp: true
|
|
});
|
|
}]);
|
|
|
|
angular.module('ui.bootstrap-custom.position', [])
|
|
|
|
/**
|
|
* A set of utility methods for working with the DOM.
|
|
* It is meant to be used where we need to absolute-position elements in
|
|
* relation to another element (this is the case for tooltips, popovers,
|
|
* typeahead suggestions etc.).
|
|
*/
|
|
.factory('$uibPosition', ['$document', '$window', function($document, $window) {
|
|
/**
|
|
* Used by scrollbarWidth() function to cache scrollbar's width.
|
|
* Do not access this variable directly, use scrollbarWidth() instead.
|
|
*/
|
|
var SCROLLBAR_WIDTH;
|
|
/**
|
|
* scrollbar on body and html element in IE and Edge overlay
|
|
* content and should be considered 0 width.
|
|
*/
|
|
var BODY_SCROLLBAR_WIDTH;
|
|
var OVERFLOW_REGEX = {
|
|
normal: /(auto|scroll)/,
|
|
hidden: /(auto|scroll|hidden)/
|
|
};
|
|
var PLACEMENT_REGEX = {
|
|
auto: /\s?auto?\s?/i,
|
|
primary: /^(top|bottom|left|right)$/,
|
|
secondary: /^(top|bottom|left|right|center)$/,
|
|
vertical: /^(top|bottom)$/
|
|
};
|
|
var BODY_REGEX = /(HTML|BODY)/;
|
|
|
|
return {
|
|
|
|
/**
|
|
* Provides a raw DOM element from a jQuery/jQLite element.
|
|
*
|
|
* @param {element} elem - The element to convert.
|
|
*
|
|
* @returns {element} A HTML element.
|
|
*/
|
|
getRawNode: function(elem) {
|
|
return elem.nodeName ? elem : elem[0] || elem;
|
|
},
|
|
|
|
/**
|
|
* Provides a parsed number for a style property. Strips
|
|
* units and casts invalid numbers to 0.
|
|
*
|
|
* @param {string} value - The style value to parse.
|
|
*
|
|
* @returns {number} A valid number.
|
|
*/
|
|
parseStyle: function(value) {
|
|
value = parseFloat(value);
|
|
return isFinite(value) ? value : 0;
|
|
},
|
|
|
|
/**
|
|
* Provides the closest positioned ancestor.
|
|
*
|
|
* @param {element} element - The element to get the offest parent for.
|
|
*
|
|
* @returns {element} The closest positioned ancestor.
|
|
*/
|
|
offsetParent: function(elem) {
|
|
elem = this.getRawNode(elem);
|
|
|
|
var offsetParent = elem.offsetParent || $document[0].documentElement;
|
|
|
|
function isStaticPositioned(el) {
|
|
return ($window.getComputedStyle(el).position || 'static') === 'static';
|
|
}
|
|
|
|
while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
|
|
offsetParent = offsetParent.offsetParent;
|
|
}
|
|
|
|
return offsetParent || $document[0].documentElement;
|
|
},
|
|
|
|
/**
|
|
* Provides the scrollbar width, concept from TWBS measureScrollbar()
|
|
* function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
|
|
* In IE and Edge, scollbar on body and html element overlay and should
|
|
* return a width of 0.
|
|
*
|
|
* @returns {number} The width of the browser scollbar.
|
|
*/
|
|
scrollbarWidth: function(isBody) {
|
|
if (isBody) {
|
|
if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) {
|
|
var bodyElem = $document.find('body');
|
|
bodyElem.addClass('uib-position-body-scrollbar-measure');
|
|
BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth;
|
|
BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0;
|
|
bodyElem.removeClass('uib-position-body-scrollbar-measure');
|
|
}
|
|
return BODY_SCROLLBAR_WIDTH;
|
|
}
|
|
|
|
if (angular.isUndefined(SCROLLBAR_WIDTH)) {
|
|
var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>');
|
|
$document.find('body').append(scrollElem);
|
|
SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
|
|
SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
|
|
scrollElem.remove();
|
|
}
|
|
|
|
return SCROLLBAR_WIDTH;
|
|
},
|
|
|
|
/**
|
|
* Provides the padding required on an element to replace the scrollbar.
|
|
*
|
|
* @returns {object} An object with the following properties:
|
|
* <ul>
|
|
* <li>**scrollbarWidth**: the width of the scrollbar</li>
|
|
* <li>**widthOverflow**: whether the the width is overflowing</li>
|
|
* <li>**right**: the amount of right padding on the element needed to replace the scrollbar</li>
|
|
* <li>**rightOriginal**: the amount of right padding currently on the element</li>
|
|
* <li>**heightOverflow**: whether the the height is overflowing</li>
|
|
* <li>**bottom**: the amount of bottom padding on the element needed to replace the scrollbar</li>
|
|
* <li>**bottomOriginal**: the amount of bottom padding currently on the element</li>
|
|
* </ul>
|
|
*/
|
|
scrollbarPadding: function(elem) {
|
|
elem = this.getRawNode(elem);
|
|
|
|
var elemStyle = $window.getComputedStyle(elem);
|
|
var paddingRight = this.parseStyle(elemStyle.paddingRight);
|
|
var paddingBottom = this.parseStyle(elemStyle.paddingBottom);
|
|
var scrollParent = this.scrollParent(elem, false, true);
|
|
var scrollbarWidth = this.scrollbarWidth(BODY_REGEX.test(scrollParent.tagName));
|
|
|
|
return {
|
|
scrollbarWidth: scrollbarWidth,
|
|
widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth,
|
|
right: paddingRight + scrollbarWidth,
|
|
originalRight: paddingRight,
|
|
heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight,
|
|
bottom: paddingBottom + scrollbarWidth,
|
|
originalBottom: paddingBottom
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Checks to see if the element is scrollable.
|
|
*
|
|
* @param {element} elem - The element to check.
|
|
* @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
|
|
* default is false.
|
|
*
|
|
* @returns {boolean} Whether the element is scrollable.
|
|
*/
|
|
isScrollable: function(elem, includeHidden) {
|
|
elem = this.getRawNode(elem);
|
|
|
|
var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
|
|
var elemStyle = $window.getComputedStyle(elem);
|
|
return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX);
|
|
},
|
|
|
|
/**
|
|
* Provides the closest scrollable ancestor.
|
|
* A port of the jQuery UI scrollParent method:
|
|
* https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
|
|
*
|
|
* @param {element} elem - The element to find the scroll parent of.
|
|
* @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
|
|
* default is false.
|
|
* @param {boolean=} [includeSelf=false] - Should the element being passed be
|
|
* included in the scrollable llokup.
|
|
*
|
|
* @returns {element} A HTML element.
|
|
*/
|
|
scrollParent: function(elem, includeHidden, includeSelf) {
|
|
elem = this.getRawNode(elem);
|
|
|
|
var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
|
|
var documentEl = $document[0].documentElement;
|
|
var elemStyle = $window.getComputedStyle(elem);
|
|
if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) {
|
|
return elem;
|
|
}
|
|
var excludeStatic = elemStyle.position === 'absolute';
|
|
var scrollParent = elem.parentElement || documentEl;
|
|
|
|
if (scrollParent === documentEl || elemStyle.position === 'fixed') {
|
|
return documentEl;
|
|
}
|
|
|
|
while (scrollParent.parentElement && scrollParent !== documentEl) {
|
|
var spStyle = $window.getComputedStyle(scrollParent);
|
|
if (excludeStatic && spStyle.position !== 'static') {
|
|
excludeStatic = false;
|
|
}
|
|
|
|
if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
|
|
break;
|
|
}
|
|
scrollParent = scrollParent.parentElement;
|
|
}
|
|
|
|
return scrollParent;
|
|
},
|
|
|
|
/**
|
|
* Provides read-only equivalent of jQuery's position function:
|
|
* http://api.jquery.com/position/ - distance to closest positioned
|
|
* ancestor. Does not account for margins by default like jQuery position.
|
|
*
|
|
* @param {element} elem - The element to caclulate the position on.
|
|
* @param {boolean=} [includeMargins=false] - Should margins be accounted
|
|
* for, default is false.
|
|
*
|
|
* @returns {object} An object with the following properties:
|
|
* <ul>
|
|
* <li>**width**: the width of the element</li>
|
|
* <li>**height**: the height of the element</li>
|
|
* <li>**top**: distance to top edge of offset parent</li>
|
|
* <li>**left**: distance to left edge of offset parent</li>
|
|
* </ul>
|
|
*/
|
|
position: function(elem, includeMagins) {
|
|
elem = this.getRawNode(elem);
|
|
|
|
var elemOffset = this.offset(elem);
|
|
if (includeMagins) {
|
|
var elemStyle = $window.getComputedStyle(elem);
|
|
elemOffset.top -= this.parseStyle(elemStyle.marginTop);
|
|
elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
|
|
}
|
|
var parent = this.offsetParent(elem);
|
|
var parentOffset = {top: 0, left: 0};
|
|
|
|
if (parent !== $document[0].documentElement) {
|
|
parentOffset = this.offset(parent);
|
|
parentOffset.top += parent.clientTop - parent.scrollTop;
|
|
parentOffset.left += parent.clientLeft - parent.scrollLeft;
|
|
}
|
|
|
|
return {
|
|
width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
|
|
height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
|
|
top: Math.round(elemOffset.top - parentOffset.top),
|
|
left: Math.round(elemOffset.left - parentOffset.left)
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Provides read-only equivalent of jQuery's offset function:
|
|
* http://api.jquery.com/offset/ - distance to viewport. Does
|
|
* not account for borders, margins, or padding on the body
|
|
* element.
|
|
*
|
|
* @param {element} elem - The element to calculate the offset on.
|
|
*
|
|
* @returns {object} An object with the following properties:
|
|
* <ul>
|
|
* <li>**width**: the width of the element</li>
|
|
* <li>**height**: the height of the element</li>
|
|
* <li>**top**: distance to top edge of viewport</li>
|
|
* <li>**right**: distance to bottom edge of viewport</li>
|
|
* </ul>
|
|
*/
|
|
offset: function(elem) {
|
|
elem = this.getRawNode(elem);
|
|
|
|
var elemBCR = elem.getBoundingClientRect();
|
|
return {
|
|
width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
|
|
height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
|
|
top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
|
|
left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Provides offset distance to the closest scrollable ancestor
|
|
* or viewport. Accounts for border and scrollbar width.
|
|
*
|
|
* Right and bottom dimensions represent the distance to the
|
|
* respective edge of the viewport element. If the element
|
|
* edge extends beyond the viewport, a negative value will be
|
|
* reported.
|
|
*
|
|
* @param {element} elem - The element to get the viewport offset for.
|
|
* @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
|
|
* of the first scrollable element, default is false.
|
|
* @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
|
|
* be accounted for, default is true.
|
|
*
|
|
* @returns {object} An object with the following properties:
|
|
* <ul>
|
|
* <li>**top**: distance to the top content edge of viewport element</li>
|
|
* <li>**bottom**: distance to the bottom content edge of viewport element</li>
|
|
* <li>**left**: distance to the left content edge of viewport element</li>
|
|
* <li>**right**: distance to the right content edge of viewport element</li>
|
|
* </ul>
|
|
*/
|
|
viewportOffset: function(elem, useDocument, includePadding) {
|
|
elem = this.getRawNode(elem);
|
|
includePadding = includePadding !== false ? true : false;
|
|
|
|
var elemBCR = elem.getBoundingClientRect();
|
|
var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};
|
|
|
|
var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
|
|
var offsetParentBCR = offsetParent.getBoundingClientRect();
|
|
|
|
offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
|
|
offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
|
|
if (offsetParent === $document[0].documentElement) {
|
|
offsetBCR.top += $window.pageYOffset;
|
|
offsetBCR.left += $window.pageXOffset;
|
|
}
|
|
offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
|
|
offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;
|
|
|
|
if (includePadding) {
|
|
var offsetParentStyle = $window.getComputedStyle(offsetParent);
|
|
offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
|
|
offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
|
|
offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
|
|
offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
|
|
}
|
|
|
|
return {
|
|
top: Math.round(elemBCR.top - offsetBCR.top),
|
|
bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
|
|
left: Math.round(elemBCR.left - offsetBCR.left),
|
|
right: Math.round(offsetBCR.right - elemBCR.right)
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Provides an array of placement values parsed from a placement string.
|
|
* Along with the 'auto' indicator, supported placement strings are:
|
|
* <ul>
|
|
* <li>top: element on top, horizontally centered on host element.</li>
|
|
* <li>top-left: element on top, left edge aligned with host element left edge.</li>
|
|
* <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
|
|
* <li>bottom: element on bottom, horizontally centered on host element.</li>
|
|
* <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
|
|
* <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
|
|
* <li>left: element on left, vertically centered on host element.</li>
|
|
* <li>left-top: element on left, top edge aligned with host element top edge.</li>
|
|
* <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
|
|
* <li>right: element on right, vertically centered on host element.</li>
|
|
* <li>right-top: element on right, top edge aligned with host element top edge.</li>
|
|
* <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
|
|
* </ul>
|
|
* A placement string with an 'auto' indicator is expected to be
|
|
* space separated from the placement, i.e: 'auto bottom-left' If
|
|
* the primary and secondary placement values do not match 'top,
|
|
* bottom, left, right' then 'top' will be the primary placement and
|
|
* 'center' will be the secondary placement. If 'auto' is passed, true
|
|
* will be returned as the 3rd value of the array.
|
|
*
|
|
* @param {string} placement - The placement string to parse.
|
|
*
|
|
* @returns {array} An array with the following values
|
|
* <ul>
|
|
* <li>**[0]**: The primary placement.</li>
|
|
* <li>**[1]**: The secondary placement.</li>
|
|
* <li>**[2]**: If auto is passed: true, else undefined.</li>
|
|
* </ul>
|
|
*/
|
|
parsePlacement: function(placement) {
|
|
var autoPlace = PLACEMENT_REGEX.auto.test(placement);
|
|
if (autoPlace) {
|
|
placement = placement.replace(PLACEMENT_REGEX.auto, '');
|
|
}
|
|
|
|
placement = placement.split('-');
|
|
|
|
placement[0] = placement[0] || 'top';
|
|
if (!PLACEMENT_REGEX.primary.test(placement[0])) {
|
|
placement[0] = 'top';
|
|
}
|
|
|
|
placement[1] = placement[1] || 'center';
|
|
if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
|
|
placement[1] = 'center';
|
|
}
|
|
|
|
if (autoPlace) {
|
|
placement[2] = true;
|
|
} else {
|
|
placement[2] = false;
|
|
}
|
|
|
|
return placement;
|
|
},
|
|
|
|
/**
|
|
* Provides coordinates for an element to be positioned relative to
|
|
* another element. Passing 'auto' as part of the placement parameter
|
|
* will enable smart placement - where the element fits. i.e:
|
|
* 'auto left-top' will check to see if there is enough space to the left
|
|
* of the hostElem to fit the targetElem, if not place right (same for secondary
|
|
* top placement). Available space is calculated using the viewportOffset
|
|
* function.
|
|
*
|
|
* @param {element} hostElem - The element to position against.
|
|
* @param {element} targetElem - The element to position.
|
|
* @param {string=} [placement=top] - The placement for the targetElem,
|
|
* default is 'top'. 'center' is assumed as secondary placement for
|
|
* 'top', 'left', 'right', and 'bottom' placements. Available placements are:
|
|
* <ul>
|
|
* <li>top</li>
|
|
* <li>top-right</li>
|
|
* <li>top-left</li>
|
|
* <li>bottom</li>
|
|
* <li>bottom-left</li>
|
|
* <li>bottom-right</li>
|
|
* <li>left</li>
|
|
* <li>left-top</li>
|
|
* <li>left-bottom</li>
|
|
* <li>right</li>
|
|
* <li>right-top</li>
|
|
* <li>right-bottom</li>
|
|
* </ul>
|
|
* @param {boolean=} [appendToBody=false] - Should the top and left values returned
|
|
* be calculated from the body element, default is false.
|
|
*
|
|
* @returns {object} An object with the following properties:
|
|
* <ul>
|
|
* <li>**top**: Value for targetElem top.</li>
|
|
* <li>**left**: Value for targetElem left.</li>
|
|
* <li>**placement**: The resolved placement.</li>
|
|
* </ul>
|
|
*/
|
|
positionElements: function(hostElem, targetElem, placement, appendToBody) {
|
|
hostElem = this.getRawNode(hostElem);
|
|
targetElem = this.getRawNode(targetElem);
|
|
|
|
// need to read from prop to support tests.
|
|
var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
|
|
var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');
|
|
|
|
placement = this.parsePlacement(placement);
|
|
|
|
var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
|
|
var targetElemPos = {top: 0, left: 0, placement: ''};
|
|
|
|
if (placement[2]) {
|
|
var viewportOffset = this.viewportOffset(hostElem, appendToBody);
|
|
|
|
var targetElemStyle = $window.getComputedStyle(targetElem);
|
|
var adjustedSize = {
|
|
width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
|
|
height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
|
|
};
|
|
|
|
placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
|
|
placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
|
|
placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
|
|
placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
|
|
placement[0];
|
|
|
|
placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
|
|
placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
|
|
placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
|
|
placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
|
|
placement[1];
|
|
|
|
if (placement[1] === 'center') {
|
|
if (PLACEMENT_REGEX.vertical.test(placement[0])) {
|
|
var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
|
|
if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
|
|
placement[1] = 'left';
|
|
} else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
|
|
placement[1] = 'right';
|
|
}
|
|
} else {
|
|
var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
|
|
if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
|
|
placement[1] = 'top';
|
|
} else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
|
|
placement[1] = 'bottom';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (placement[0]) {
|
|
case 'top':
|
|
targetElemPos.top = hostElemPos.top - targetHeight;
|
|
break;
|
|
case 'bottom':
|
|
targetElemPos.top = hostElemPos.top + hostElemPos.height;
|
|
break;
|
|
case 'left':
|
|
targetElemPos.left = hostElemPos.left - targetWidth;
|
|
break;
|
|
case 'right':
|
|
targetElemPos.left = hostElemPos.left + hostElemPos.width;
|
|
break;
|
|
}
|
|
|
|
switch (placement[1]) {
|
|
case 'top':
|
|
targetElemPos.top = hostElemPos.top;
|
|
break;
|
|
case 'bottom':
|
|
targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
|
|
break;
|
|
case 'left':
|
|
targetElemPos.left = hostElemPos.left;
|
|
break;
|
|
case 'right':
|
|
targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
|
|
break;
|
|
case 'center':
|
|
if (PLACEMENT_REGEX.vertical.test(placement[0])) {
|
|
targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
|
|
} else {
|
|
targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
|
|
}
|
|
break;
|
|
}
|
|
|
|
targetElemPos.top = Math.round(targetElemPos.top);
|
|
targetElemPos.left = Math.round(targetElemPos.left);
|
|
targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];
|
|
|
|
return targetElemPos;
|
|
},
|
|
|
|
/**
|
|
* Provides a way to adjust the top positioning after first
|
|
* render to correctly align element to top after content
|
|
* rendering causes resized element height
|
|
*
|
|
* @param {array} placementClasses - The array of strings of classes
|
|
* element should have.
|
|
* @param {object} containerPosition - The object with container
|
|
* position information
|
|
* @param {number} initialHeight - The initial height for the elem.
|
|
* @param {number} currentHeight - The current height for the elem.
|
|
*/
|
|
adjustTop: function(placementClasses, containerPosition, initialHeight, currentHeight) {
|
|
if (placementClasses.indexOf('top') !== -1 && initialHeight !== currentHeight) {
|
|
return {
|
|
top: containerPosition.top - currentHeight + 'px'
|
|
};
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Provides a way for positioning tooltip & dropdown
|
|
* arrows when using placement options beyond the standard
|
|
* left, right, top, or bottom.
|
|
*
|
|
* @param {element} elem - The tooltip/dropdown element.
|
|
* @param {string} placement - The placement for the elem.
|
|
*/
|
|
positionArrow: function(elem, placement) {
|
|
elem = this.getRawNode(elem);
|
|
|
|
var innerElem = elem.querySelector('.tooltip-inner, .popover-inner');
|
|
if (!innerElem) {
|
|
return;
|
|
}
|
|
|
|
var isTooltip = angular.element(innerElem).hasClass('tooltip-inner');
|
|
|
|
var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
|
|
if (!arrowElem) {
|
|
return;
|
|
}
|
|
|
|
var arrowCss = {
|
|
top: '',
|
|
bottom: '',
|
|
left: '',
|
|
right: ''
|
|
};
|
|
|
|
placement = this.parsePlacement(placement);
|
|
if (placement[1] === 'center') {
|
|
// no adjustment necessary - just reset styles
|
|
angular.element(arrowElem).css(arrowCss);
|
|
return;
|
|
}
|
|
|
|
var borderProp = 'border-' + placement[0] + '-width';
|
|
var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];
|
|
|
|
var borderRadiusProp = 'border-';
|
|
if (PLACEMENT_REGEX.vertical.test(placement[0])) {
|
|
borderRadiusProp += placement[0] + '-' + placement[1];
|
|
} else {
|
|
borderRadiusProp += placement[1] + '-' + placement[0];
|
|
}
|
|
borderRadiusProp += '-radius';
|
|
var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];
|
|
|
|
switch (placement[0]) {
|
|
case 'top':
|
|
arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
|
|
break;
|
|
case 'bottom':
|
|
arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
|
|
break;
|
|
case 'left':
|
|
arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
|
|
break;
|
|
case 'right':
|
|
arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
|
|
break;
|
|
}
|
|
|
|
arrowCss[placement[1]] = borderRadius;
|
|
|
|
angular.element(arrowElem).css(arrowCss);
|
|
}
|
|
};
|
|
}]);
|
|
|
|
angular.module('ui.bootstrap-custom.stackedMap', [])
|
|
/**
|
|
* A helper, internal data structure that acts as a map but also allows getting / removing
|
|
* elements in the LIFO order
|
|
*/
|
|
.factory('$$stackedMap', function() {
|
|
return {
|
|
createNew: function() {
|
|
var stack = [];
|
|
|
|
return {
|
|
add: function(key, value) {
|
|
stack.push({
|
|
key: key,
|
|
value: value
|
|
});
|
|
},
|
|
get: function(key) {
|
|
for (var i = 0; i < stack.length; i++) {
|
|
if (key === stack[i].key) {
|
|
return stack[i];
|
|
}
|
|
}
|
|
},
|
|
keys: function() {
|
|
var keys = [];
|
|
for (var i = 0; i < stack.length; i++) {
|
|
keys.push(stack[i].key);
|
|
}
|
|
return keys;
|
|
},
|
|
top: function() {
|
|
return stack[stack.length - 1];
|
|
},
|
|
remove: function(key) {
|
|
var idx = -1;
|
|
for (var i = 0; i < stack.length; i++) {
|
|
if (key === stack[i].key) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
return stack.splice(idx, 1)[0];
|
|
},
|
|
removeTop: function() {
|
|
return stack.pop();
|
|
},
|
|
length: function() {
|
|
return stack.length;
|
|
}
|
|
};
|
|
}
|
|
};
|
|
});
|
|
angular.module("uib/template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) {
|
|
$templateCache.put("uib/template/popover/popover-html.html",
|
|
"<div class=\"arrow\"></div>\n" +
|
|
"\n" +
|
|
"<div class=\"popover-inner\">\n" +
|
|
" <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" +
|
|
" <div class=\"popover-content\" ng-bind-html=\"contentExp()\"></div>\n" +
|
|
"</div>\n" +
|
|
"");
|
|
}]);
|
|
|
|
angular.module("uib/template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) {
|
|
$templateCache.put("uib/template/popover/popover-template.html",
|
|
"<div class=\"arrow\"></div>\n" +
|
|
"\n" +
|
|
"<div class=\"popover-inner\">\n" +
|
|
" <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" +
|
|
" <div class=\"popover-content\"\n" +
|
|
" uib-tooltip-template-transclude=\"contentExp()\"\n" +
|
|
" tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
|
|
"</div>\n" +
|
|
"");
|
|
}]);
|
|
|
|
angular.module("uib/template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
|
|
$templateCache.put("uib/template/popover/popover.html",
|
|
"<div class=\"arrow\"></div>\n" +
|
|
"\n" +
|
|
"<div class=\"popover-inner\">\n" +
|
|
" <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" +
|
|
" <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
|
|
"</div>\n" +
|
|
"");
|
|
}]);
|
|
|
|
angular.module("uib/template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) {
|
|
$templateCache.put("uib/template/tooltip/tooltip-html-popup.html",
|
|
"<div class=\"tooltip-arrow\"></div>\n" +
|
|
"<div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n" +
|
|
"");
|
|
}]);
|
|
|
|
angular.module("uib/template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
|
|
$templateCache.put("uib/template/tooltip/tooltip-popup.html",
|
|
"<div class=\"tooltip-arrow\"></div>\n" +
|
|
"<div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
|
|
"");
|
|
}]);
|
|
|
|
angular.module("uib/template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) {
|
|
$templateCache.put("uib/template/tooltip/tooltip-template-popup.html",
|
|
"<div class=\"tooltip-arrow\"></div>\n" +
|
|
"<div class=\"tooltip-inner\"\n" +
|
|
" uib-tooltip-template-transclude=\"contentExp()\"\n" +
|
|
" tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
|
|
"");
|
|
}]);
|
|
angular.module('ui.bootstrap-custom.tooltip').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow,[uib-popover-popup].popover.top-left > .arrow,[uib-popover-popup].popover.top-right > .arrow,[uib-popover-popup].popover.bottom-left > .arrow,[uib-popover-popup].popover.bottom-right > .arrow,[uib-popover-popup].popover.left-top > .arrow,[uib-popover-popup].popover.left-bottom > .arrow,[uib-popover-popup].popover.right-top > .arrow,[uib-popover-popup].popover.right-bottom > .arrow,[uib-popover-html-popup].popover.top-left > .arrow,[uib-popover-html-popup].popover.top-right > .arrow,[uib-popover-html-popup].popover.bottom-left > .arrow,[uib-popover-html-popup].popover.bottom-right > .arrow,[uib-popover-html-popup].popover.left-top > .arrow,[uib-popover-html-popup].popover.left-bottom > .arrow,[uib-popover-html-popup].popover.right-top > .arrow,[uib-popover-html-popup].popover.right-bottom > .arrow,[uib-popover-template-popup].popover.top-left > .arrow,[uib-popover-template-popup].popover.top-right > .arrow,[uib-popover-template-popup].popover.bottom-left > .arrow,[uib-popover-template-popup].popover.bottom-right > .arrow,[uib-popover-template-popup].popover.left-top > .arrow,[uib-popover-template-popup].popover.left-bottom > .arrow,[uib-popover-template-popup].popover.right-top > .arrow,[uib-popover-template-popup].popover.right-bottom > .arrow{top:auto;bottom:auto;left:auto;right:auto;margin:0;}[uib-popover-popup].popover,[uib-popover-html-popup].popover,[uib-popover-template-popup].popover{display:block !important;}</style>'); angular.$$uibTooltipCss = true; });
|
|
angular.module('ui.bootstrap-custom.position').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibPositionCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-position-measure{display:block !important;visibility:hidden !important;position:absolute !important;top:-9999px !important;left:-9999px !important;}.uib-position-scrollbar-measure{position:absolute !important;top:-9999px !important;width:50px !important;height:50px !important;overflow:scroll !important;}.uib-position-body-scrollbar-measure{overflow:scroll !important;}</style>'); angular.$$uibPositionCss = true; }); |