From 9f9b2fbcd87163e898a4d35ff277a966921cf8ab Mon Sep 17 00:00:00 2001
From: Joel Steres
Date: Wed, 24 Aug 2016 20:39:38 -0700
Subject: [PATCH 1/9] Compensate for body tag with position absolute, relative
or static
Also opts for making all placement based on top rather than bottom.
---
src/placementcalculator.js | 48 ++++++++++----------
src/tooltipcontroller.js | 16 +++----
src/utility.js | 46 ++++++++++++++++++-
test/bodyoffset-abs.html | 78 ++++++++++++++++++++++++++++++++
test/bodyoffset-rel.html | 78 ++++++++++++++++++++++++++++++++
test/tests-bodyoffset.js | 69 ++++++++++++++++++++++++++++
test/unit/placementcalculator.js | 14 +-----
7 files changed, 303 insertions(+), 46 deletions(-)
create mode 100644 test/bodyoffset-abs.html
create mode 100644 test/bodyoffset-rel.html
create mode 100644 test/tests-bodyoffset.js
diff --git a/src/placementcalculator.js b/src/placementcalculator.js
index 1ec10ed6..567ce9fd 100644
--- a/src/placementcalculator.js
+++ b/src/placementcalculator.js
@@ -38,52 +38,52 @@ function PlacementCalculator() {
// calculate the appropriate x and y position in the document
switch (placement) {
case 'n':
- coords.set('left', position.left - (tipWidth / 2));
- coords.set('bottom', session.windowHeight - position.top + offset);
+ coords.set('left', position.left - (tipWidth / 2) - session.positionCompensation.left);
+ coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
break;
case 'e':
- coords.set('left', position.left + offset);
- coords.set('top', position.top - (tipHeight / 2));
+ coords.set('left', position.left + offset - session.positionCompensation.left);
+ coords.set('top', position.top - (tipHeight / 2) - session.positionCompensation.top);
break;
case 's':
- coords.set('left', position.left - (tipWidth / 2));
- coords.set('top', position.top + offset);
+ coords.set('left', position.left - (tipWidth / 2) - session.positionCompensation.left);
+ coords.set('top', position.top + offset - session.positionCompensation.top);
break;
case 'w':
- coords.set('top', position.top - (tipHeight / 2));
- coords.set('right', session.windowWidth - position.left + offset);
+ coords.set('top', position.top - (tipHeight / 2) - session.positionCompensation.top);
+ coords.set('right', session.windowWidth - position.left + offset - session.positionCompensation.right);
break;
case 'nw':
- coords.set('bottom', session.windowHeight - position.top + offset);
- coords.set('right', session.windowWidth - position.left - 20);
+ coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
+ coords.set('right', session.windowWidth - position.left - session.positionCompensation.right - 20);
break;
case 'nw-alt':
- coords.set('left', position.left);
- coords.set('bottom', session.windowHeight - position.top + offset);
+ coords.set('left', position.left - session.positionCompensation.left);
+ coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
break;
case 'ne':
- coords.set('left', position.left - 20);
- coords.set('bottom', session.windowHeight - position.top + offset);
+ coords.set('left', position.left - session.positionCompensation.left - 20);
+ coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
break;
case 'ne-alt':
- coords.set('bottom', session.windowHeight - position.top + offset);
- coords.set('right', session.windowWidth - position.left);
+ coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
+ coords.set('right', session.windowWidth - position.left - session.positionCompensation.right);
break;
case 'sw':
- coords.set('top', position.top + offset);
- coords.set('right', session.windowWidth - position.left - 20);
+ coords.set('top', position.top + offset - session.positionCompensation.top);
+ coords.set('right', session.windowWidth - position.left - session.positionCompensation.right - 20);
break;
case 'sw-alt':
- coords.set('left', position.left);
- coords.set('top', position.top + offset);
+ coords.set('left', position.left - session.positionCompensation.left);
+ coords.set('top', position.top + offset - session.positionCompensation.top);
break;
case 'se':
- coords.set('left', position.left - 20);
- coords.set('top', position.top + offset);
+ coords.set('left', position.left - session.positionCompensation.left - 20);
+ coords.set('top', position.top + offset - session.positionCompensation.top);
break;
case 'se-alt':
- coords.set('top', position.top + offset);
- coords.set('right', session.windowWidth - position.left);
+ coords.set('top', position.top + offset - session.positionCompensation.top);
+ coords.set('right', session.windowWidth - position.left - session.positionCompensation.right);
break;
}
diff --git a/src/tooltipcontroller.js b/src/tooltipcontroller.js
index f5d258f7..81d6ec86 100644
--- a/src/tooltipcontroller.js
+++ b/src/tooltipcontroller.js
@@ -202,8 +202,8 @@ function TooltipController(options) {
// support mouse-follow and fixed position tips at the same time by
// moving the tooltip to the last cursor location after it is hidden
- coords.set('top', session.currentY + options.offset);
- coords.set('left', session.currentX + options.offset);
+ coords.set('top', session.currentY + options.offset - session.positionCompensation.top);
+ coords.set('left', session.currentX + options.offset - session.positionCompensation.left);
tipElement.css(coords);
// trigger powerTipClose event
@@ -231,8 +231,8 @@ function TooltipController(options) {
collisionCount;
// grab collisions
- coords.set('top', session.currentY + options.offset);
- coords.set('left', session.currentX + options.offset);
+ coords.set('top', session.currentY + options.offset - session.positionCompensation.top);
+ coords.set('left', session.currentX + options.offset - session.positionCompensation.left);
collisions = getViewportCollisions(
coords,
tipWidth,
@@ -246,16 +246,16 @@ function TooltipController(options) {
// if there is only one collision (bottom or right) then
// simply constrain the tooltip to the view port
if (collisions === Collision.right) {
- coords.set('left', session.windowWidth - tipWidth);
+ coords.set('left', session.windowWidth - tipWidth - session.positionCompensation.left);
} else if (collisions === Collision.bottom) {
- coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
+ coords.set('top', session.scrollTop + session.windowHeight - tipHeight - session.positionCompensation.top);
}
} else {
// if the tooltip has more than one collision then it is
// trapped in the corner and should be flipped to get it out
// of the users way
- coords.set('left', session.currentX - tipWidth - options.offset);
- coords.set('top', session.currentY - tipHeight - options.offset);
+ coords.set('left', session.currentX - tipWidth - options.offset - session.positionCompensation.left);
+ coords.set('top', session.currentY - tipHeight - options.offset - session.positionCompensation.top);
}
}
diff --git a/src/utility.js b/src/utility.js
index b1b5455f..be68c88e 100644
--- a/src/utility.js
+++ b/src/utility.js
@@ -60,6 +60,7 @@ function getViewportDimensions() {
session.scrollTop = $window.scrollTop();
session.windowWidth = $window.width();
session.windowHeight = $window.height();
+ session.positionCompensation = computePositionCompensation(session.windowWidth, session.windowHeight);
}
/**
@@ -69,6 +70,7 @@ function getViewportDimensions() {
function trackResize() {
session.windowWidth = $window.width();
session.windowHeight = $window.height();
+ session.positionCompensation = computePositionCompensation(session.windowWidth, session.windowHeight);
}
/**
@@ -166,8 +168,10 @@ function getTooltipContent(element) {
* @return {number} Value with the collision flags.
*/
function getViewportCollisions(coords, elementWidth, elementHeight) {
- var viewportTop = session.scrollTop,
- viewportLeft = session.scrollLeft,
+ // adjusting viewport even though it might be negative because coords
+ // comparing with are relative to compensated position
+ var viewportTop = session.scrollTop - session.positionCompensation.top,
+ viewportLeft = session.scrollLeft - session.positionCompensation.left,
viewportBottom = viewportTop + session.windowHeight,
viewportRight = viewportLeft + session.windowWidth,
collisions = Collision.none;
@@ -201,3 +205,41 @@ function countFlags(value) {
}
return count;
}
+
+/**
+ * Compute compensating position offsets if body element has non-standard position attribute.
+ * @private
+ * @param {number} windowWidth Window width in pixels.
+ * @param {number} windowHeight Window height in pixels.
+ * @return {Offsets} The top, left, right, bottom offset in pixels
+ */
+function computePositionCompensation(windowWidth, windowHeight) {
+ var bodyWidthWithMargin,
+ bodyHeightWithMargin,
+ offsets,
+ bodyPositionPx;
+
+ switch ($body.css('position')) {
+ case 'absolute':
+ case 'fixed':
+ case 'relative':
+ // jquery offset and position functions return top and left
+ // offset function computes position + margin
+ offsets = $body.offset();
+ bodyPositionPx = $body.position();
+ // because element might be positioned compute right margin using the different between
+ // outerWidth computations and add position offset
+ bodyWidthWithMargin = $body.outerWidth(true);
+ bodyHeightWithMargin = $body.outerHeight(true);
+ // right offset = right margin + body right position
+ offsets.right = (bodyWidthWithMargin - $body.outerWidth() - (offsets.left - bodyPositionPx.left)) + (windowWidth - bodyWidthWithMargin - bodyPositionPx.left);
+ // bottom offset = bottom margin + body bottom position
+ offsets.bottom = (bodyHeightWithMargin - $body.outerHeight() - offsets.top) + (windowHeight - bodyHeightWithMargin - bodyPositionPx.top);
+ break;
+ default:
+ // even though body may have offset, no compensation is required
+ offsets = { top: 0, bottom: 0, left: 0, right: 0 };
+ }
+
+ return offsets;
+}
diff --git a/test/bodyoffset-abs.html b/test/bodyoffset-abs.html
new file mode 100644
index 00000000..62775272
--- /dev/null
+++ b/test/bodyoffset-abs.html
@@ -0,0 +1,78 @@
+
+
+
+
+ PowerTip Test Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Huge Text
+
The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Huge Text with Smart Placement
+
The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Trapped mouse following tooltip
+
This box has a mouse following tooltip.
+
Trap it in the bottom right corner of the viewport. It should flip out of the way. It should not flip if it only hits one edge.
The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Huge Text with Smart Placement
+
The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Trapped mouse following tooltip
+
This box has a mouse following tooltip.
+
Trap it in the bottom right corner of the viewport. It should flip out of the way. It should not flip if it only hits one edge.
+
+
+
+
diff --git a/test/tests-bodyoffset.js b/test/tests-bodyoffset.js
new file mode 100644
index 00000000..34603564
--- /dev/null
+++ b/test/tests-bodyoffset.js
@@ -0,0 +1,69 @@
+$(function() {
+ 'use strict';
+
+ // setup huge text tooltips
+ var hugeText = [
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent sed',
+ 'volutpat tellus. Fusce mollis iaculis est at sodales. Proin aliquam',
+ 'bibendum neque, nec blandit orci porttitor non. Cras lacinia varius',
+ 'felis vel ultricies. Nulla eu sapien arcu, dapibus tempor eros.',
+ 'Praesent aliquet hendrerit commodo. Pellentesque habitant morbi',
+ 'tristique senectus et netus et malesuada fames ac turpis egestas.',
+ 'Proin gravida justo faucibus urna dictum id egestas velit hendrerit.',
+ 'Praesent dapibus rutrum tempor. Sed ultrices varius purus, eu rhoncus',
+ 'tortor scelerisque sit amet. Sed vitae molestie diam. Pellentesque',
+ 'posuere euismod venenatis. Proin ut ligula vel urna lacinia accumsan.',
+ 'Quisque commodo ultrices orci ut cursus. Aliquam in dolor orci. Nunc',
+ 'pretium euismod odio.'
+ ].join(' ');
+ $.each(
+ [
+ 'north',
+ 'east',
+ 'south',
+ 'west',
+ 'north-west',
+ 'north-east',
+ 'south-west',
+ 'south-east',
+ 'north-west-alt',
+ 'north-east-alt',
+ 'south-west-alt',
+ 'south-east-alt'
+ ],
+ function(i, val) {
+ $('.' + val).data('powertip', hugeText);
+ }
+ );
+
+ // Huge text
+ $('#huge-text .north').powerTip({ placement: 'n' });
+ $('#huge-text .east').powerTip({ placement: 'e' });
+ $('#huge-text .south').powerTip({ placement: 's' });
+ $('#huge-text .west').powerTip({ placement: 'w' });
+ $('#huge-text .north-west').powerTip({ placement: 'nw' });
+ $('#huge-text .north-east').powerTip({ placement: 'ne' });
+ $('#huge-text .south-west').powerTip({ placement: 'sw' });
+ $('#huge-text .south-east').powerTip({ placement: 'se' });
+ $('#huge-text .north-west-alt').powerTip({ placement: 'nw-alt' });
+ $('#huge-text .north-east-alt').powerTip({ placement: 'ne-alt' });
+ $('#huge-text .south-west-alt').powerTip({ placement: 'sw-alt' });
+ $('#huge-text .south-east-alt').powerTip({ placement: 'se-alt' });
+
+ // Huge text with smart placement
+ $('#huge-text-smart .north').powerTip({ placement: 'n', smartPlacement: true });
+ $('#huge-text-smart .east').powerTip({ placement: 'e', smartPlacement: true });
+ $('#huge-text-smart .south').powerTip({ placement: 's', smartPlacement: true });
+ $('#huge-text-smart .west').powerTip({ placement: 'w', smartPlacement: true });
+ $('#huge-text-smart .north-west').powerTip({ placement: 'nw', smartPlacement: true });
+ $('#huge-text-smart .north-east').powerTip({ placement: 'ne', smartPlacement: true });
+ $('#huge-text-smart .south-west').powerTip({ placement: 'sw', smartPlacement: true });
+ $('#huge-text-smart .south-east').powerTip({ placement: 'se', smartPlacement: true });
+ $('#huge-text-smart .north-west-alt').powerTip({ placement: 'nw-alt', smartPlacement: true });
+ $('#huge-text-smart .north-east-alt').powerTip({ placement: 'ne-alt', smartPlacement: true });
+ $('#huge-text-smart .south-west-alt').powerTip({ placement: 'sw-alt', smartPlacement: true });
+ $('#huge-text-smart .south-east-alt').powerTip({ placement: 'se-alt', smartPlacement: true });
+
+ // Trapped mouse following tooltip
+ $('#trapped-mousefollow').powerTip({ followMouse: true });
+});
diff --git a/test/unit/placementcalculator.js b/test/unit/placementcalculator.js
index b18c57aa..dd09873c 100644
--- a/test/unit/placementcalculator.js
+++ b/test/unit/placementcalculator.js
@@ -40,11 +40,6 @@ $(function() {
case 'n':
case 'ne':
case 'nw-alt':
- assert.strictEqual(coords.top, 'auto', key + ': top property is set to auto');
- assert.strictEqual($.isNumeric(coords.left), true, key + ': left property is set to a number');
- assert.strictEqual(coords.right, 'auto', key + ': right property is set to auto');
- assert.strictEqual($.isNumeric(coords.bottom), true, key + ': bottom property is set to a number');
- break;
case 'e':
case 's':
case 'se':
@@ -57,17 +52,12 @@ $(function() {
case 'w':
case 'sw':
case 'se-alt':
- assert.strictEqual($.isNumeric(coords.top), true, key + ': top property is set to a number');
- assert.strictEqual(coords.left, 'auto', key + ': left property is set to auto');
- assert.strictEqual($.isNumeric(coords.right), true, key + ': right property is set to a number');
- assert.strictEqual(coords.bottom, 'auto', key + ': bottom property is set to auto');
- break;
case 'nw':
case 'ne-alt':
- assert.strictEqual(coords.top, 'auto', key + ': top property is set to auto');
+ assert.strictEqual($.isNumeric(coords.top), true, key + ': top property is set to a number');
assert.strictEqual(coords.left, 'auto', key + ': left property is set to auto');
assert.strictEqual($.isNumeric(coords.right), true, key + ': right property is set to a number');
- assert.strictEqual($.isNumeric(coords.bottom), true, key + ': bottom property is set to a number');
+ assert.strictEqual(coords.bottom, 'auto', key + ': bottom property is set to auto');
break;
}
});
From 66baec7b5d2ae34d2bffb197c99b59e02d380e23 Mon Sep 17 00:00:00 2001
From: Joel Steres
Date: Mon, 17 Oct 2016 14:02:47 -0700
Subject: [PATCH 2/9] Position north side placements using bottom css
As requested in #151 referencing issue #31 for rationale
---
src/placementcalculator.js | 10 +++++-----
test/unit/placementcalculator.js | 14 ++++++++++++--
2 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/src/placementcalculator.js b/src/placementcalculator.js
index 567ce9fd..e476d77f 100644
--- a/src/placementcalculator.js
+++ b/src/placementcalculator.js
@@ -39,7 +39,7 @@ function PlacementCalculator() {
switch (placement) {
case 'n':
coords.set('left', position.left - (tipWidth / 2) - session.positionCompensation.left);
- coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
+ coords.set('bottom', session.windowHeight - position.top + offset - session.positionCompensation.bottom);
break;
case 'e':
coords.set('left', position.left + offset - session.positionCompensation.left);
@@ -54,19 +54,19 @@ function PlacementCalculator() {
coords.set('right', session.windowWidth - position.left + offset - session.positionCompensation.right);
break;
case 'nw':
- coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
+ coords.set('bottom', session.windowHeight - position.top + offset - session.positionCompensation.bottom);
coords.set('right', session.windowWidth - position.left - session.positionCompensation.right - 20);
break;
case 'nw-alt':
coords.set('left', position.left - session.positionCompensation.left);
- coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
+ coords.set('bottom', session.windowHeight - position.top + offset - session.positionCompensation.bottom);
break;
case 'ne':
coords.set('left', position.left - session.positionCompensation.left - 20);
- coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
+ coords.set('bottom', session.windowHeight - position.top + offset - session.positionCompensation.bottom);
break;
case 'ne-alt':
- coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
+ coords.set('bottom', session.windowHeight - position.top + offset - session.positionCompensation.bottom);
coords.set('right', session.windowWidth - position.left - session.positionCompensation.right);
break;
case 'sw':
diff --git a/test/unit/placementcalculator.js b/test/unit/placementcalculator.js
index dd09873c..b18c57aa 100644
--- a/test/unit/placementcalculator.js
+++ b/test/unit/placementcalculator.js
@@ -40,6 +40,11 @@ $(function() {
case 'n':
case 'ne':
case 'nw-alt':
+ assert.strictEqual(coords.top, 'auto', key + ': top property is set to auto');
+ assert.strictEqual($.isNumeric(coords.left), true, key + ': left property is set to a number');
+ assert.strictEqual(coords.right, 'auto', key + ': right property is set to auto');
+ assert.strictEqual($.isNumeric(coords.bottom), true, key + ': bottom property is set to a number');
+ break;
case 'e':
case 's':
case 'se':
@@ -52,13 +57,18 @@ $(function() {
case 'w':
case 'sw':
case 'se-alt':
- case 'nw':
- case 'ne-alt':
assert.strictEqual($.isNumeric(coords.top), true, key + ': top property is set to a number');
assert.strictEqual(coords.left, 'auto', key + ': left property is set to auto');
assert.strictEqual($.isNumeric(coords.right), true, key + ': right property is set to a number');
assert.strictEqual(coords.bottom, 'auto', key + ': bottom property is set to auto');
break;
+ case 'nw':
+ case 'ne-alt':
+ assert.strictEqual(coords.top, 'auto', key + ': top property is set to auto');
+ assert.strictEqual(coords.left, 'auto', key + ': left property is set to auto');
+ assert.strictEqual($.isNumeric(coords.right), true, key + ': right property is set to a number');
+ assert.strictEqual($.isNumeric(coords.bottom), true, key + ': bottom property is set to a number');
+ break;
}
});
});
From 5bf6a63637857c9c49c7d194039381f7ec6d898a Mon Sep 17 00:00:00 2001
From: Joel Steres
Date: Fri, 9 Dec 2016 09:05:17 -0800
Subject: [PATCH 3/9] Compensate for positioned HTML element
---
src/core.js | 1 +
src/utility.js | 65 +++++++++++++++++++--------------
test/htmloffset-rel.html | 78 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 118 insertions(+), 26 deletions(-)
create mode 100644 test/htmloffset-rel.html
diff --git a/src/core.js b/src/core.js
index cc11d8c5..27bd7c78 100644
--- a/src/core.js
+++ b/src/core.js
@@ -10,6 +10,7 @@
// useful private variables
var $document = $(document),
$window = $(window),
+ $html = $(document.documentElement),
$body = $('body');
// constants
diff --git a/src/utility.js b/src/utility.js
index be68c88e..d8a94998 100644
--- a/src/utility.js
+++ b/src/utility.js
@@ -207,38 +207,51 @@ function countFlags(value) {
}
/**
- * Compute compensating position offsets if body element has non-standard position attribute.
+ * Compute compensating position offsets if body or html element has non-static position attribute.
* @private
* @param {number} windowWidth Window width in pixels.
* @param {number} windowHeight Window height in pixels.
* @return {Offsets} The top, left, right, bottom offset in pixels
*/
function computePositionCompensation(windowWidth, windowHeight) {
- var bodyWidthWithMargin,
- bodyHeightWithMargin,
- offsets,
- bodyPositionPx;
-
- switch ($body.css('position')) {
- case 'absolute':
- case 'fixed':
- case 'relative':
- // jquery offset and position functions return top and left
- // offset function computes position + margin
- offsets = $body.offset();
- bodyPositionPx = $body.position();
- // because element might be positioned compute right margin using the different between
- // outerWidth computations and add position offset
- bodyWidthWithMargin = $body.outerWidth(true);
- bodyHeightWithMargin = $body.outerHeight(true);
- // right offset = right margin + body right position
- offsets.right = (bodyWidthWithMargin - $body.outerWidth() - (offsets.left - bodyPositionPx.left)) + (windowWidth - bodyWidthWithMargin - bodyPositionPx.left);
- // bottom offset = bottom margin + body bottom position
- offsets.bottom = (bodyHeightWithMargin - $body.outerHeight() - offsets.top) + (windowHeight - bodyHeightWithMargin - bodyPositionPx.top);
- break;
- default:
- // even though body may have offset, no compensation is required
- offsets = { top: 0, bottom: 0, left: 0, right: 0 };
+ // Check if the element is "positioned". A "positioned" element has a CSS
+ // position value other than static. Whether the element is positioned is
+ // relevant because absolutely positioned elements are positioned relative
+ // to the first positioned ancestor rather than relative to the doc origin.
+ var isPositioned = function(el) {
+ return el.css('position') !== 'static';
+ };
+
+ var getElementOffsets = function(el) {
+ var elWidthWithMargin,
+ elHeightWithMargin,
+ elPositionPx,
+ offsets;
+ // jquery offset and position functions return top and left
+ // offset function computes position + margin
+ offsets = el.offset();
+ elPositionPx = el.position();
+
+ // Compute the far margins based off the outerWidth values.
+ elWidthWithMargin = el.outerWidth(true);
+ elHeightWithMargin = el.outerHeight(true);
+
+ // right offset = right margin + body right position
+ offsets.right = (elWidthWithMargin - el.outerWidth() - (offsets.left - elPositionPx.left)) + (windowWidth - elWidthWithMargin - elPositionPx.left);
+ // bottom offset = bottom margin + body bottom position
+ offsets.bottom = (elHeightWithMargin - el.outerHeight() - offsets.top) + (windowHeight - elHeightWithMargin - elPositionPx.top);
+ return offsets;
+ };
+
+ var offsets;
+
+ if (isPositioned($body)) {
+ offsets = getElementOffsets($body);
+ } else if (isPositioned($html)) {
+ offsets = getElementOffsets($html);
+ } else {
+ // even though body may have offset, no compensation is required
+ offsets = { top: 0, bottom: 0, left: 0, right: 0 };
}
return offsets;
diff --git a/test/htmloffset-rel.html b/test/htmloffset-rel.html
new file mode 100644
index 00000000..42fa900b
--- /dev/null
+++ b/test/htmloffset-rel.html
@@ -0,0 +1,78 @@
+
+
+
+
+ PowerTip Test Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Huge Text
+
The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Huge Text with Smart Placement
+
The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Trapped mouse following tooltip
+
This box has a mouse following tooltip.
+
Trap it in the bottom right corner of the viewport. It should flip out of the way. It should not flip if it only hits one edge.
+
+
+
+
From 2d41b1e557c74726dfd9c5eb621742cf3e3ad166 Mon Sep 17 00:00:00 2001
From: Joel Steres
Date: Mon, 30 Jan 2017 15:44:32 -0800
Subject: [PATCH 4/9] Refactor inlined functions to top level per feedback. Fix
bottom calc.
Also simplified both bottom and right offset calculations by removing
canceled terms.
---
src/utility.js | 61 ++++++++++++++++++++++++++------------------------
1 file changed, 32 insertions(+), 29 deletions(-)
diff --git a/src/utility.js b/src/utility.js
index d8a94998..27310078 100644
--- a/src/utility.js
+++ b/src/utility.js
@@ -206,49 +206,52 @@ function countFlags(value) {
return count;
}
+/**
+ * Check whether element has CSS position attribute other than static
+ * @private
+ * @param {jQuery} element Element to check
+ * @return {boolean} indicating whether position attribute is non-static.
+ */
+function isPositionNotStatic(element) {
+ return element.css('position') !== 'static';
+}
+
+/**
+ * Get element offsets
+ * @private
+ * @param {jQuery} el Element to check
+ * @param {number} windowWidth Window width in pixels.
+ * @param {number} windowHeight Window height in pixels.
+ * @return {Object} The top, left, right, bottom offset in pixels
+ */
+function getElementOffsets(el, windowWidth, windowHeight) {
+ // jquery offset returns top and left relative to document in pixels.
+ var offsets = el.offset();
+ // right and bottom offset relative to window width/height
+ offsets.right = windowWidth - el.outerWidth() - offsets.left;
+ offsets.bottom = windowHeight - el.outerHeight() - offsets.top;
+ return offsets;
+}
+
/**
* Compute compensating position offsets if body or html element has non-static position attribute.
* @private
* @param {number} windowWidth Window width in pixels.
* @param {number} windowHeight Window height in pixels.
- * @return {Offsets} The top, left, right, bottom offset in pixels
+ * @return {Object} The top, left, right, bottom offset in pixels
*/
function computePositionCompensation(windowWidth, windowHeight) {
// Check if the element is "positioned". A "positioned" element has a CSS
// position value other than static. Whether the element is positioned is
// relevant because absolutely positioned elements are positioned relative
// to the first positioned ancestor rather than relative to the doc origin.
- var isPositioned = function(el) {
- return el.css('position') !== 'static';
- };
-
- var getElementOffsets = function(el) {
- var elWidthWithMargin,
- elHeightWithMargin,
- elPositionPx,
- offsets;
- // jquery offset and position functions return top and left
- // offset function computes position + margin
- offsets = el.offset();
- elPositionPx = el.position();
-
- // Compute the far margins based off the outerWidth values.
- elWidthWithMargin = el.outerWidth(true);
- elHeightWithMargin = el.outerHeight(true);
-
- // right offset = right margin + body right position
- offsets.right = (elWidthWithMargin - el.outerWidth() - (offsets.left - elPositionPx.left)) + (windowWidth - elWidthWithMargin - elPositionPx.left);
- // bottom offset = bottom margin + body bottom position
- offsets.bottom = (elHeightWithMargin - el.outerHeight() - offsets.top) + (windowHeight - elHeightWithMargin - elPositionPx.top);
- return offsets;
- };
var offsets;
- if (isPositioned($body)) {
- offsets = getElementOffsets($body);
- } else if (isPositioned($html)) {
- offsets = getElementOffsets($html);
+ if (isPositionNotStatic($body)) {
+ offsets = getElementOffsets($body, windowWidth, windowHeight);
+ } else if (isPositionNotStatic($html)) {
+ offsets = getElementOffsets($html, windowWidth, windowHeight);
} else {
// even though body may have offset, no compensation is required
offsets = { top: 0, bottom: 0, left: 0, right: 0 };
From 700edb760ac7d029ed494feb666b5b0fc0031186 Mon Sep 17 00:00:00 2001
From: Joel Steres
Date: Tue, 31 Jan 2017 04:23:01 -0800
Subject: [PATCH 5/9] Add position test cases to edge-cases
---
test/edge-cases.html | 55 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 54 insertions(+), 1 deletion(-)
diff --git a/test/edge-cases.html b/test/edge-cases.html
index 0da70549..b41267d7 100644
--- a/test/edge-cases.html
+++ b/test/edge-cases.html
@@ -29,11 +29,54 @@
$('#powertip-css').attr('href', '../css/jquery.powertip' + theme + '.css');
}
+ // css position switcher allows testing html and body CSS position values that
+ // changes the origin from which the tooltip is positioned.
+ function setCssPosition() {
+ var attributeSets = {
+ // default positioning is static
+ static: { position: '', left: '', right: '', top: '', bottom: '' },
+ absolute: { position: 'absolute', left: '50px', right: '100px', top: '25px', bottom: '75px' },
+ relative: { position: 'relative', left: '50px', right: '100px', top: '25px', bottom: '75px' },
+ fixed: { position: 'fixed', left: '50px', right: '100px', top: '25px', bottom: '75px' },
+ };
+ var posType = $('#position-switcher').val();
+ var $html = $(document.documentElement);
+ var $body = $(document.body);
+ $html.css(attributeSets['static']);
+ $body.css(attributeSets['static']);
+ switch(posType) {
+ case 'html-relative':
+ $html.css(attributeSets['relative']);
+ break;
+ case 'html-fixed':
+ $html.css(attributeSets['fixed']);
+ break;
+ case 'html-absolute':
+ $html.css(attributeSets['absolute']);
+ break;
+ case 'body-relative':
+ $body.css(attributeSets['relative']);
+ break;
+ case 'body-fixed':
+ $body.css(attributeSets['fixed']);
+ break;
+ case 'body-absolute':
+ $body.css(attributeSets['absolute']);
+ break;
+ default:
+ // attributes clear above
+ break;
+ }
+ // Trigger resize to force recalculation of position compensation
+ window.dispatchEvent(new Event('resize'));
+ }
+
// run theme switcher on page load
setTheme();
- // hook theme switcher to select change
+ // hook theme and position switcher to select change
$('#theme-switcher').on('change', setTheme);
+ $('#position-switcher').on('change', setCssPosition);
// session debug info box
var debugOutput = $('#session pre');
@@ -82,6 +125,16 @@
The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Huge Text with Smart Placement
-
The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Trapped mouse following tooltip
-
This box has a mouse following tooltip.
-
Trap it in the bottom right corner of the viewport. It should flip out of the way. It should not flip if it only hits one edge.
The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Huge Text with Smart Placement
-
The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Trapped mouse following tooltip
-
This box has a mouse following tooltip.
-
Trap it in the bottom right corner of the viewport. It should flip out of the way. It should not flip if it only hits one edge.
The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Huge Text with Smart Placement
-
The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Trapped mouse following tooltip
-
This box has a mouse following tooltip.
-
Trap it in the bottom right corner of the viewport. It should flip out of the way. It should not flip if it only hits one edge.
-
-
-
-
diff --git a/test/tests-bodyoffset.js b/test/tests-bodyoffset.js
deleted file mode 100644
index 34603564..00000000
--- a/test/tests-bodyoffset.js
+++ /dev/null
@@ -1,69 +0,0 @@
-$(function() {
- 'use strict';
-
- // setup huge text tooltips
- var hugeText = [
- 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent sed',
- 'volutpat tellus. Fusce mollis iaculis est at sodales. Proin aliquam',
- 'bibendum neque, nec blandit orci porttitor non. Cras lacinia varius',
- 'felis vel ultricies. Nulla eu sapien arcu, dapibus tempor eros.',
- 'Praesent aliquet hendrerit commodo. Pellentesque habitant morbi',
- 'tristique senectus et netus et malesuada fames ac turpis egestas.',
- 'Proin gravida justo faucibus urna dictum id egestas velit hendrerit.',
- 'Praesent dapibus rutrum tempor. Sed ultrices varius purus, eu rhoncus',
- 'tortor scelerisque sit amet. Sed vitae molestie diam. Pellentesque',
- 'posuere euismod venenatis. Proin ut ligula vel urna lacinia accumsan.',
- 'Quisque commodo ultrices orci ut cursus. Aliquam in dolor orci. Nunc',
- 'pretium euismod odio.'
- ].join(' ');
- $.each(
- [
- 'north',
- 'east',
- 'south',
- 'west',
- 'north-west',
- 'north-east',
- 'south-west',
- 'south-east',
- 'north-west-alt',
- 'north-east-alt',
- 'south-west-alt',
- 'south-east-alt'
- ],
- function(i, val) {
- $('.' + val).data('powertip', hugeText);
- }
- );
-
- // Huge text
- $('#huge-text .north').powerTip({ placement: 'n' });
- $('#huge-text .east').powerTip({ placement: 'e' });
- $('#huge-text .south').powerTip({ placement: 's' });
- $('#huge-text .west').powerTip({ placement: 'w' });
- $('#huge-text .north-west').powerTip({ placement: 'nw' });
- $('#huge-text .north-east').powerTip({ placement: 'ne' });
- $('#huge-text .south-west').powerTip({ placement: 'sw' });
- $('#huge-text .south-east').powerTip({ placement: 'se' });
- $('#huge-text .north-west-alt').powerTip({ placement: 'nw-alt' });
- $('#huge-text .north-east-alt').powerTip({ placement: 'ne-alt' });
- $('#huge-text .south-west-alt').powerTip({ placement: 'sw-alt' });
- $('#huge-text .south-east-alt').powerTip({ placement: 'se-alt' });
-
- // Huge text with smart placement
- $('#huge-text-smart .north').powerTip({ placement: 'n', smartPlacement: true });
- $('#huge-text-smart .east').powerTip({ placement: 'e', smartPlacement: true });
- $('#huge-text-smart .south').powerTip({ placement: 's', smartPlacement: true });
- $('#huge-text-smart .west').powerTip({ placement: 'w', smartPlacement: true });
- $('#huge-text-smart .north-west').powerTip({ placement: 'nw', smartPlacement: true });
- $('#huge-text-smart .north-east').powerTip({ placement: 'ne', smartPlacement: true });
- $('#huge-text-smart .south-west').powerTip({ placement: 'sw', smartPlacement: true });
- $('#huge-text-smart .south-east').powerTip({ placement: 'se', smartPlacement: true });
- $('#huge-text-smart .north-west-alt').powerTip({ placement: 'nw-alt', smartPlacement: true });
- $('#huge-text-smart .north-east-alt').powerTip({ placement: 'ne-alt', smartPlacement: true });
- $('#huge-text-smart .south-west-alt').powerTip({ placement: 'sw-alt', smartPlacement: true });
- $('#huge-text-smart .south-east-alt').powerTip({ placement: 'se-alt', smartPlacement: true });
-
- // Trapped mouse following tooltip
- $('#trapped-mousefollow').powerTip({ followMouse: true });
-});
From b9713e40a7377d8e201d11bac0641e701378a4e1 Mon Sep 17 00:00:00 2001
From: Joel Steres
Date: Fri, 16 Jun 2017 03:15:01 -0700
Subject: [PATCH 7/9] Reworked positioning and collision computations.
Measurement compensation moved to CSSCoordinates.
Calculations redesigned based on study of measurement origin changes
based on CSS position, margin and border widths.
---
src/core.js | 3 +-
src/csscoordinates.js | 101 ++++++++++++++++++++++++++++++++++++
src/placementcalculator.js | 48 ++++++++---------
src/tooltipcontroller.js | 24 ++++-----
src/utility.js | 38 +++++++++-----
test/unit/csscoordinates.js | 1 +
test/unit/utility.js | 47 ++++++++++++-----
7 files changed, 198 insertions(+), 64 deletions(-)
diff --git a/src/core.js b/src/core.js
index 27bd7c78..e849efb3 100644
--- a/src/core.js
+++ b/src/core.js
@@ -61,7 +61,8 @@ var session = {
windowWidth: 0,
windowHeight: 0,
scrollTop: 0,
- scrollLeft: 0
+ scrollLeft: 0,
+ positionCompensation: { top: 0, bottom: 0, left: 0, right: 0 }
};
/**
diff --git a/src/csscoordinates.js b/src/csscoordinates.js
index 45b9ac2e..e2e33376 100644
--- a/src/csscoordinates.js
+++ b/src/csscoordinates.js
@@ -15,6 +15,26 @@
function CSSCoordinates() {
var me = this;
+ function compensated(val, comp) {
+ return val === 'auto' ? val : val - comp;
+ }
+
+ /**
+ * Return positioned element's origin with respect to the viewport home
+ * @private
+ * @param {object} el The positioned element to measure
+ */
+ function positionedParentViewportHomeOffset(el) {
+ var originX = el[0].getBoundingClientRect().left,
+ originY = el[0].getBoundingClientRect().top,
+ borderTopWidth = parseFloat(el.css('borderTopWidth')),
+ borderLeftWidth = parseFloat(el.css('borderLeftWidth'));
+ return {
+ top: originY + borderTopWidth + $document.scrollTop(),
+ left: originX + borderLeftWidth + $document.scrollLeft()
+ };
+ }
+
// initialize object properties
me.top = 'auto';
me.left = 'auto';
@@ -32,4 +52,85 @@ function CSSCoordinates() {
me[property] = Math.round(value);
}
};
+
+ me.getCompensated = function() {
+ return {
+ top: me.topCompensated,
+ left: me.leftCompensated,
+ right: me.rightCompensated,
+ bottom: me.bottomCompensated
+ };
+ };
+
+ me.fromViewportHome = function() {
+ // Coordinates with respect to viewport origin when scrolled to (0,0).
+ var coords = me.getCompensated(),
+ originOffset;
+
+ // For the cases where there is a positioned ancestor, compensate for offset of
+ // ancestor origin. Note that bounding rect includes border, if any.
+ if (isPositionNotStatic($body)) {
+ originOffset = positionedParentViewportHomeOffset($body);
+ if (coords.top !== 'auto') {
+ coords.top = coords.top + originOffset.top;
+ }
+ if (coords.left !== 'auto') {
+ coords.left = coords.left + originOffset.left;
+ }
+ if (coords.right !== 'auto') {
+ coords.right = originOffset.left + $body.width() - coords.right;
+ }
+ if (coords.bottom !== 'auto') {
+ coords.bottom = originOffset.top + $body.height() - coords.bottom;
+ }
+ } else if (isPositionNotStatic($html)) {
+ originOffset = positionedParentViewportHomeOffset($html);
+ if (coords.top !== 'auto') {
+ coords.top = coords.top + originOffset.top;
+ }
+ if (coords.left !== 'auto') {
+ coords.left = coords.left + originOffset.left;
+ }
+ if (coords.right !== 'auto') {
+ coords.right = originOffset.left + $body.width() - coords.right;
+ }
+ if (coords.bottom !== 'auto') {
+ coords.bottom = originOffset.top + $body.height() - coords.bottom;
+ }
+ } else {
+ // Change origin of right, bottom measurement to viewport (0,0) and invert sign
+ if (coords.right !== 'auto') {
+ coords.right = session.windowWidth - coords.right;
+ }
+ if (coords.bottom !== 'auto') {
+ coords.bottom = session.windowHeight - coords.bottom;
+ }
+ }
+
+ return coords;
+ };
+
+ Object.defineProperty(me, 'topCompensated', {
+ get: function() {
+ return compensated(me.top, session.positionCompensation.top);
+ }
+ });
+
+ Object.defineProperty(me, 'bottomCompensated', {
+ get: function() {
+ return compensated(me.bottom, session.positionCompensation.bottom);
+ }
+ });
+
+ Object.defineProperty(me, 'leftCompensated', {
+ get: function() {
+ return compensated(me.left, session.positionCompensation.left);
+ }
+ });
+
+ Object.defineProperty(me, 'rightCompensated', {
+ get: function() {
+ return compensated(me.right, session.positionCompensation.right);
+ }
+ });
}
diff --git a/src/placementcalculator.js b/src/placementcalculator.js
index e476d77f..1ec10ed6 100644
--- a/src/placementcalculator.js
+++ b/src/placementcalculator.js
@@ -38,52 +38,52 @@ function PlacementCalculator() {
// calculate the appropriate x and y position in the document
switch (placement) {
case 'n':
- coords.set('left', position.left - (tipWidth / 2) - session.positionCompensation.left);
- coords.set('bottom', session.windowHeight - position.top + offset - session.positionCompensation.bottom);
+ coords.set('left', position.left - (tipWidth / 2));
+ coords.set('bottom', session.windowHeight - position.top + offset);
break;
case 'e':
- coords.set('left', position.left + offset - session.positionCompensation.left);
- coords.set('top', position.top - (tipHeight / 2) - session.positionCompensation.top);
+ coords.set('left', position.left + offset);
+ coords.set('top', position.top - (tipHeight / 2));
break;
case 's':
- coords.set('left', position.left - (tipWidth / 2) - session.positionCompensation.left);
- coords.set('top', position.top + offset - session.positionCompensation.top);
+ coords.set('left', position.left - (tipWidth / 2));
+ coords.set('top', position.top + offset);
break;
case 'w':
- coords.set('top', position.top - (tipHeight / 2) - session.positionCompensation.top);
- coords.set('right', session.windowWidth - position.left + offset - session.positionCompensation.right);
+ coords.set('top', position.top - (tipHeight / 2));
+ coords.set('right', session.windowWidth - position.left + offset);
break;
case 'nw':
- coords.set('bottom', session.windowHeight - position.top + offset - session.positionCompensation.bottom);
- coords.set('right', session.windowWidth - position.left - session.positionCompensation.right - 20);
+ coords.set('bottom', session.windowHeight - position.top + offset);
+ coords.set('right', session.windowWidth - position.left - 20);
break;
case 'nw-alt':
- coords.set('left', position.left - session.positionCompensation.left);
- coords.set('bottom', session.windowHeight - position.top + offset - session.positionCompensation.bottom);
+ coords.set('left', position.left);
+ coords.set('bottom', session.windowHeight - position.top + offset);
break;
case 'ne':
- coords.set('left', position.left - session.positionCompensation.left - 20);
- coords.set('bottom', session.windowHeight - position.top + offset - session.positionCompensation.bottom);
+ coords.set('left', position.left - 20);
+ coords.set('bottom', session.windowHeight - position.top + offset);
break;
case 'ne-alt':
- coords.set('bottom', session.windowHeight - position.top + offset - session.positionCompensation.bottom);
- coords.set('right', session.windowWidth - position.left - session.positionCompensation.right);
+ coords.set('bottom', session.windowHeight - position.top + offset);
+ coords.set('right', session.windowWidth - position.left);
break;
case 'sw':
- coords.set('top', position.top + offset - session.positionCompensation.top);
- coords.set('right', session.windowWidth - position.left - session.positionCompensation.right - 20);
+ coords.set('top', position.top + offset);
+ coords.set('right', session.windowWidth - position.left - 20);
break;
case 'sw-alt':
- coords.set('left', position.left - session.positionCompensation.left);
- coords.set('top', position.top + offset - session.positionCompensation.top);
+ coords.set('left', position.left);
+ coords.set('top', position.top + offset);
break;
case 'se':
- coords.set('left', position.left - session.positionCompensation.left - 20);
- coords.set('top', position.top + offset - session.positionCompensation.top);
+ coords.set('left', position.left - 20);
+ coords.set('top', position.top + offset);
break;
case 'se-alt':
- coords.set('top', position.top + offset - session.positionCompensation.top);
- coords.set('right', session.windowWidth - position.left - session.positionCompensation.right);
+ coords.set('top', position.top + offset);
+ coords.set('right', session.windowWidth - position.left);
break;
}
diff --git a/src/tooltipcontroller.js b/src/tooltipcontroller.js
index 81d6ec86..dc4a6d4d 100644
--- a/src/tooltipcontroller.js
+++ b/src/tooltipcontroller.js
@@ -202,9 +202,9 @@ function TooltipController(options) {
// support mouse-follow and fixed position tips at the same time by
// moving the tooltip to the last cursor location after it is hidden
- coords.set('top', session.currentY + options.offset - session.positionCompensation.top);
- coords.set('left', session.currentX + options.offset - session.positionCompensation.left);
- tipElement.css(coords);
+ coords.set('top', session.currentY + options.offset);
+ coords.set('left', session.currentX + options.offset);
+ tipElement.css(coords.getCompensated());
// trigger powerTipClose event
element.trigger('powerTipClose');
@@ -231,8 +231,8 @@ function TooltipController(options) {
collisionCount;
// grab collisions
- coords.set('top', session.currentY + options.offset - session.positionCompensation.top);
- coords.set('left', session.currentX + options.offset - session.positionCompensation.left);
+ coords.set('top', session.currentY + options.offset);
+ coords.set('left', session.currentX + options.offset);
collisions = getViewportCollisions(
coords,
tipWidth,
@@ -246,21 +246,21 @@ function TooltipController(options) {
// if there is only one collision (bottom or right) then
// simply constrain the tooltip to the view port
if (collisions === Collision.right) {
- coords.set('left', session.windowWidth - tipWidth - session.positionCompensation.left);
+ coords.set('left', session.windowWidth - tipWidth);
} else if (collisions === Collision.bottom) {
- coords.set('top', session.scrollTop + session.windowHeight - tipHeight - session.positionCompensation.top);
+ coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
}
} else {
// if the tooltip has more than one collision then it is
// trapped in the corner and should be flipped to get it out
// of the users way
- coords.set('left', session.currentX - tipWidth - options.offset - session.positionCompensation.left);
- coords.set('top', session.currentY - tipHeight - options.offset - session.positionCompensation.top);
+ coords.set('left', session.currentX - tipWidth - options.offset);
+ coords.set('top', session.currentY - tipHeight - options.offset);
}
}
// position the tooltip
- tipElement.css(coords);
+ tipElement.css(coords.getCompensated());
}
}
@@ -325,7 +325,7 @@ function TooltipController(options) {
// set the tip to 0,0 to get the full expanded width
coords.set('top', 0);
coords.set('left', 0);
- tipElement.css(coords);
+ tipElement.css(coords.getCompensated());
// to support elastic tooltips we need to check for a change in the
// rendered dimensions after the tooltip has been positioned
@@ -344,7 +344,7 @@ function TooltipController(options) {
);
// place the tooltip
- tipElement.css(coords);
+ tipElement.css(coords.getCompensated());
} while (
// sanity check: limit to 5 iterations, and...
++iterationCount <= 5 &&
diff --git a/src/utility.js b/src/utility.js
index 27310078..6378b880 100644
--- a/src/utility.js
+++ b/src/utility.js
@@ -170,22 +170,23 @@ function getTooltipContent(element) {
function getViewportCollisions(coords, elementWidth, elementHeight) {
// adjusting viewport even though it might be negative because coords
// comparing with are relative to compensated position
- var viewportTop = session.scrollTop - session.positionCompensation.top,
- viewportLeft = session.scrollLeft - session.positionCompensation.left,
+ var viewportTop = session.scrollTop,
+ viewportLeft = session.scrollLeft,
viewportBottom = viewportTop + session.windowHeight,
viewportRight = viewportLeft + session.windowWidth,
+ coordsFromViewport = coords.fromViewportHome(),
collisions = Collision.none;
- if (coords.top < viewportTop || Math.abs(coords.bottom - session.windowHeight) - elementHeight < viewportTop) {
+ if (coordsFromViewport.top < viewportTop || coordsFromViewport.bottom - elementHeight < viewportTop) {
collisions |= Collision.top;
}
- if (coords.top + elementHeight > viewportBottom || Math.abs(coords.bottom - session.windowHeight) > viewportBottom) {
+ if (coordsFromViewport.top + elementHeight > viewportBottom || coordsFromViewport.bottom > viewportBottom) {
collisions |= Collision.bottom;
}
- if (coords.left < viewportLeft || coords.right + elementWidth > viewportRight) {
+ if (coordsFromViewport.left < viewportLeft || coordsFromViewport.right - elementWidth < viewportLeft) {
collisions |= Collision.left;
}
- if (coords.left + elementWidth > viewportRight || coords.right < viewportLeft) {
+ if (coordsFromViewport.left + elementWidth > viewportRight || coordsFromViewport.right > viewportRight) {
collisions |= Collision.right;
}
@@ -220,16 +221,27 @@ function isPositionNotStatic(element) {
* Get element offsets
* @private
* @param {jQuery} el Element to check
- * @param {number} windowWidth Window width in pixels.
- * @param {number} windowHeight Window height in pixels.
* @return {Object} The top, left, right, bottom offset in pixels
*/
-function getElementOffsets(el, windowWidth, windowHeight) {
+function getElementOffsets(el) {
// jquery offset returns top and left relative to document in pixels.
- var offsets = el.offset();
- // right and bottom offset relative to window width/height
- offsets.right = windowWidth - el.outerWidth() - offsets.left;
- offsets.bottom = windowHeight - el.outerHeight() - offsets.top;
+ var offsets = el.offset(),
+ borderLeftWidth = parseFloat(el.css('border-left-width')),
+ borderTopWidth = parseFloat(el.css('border-top-width')),
+ right,
+ bottom;
+
+ // right and bottom offset were relative to where screen.width,
+ // screen.height fell in document. Change reference point to inner-bottom,
+ // inner-right of element. Compensate for border which is outside
+ // measurement area. Avoid updating any measurement set to 'auto' which will
+ // result in a computed result of NaN.
+ right = session.windowWidth - el.innerWidth() - offsets.left - borderLeftWidth;
+ bottom = session.windowHeight - el.innerHeight() - offsets.top - borderTopWidth;
+ offsets.top = offsets.top + borderTopWidth;
+ offsets.left = offsets.left + borderLeftWidth;
+ offsets.right = right ? right : 0;
+ offsets.bottom = bottom ? bottom : 0;
return offsets;
}
diff --git a/test/unit/csscoordinates.js b/test/unit/csscoordinates.js
index 21c77139..c0d0b99b 100644
--- a/test/unit/csscoordinates.js
+++ b/test/unit/csscoordinates.js
@@ -14,6 +14,7 @@ $(function() {
QUnit.test('expose methods', function(assert) {
var coords = new CSSCoordinates();
assert.strictEqual(typeof coords.set, 'function', 'set method is defined');
+ assert.strictEqual(typeof coords.getCompensated, 'function', 'getCompensated method is defined');
});
QUnit.test('decimal values are rounded', function(assert) {
diff --git a/test/unit/utility.js b/test/unit/utility.js
index df18e732..fc207bec 100644
--- a/test/unit/utility.js
+++ b/test/unit/utility.js
@@ -143,29 +143,48 @@ $(function() {
assert.strictEqual(countFlags(topLeft), 2, 'exactly two collisions detected for top-left test');
}
+ function makeCoords(val) {
+ var coords = new CSSCoordinates();
+ assert.ok(coords);
+ assert.ok(coords.getCompensated);
+ if ('top' in val) {
+ coords.set('top', val.top);
+ }
+ if ('left' in val) {
+ coords.set('left', val.left);
+ }
+ if ('right' in val) {
+ coords.set('right', val.right);
+ }
+ if ('bottom' in val) {
+ coords.set('bottom', val.bottom);
+ }
+ return coords;
+ }
+
// need to make sure initTracking() has been invoked to populate the
// viewport dimensions cache
initTracking();
// top/left placement
- none = getViewportCollisions({ top: 0, left: 0 }, 200, 100);
- right = getViewportCollisions({ top: 0, left: windowWidth - 199 }, 200, 100);
- bottom = getViewportCollisions({ top: windowHeight - 99, left: 0 }, 200, 100);
- bottomRight = getViewportCollisions({ top: windowHeight - 99, left: windowWidth - 199 }, 200, 100);
- top = getViewportCollisions({ top: -1, left: 0 }, 200, 100);
- left = getViewportCollisions({ top: 0, left: -1 }, 200, 100);
- topLeft = getViewportCollisions({ top: -1, left: -1 }, 200, 100);
+ none = getViewportCollisions(makeCoords({ top: 0, left: 0 }), 200, 100);
+ right = getViewportCollisions(makeCoords({ top: 0, left: windowWidth - 199 }), 200, 100);
+ bottom = getViewportCollisions(makeCoords({ top: windowHeight - 99, left: 0 }), 200, 100);
+ bottomRight = getViewportCollisions(makeCoords({ top: windowHeight - 99, left: windowWidth - 199 }), 200, 100);
+ top = getViewportCollisions(makeCoords({ top: -1, left: 0 }), 200, 100);
+ left = getViewportCollisions(makeCoords({ top: 0, left: -1 }), 200, 100);
+ topLeft = getViewportCollisions(makeCoords({ top: -1, left: -1 }), 200, 100);
doTests();
// bottom/right placement
- none = getViewportCollisions({ bottom: 0, right: 0 }, 200, 100);
- right = getViewportCollisions({ bottom: 0, right: -1 }, 200, 100);
- bottom = getViewportCollisions({ bottom: -1, right: 0 }, 200, 100);
- bottomRight = getViewportCollisions({ bottom: -1, right: -1 }, 200, 100);
- top = getViewportCollisions({ bottom: windowHeight - 99, right: 0 }, 200, 100);
- left = getViewportCollisions({ bottom: 0, right: windowWidth - 199 }, 200, 100);
- topLeft = getViewportCollisions({ bottom: windowHeight - 99, right: windowWidth - 199 }, 200, 100);
+ none = getViewportCollisions(makeCoords({ bottom: 0, right: 0 }), 200, 100);
+ right = getViewportCollisions(makeCoords({ bottom: 0, right: -1 }), 200, 100);
+ bottom = getViewportCollisions(makeCoords({ bottom: -1, right: 0 }), 200, 100);
+ bottomRight = getViewportCollisions(makeCoords({ bottom: -1, right: -1 }), 200, 100);
+ top = getViewportCollisions(makeCoords({ bottom: windowHeight - 99, right: 0 }), 200, 100);
+ left = getViewportCollisions(makeCoords({ bottom: 0, right: windowWidth - 199 }), 200, 100);
+ topLeft = getViewportCollisions(makeCoords({ bottom: windowHeight - 99, right: windowWidth - 199 }), 200, 100);
doTests();
});
From ac7faa4c0dabe097b0206d4486a61369b435c7f7 Mon Sep 17 00:00:00 2001
From: Joel Steres
Date: Wed, 10 Jan 2018 05:01:07 -0800
Subject: [PATCH 8/9] Add docstring to compensation function.
---
src/csscoordinates.js | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/csscoordinates.js b/src/csscoordinates.js
index e2e33376..4aeff590 100644
--- a/src/csscoordinates.js
+++ b/src/csscoordinates.js
@@ -15,8 +15,15 @@
function CSSCoordinates() {
var me = this;
- function compensated(val, comp) {
- return val === 'auto' ? val : val - comp;
+ /**
+ * Return value the compensated value allowing for the special value of 'auto'.
+ * @private
+ * @param {number} value The value to be compensated.
+ * @param {number} comp The amount by which the value should be adjusted.
+ * @returns {number} The value less comp unless 'auto'
+ */
+ function compensated(value, comp) {
+ return value === 'auto' ? value : value - comp;
}
/**
From 9ff077b411b74330ee0be3dd1b23e6347bceeb36 Mon Sep 17 00:00:00 2001
From: Joel Steres
Date: Wed, 10 Jan 2018 05:05:27 -0800
Subject: [PATCH 9/9] Add docstring returns description to
positionedParentViewportHomeOffset
---
src/csscoordinates.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/csscoordinates.js b/src/csscoordinates.js
index 4aeff590..345d7611 100644
--- a/src/csscoordinates.js
+++ b/src/csscoordinates.js
@@ -30,6 +30,7 @@ function CSSCoordinates() {
* Return positioned element's origin with respect to the viewport home
* @private
* @param {object} el The positioned element to measure
+ * @returns {object} The top and left coordinates of the element relative to the viewport.
*/
function positionedParentViewportHomeOffset(el) {
var originX = el[0].getBoundingClientRect().left,