]> git.ipfire.org Git - thirdparty/foundation/foundation-sites.git/commitdiff
Update box and dropdown to allow finding minimum possible overflow
authorKevin Ball <kmball11@gmail.com>
Wed, 24 May 2017 04:35:27 +0000 (21:35 -0700)
committerKevin Ball <kmball11@gmail.com>
Wed, 24 May 2017 04:35:27 +0000 (21:35 -0700)
js/foundation.dropdown.js
js/foundation.util.box.js
test/visual/dropdown/overflow.html

index 5b078b1e0eb3bd31ad4261729fc0816bbe2c7a72..ffe7492e0d0847f94dc8d8b479ebfc899d3421ab 100644 (file)
@@ -29,6 +29,15 @@ const ALIGNMENTS = {
   'bottom': HORIZONTAL_ALIGNMENTS
 }
 
+function nextItem(item, array) {
+  var currentIdx = array.indexOf(item);
+  if(currentIdx === array.length - 1) {
+    return array[0];
+  } else {
+    return array[currentIdx + 1];
+  }
+}
+
 class Dropdown extends Plugin {
   /**
    * Creates a new instance of a dropdown.
@@ -85,13 +94,6 @@ class Dropdown extends Plugin {
   }
 
   _setupPositionAndAlignment() {
-    if(this.options.position === 'left' || this.options.position === 'right') {
-      this.isHorizontallyPositioned = true;
-    }
-    if(this.options.position === 'top' || this.options.position === 'bottom') {
-      this.isVerticallyPositioned = true;
-    }
-
     this.position  = this.options.position === 'auto' ? this._getDefaultPosition() : this.options.position;
     this.alignment = this.options.alignment === 'auto' ? this._getDefaultAlignment() : this.options.alignment;
   }
@@ -146,6 +148,13 @@ class Dropdown extends Plugin {
    * @private
    */
   _reposition() {
+    if(this._alignmentsExhausted(this.position)) {
+      this.position = nextItem(this.position, POSITIONS);
+      this.alignment = ALIGNMENTS[this.position][0];
+      console.log('alignments exhausted, repositioned to ', this.position, this.alignment);
+    } else {
+      this._realign();
+    }
   }
 
 
@@ -157,13 +166,8 @@ class Dropdown extends Plugin {
    */
   _realign() {
     this._addTriedPosition(this.position, this.alignment)
-    var alignments = ALIGNMENTS[this.position]
-    var currentIdx = alignments.indexOf(this.alignment);
-    if(currentIdx === alignments.length - 1) {
-      this.alignment = alignments[0];
-    } else {
-      this.alignment = alignments[currentIdx + 1];
-    }
+    this.alignment = nextItem(this.alignment, ALIGNMENTS[this.position])
+    console.log('realigning to', this.position, this.alignment);
   }
 
   _addTriedPosition(position, alignment) {
@@ -172,10 +176,15 @@ class Dropdown extends Plugin {
   }
 
   _positionsExhausted() {
-    if(this.options.position === 'auto') {
-    } else {
-      return this.triedPositions[this.position] && this.triedPositions[this.position].length == ALIGNMENTS[this.position].length;
+    var isExhausted = true;
+    for(var i = 0; i < POSITIONS.length; i++) {
+      isExhausted = isExhausted && this._alignmentsExhausted(POSITIONS[i]);
     }
+    return isExhausted;
+  }
+
+  _alignmentsExhausted(position) {
+    return this.triedPositions[position] && this.triedPositions[position].length == ALIGNMENTS[position].length;
   }
 
   /**
@@ -193,15 +202,30 @@ class Dropdown extends Plugin {
     this.$element.offset(Box.GetExplicitOffsets(this.$element, this.$anchor, this.position, this.alignment, this.options.vOffset, this.options.hOffset));
 
     if(!this.options.allowOverlap) {
-      while(!Box.ImNotTouchingYou(this.$element, this.$parent, this.isVerticallyPositioned, this.isHorizontallyPositioned) && !this._positionsExhausted()){
-        if(this.options.position === 'auto') {
-          this._reposition();
-        } else {
-          console.log('realigning');
-          this._realign();
+      var overlaps = {};
+      var minOverlap = 100000000;
+      // default coordinates to how we start, in case we can't figure out better
+      var minCoordinates = {position: this.position, alignment: this.alignment};
+      while(!this._positionsExhausted()) {
+        let overlap = Box.OverlapArea(this.$element, this.$parent);
+        if(overlap === 0) {
+          return;
         }
-        this._setPosition();
+
+        if(overlap < minOverlap) {
+          minOverlap = overlap;
+          minCoordinates = {position: this.position, alignment: this.alignment};
+        }
+
+        this._reposition();
+
+        this.$element.offset(Box.GetExplicitOffsets(this.$element, this.$anchor, this.position, this.alignment, this.options.vOffset, this.options.hOffset));
       }
+      // If we get through the entire loop, there was no non-overlapping
+      // position available. Pick the version with least overlap.
+      this.position = minCoordinates.position;
+      this.alignment = minCoordinates.alignment;
+      this.$element.offset(Box.GetExplicitOffsets(this.$element, this.$anchor, this.position, this.alignment, this.options.vOffset, this.options.hOffset));
     }
   }
 
index a5c6d082fb4d540a3621079458a27b415e83cb59..f00bcc74bcb56003e0ef5189ab3df28ac709440c 100644 (file)
@@ -5,6 +5,7 @@ import { rtl as Rtl } from "./foundation.util.core";
 
 var Box = {
   ImNotTouchingYou: ImNotTouchingYou,
+  OverlapArea: OverlapArea,
   GetDimensions: GetDimensions,
   GetOffsets: GetOffsets,
   GetExplicitOffsets: GetExplicitOffsets
@@ -21,36 +22,42 @@ var Box = {
  * @returns {Boolean} - true if collision free, false if a collision in any direction.
  */
 function ImNotTouchingYou(element, parent, lrOnly, tbOnly) {
-  var eleDims = GetDimensions(element),
-      top, bottom, left, right;
+  return OverlapArea(element, parent, lrOnly, tbOnly) === 0;
+};
 
+function OverlapArea(element, parent, lrOnly, tbOnly) {
+  var eleDims = GetDimensions(element),
+      topOver, bottomOver, leftOver, rightOver;
   if (parent) {
     var parDims = GetDimensions(parent);
 
-    bottom = (eleDims.offset.top + eleDims.height <= parDims.height + parDims.offset.top);
-    top    = (eleDims.offset.top >= parDims.offset.top);
-    left   = (eleDims.offset.left >= parDims.offset.left);
-    right  = (eleDims.offset.left + eleDims.width <= parDims.width + parDims.offset.left);
+    bottomOver = (parDims.height + parDims.offset.top) - (eleDims.offset.top + eleDims.height);
+    topOver    = eleDims.offset.top - parDims.offset.top;
+    leftOver   = eleDims.offset.left - parDims.offset.left;
+    rightOver  = (parDims.width + parDims.offset.left) - (eleDims.offset.left + eleDims.width);
   }
   else {
-    bottom = (eleDims.offset.top + eleDims.height <= eleDims.windowDims.height + eleDims.windowDims.offset.top);
-    top    = (eleDims.offset.top >= eleDims.windowDims.offset.top);
-    left   = (eleDims.offset.left >= eleDims.windowDims.offset.left);
-    right  = (eleDims.offset.left + eleDims.width <= eleDims.windowDims.width);
+    bottomOver = (eleDims.windowDims.height + eleDims.windowDims.offset.top) - (eleDims.offset.top + eleDims.height);
+    topOver    = eleDims.offset.top - eleDims.windowDims.offset.top;
+    leftOver   = eleDims.offset.left - eleDims.windowDims.offset.left;
+    rightOver  = eleDims.windowDims.width - (eleDims.offset.left + eleDims.width);
   }
 
-  var allDirs = [bottom, top, left, right];
+  bottomOver = Math.min(bottomOver, 0);
+  topOver    = Math.min(topOver, 0);
+  leftOver   = Math.min(leftOver, 0);
+  rightOver  = Math.min(rightOver, 0);
 
   if (lrOnly) {
-    return left === right === true;
+    return leftOver + rightOver;
   }
-
   if (tbOnly) {
-    return top === bottom === true;
+    return topOver + bottomOver;
   }
 
-  return allDirs.indexOf(false) === -1;
-};
+  // use sum of squares b/c we care about overlap area.
+  return Math.sqrt((topOver * topOver) + (bottomOver * bottomOver) + (leftOver * leftOver) + (rightOver * rightOver));
+}
 
 /**
  * Uses native methods to return an object of dimension values.
index ba26f459805d2515c9cdf403f2bce42d33f549bc..7099eb8c14b59f56c6bdfbb32aafce754286c0ec 100644 (file)
@@ -14,7 +14,7 @@
       <p>These dropdowns test various overflow situations.</p>
 
       <h4>Right and Left Overflow</h4>
-      <div class="row small-up-1 medium-up-2" style="background-color:#ddd;">
+      <div class="row small-up-1 medium-up-2" style="background-color:#ddd; padding: 20px 0;">
         <div class="column">
           <p>Bottom Right Default Behavior (no overflow)</p>
           <button class="button" type="button" data-toggle="example-dropdown-bottom-right">Toggle Dropdown</button>