jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/385309 )

Change subject: MenuSelectWidget, PopupWidget: Automatically change popup 
direction if there is no space
......................................................................


MenuSelectWidget, PopupWidget: Automatically change popup direction if there is 
no space

The general idea of "flip direction if the default direction causes
clipping" is lifted from the implementation in PopupToolGroup, but the
details are different.

Note that we do not flip the direction if the clipping starts after
the widget is already visible (e.g. when the user scrolls), that seems
like it would be annoying.

PopupWidget gains a new config option 'autoFlip' (default 'true'),
which allows this behavior to be disabled. It's always enabled for
MenuSelectWidget.

Bug: T158934
Change-Id: Ifa26336b501c8aac0aebc8f9fe1120adc4b6c8bf
---
M src/themes/apex/widgets.less
M src/themes/wikimediaui/widgets.less
M src/widgets/MenuSelectWidget.js
M src/widgets/PopupWidget.js
4 files changed, 120 insertions(+), 9 deletions(-)

Approvals:
  jenkins-bot: Verified
  Jforrester: Looks good to me, approved



diff --git a/src/themes/apex/widgets.less b/src/themes/apex/widgets.less
index 61b8519..ba5ea59 100644
--- a/src/themes/apex/widgets.less
+++ b/src/themes/apex/widgets.less
@@ -580,6 +580,7 @@
 .theme-oo-ui-menuSelectWidget () {
        background-color: @background-color-main;
        margin-top: -1px;
+       margin-bottom: -1px;
        border: 1px solid #ccc;
        border-radius: 0 0 @border-radius-base @border-radius-base;
        box-shadow: 0 0.15em 1em 0 rgba( 0, 0, 0, 0.2 );
diff --git a/src/themes/wikimediaui/widgets.less 
b/src/themes/wikimediaui/widgets.less
index 5023370..d8df4fc 100644
--- a/src/themes/wikimediaui/widgets.less
+++ b/src/themes/wikimediaui/widgets.less
@@ -279,6 +279,7 @@
 
        &-popup {
                margin-top: -@border-width-base;
+               margin-bottom: -@border-width-base;
 
                > .oo-ui-popupWidget-popup {
                        border: 0;
@@ -789,6 +790,7 @@
 .theme-oo-ui-menuSelectWidget () {
        background-color: @background-color-base;
        margin-top: -1px;
+       margin-bottom: -1px;
        border: @border-menu;
        border-radius: 0 0 @border-radius-base @border-radius-base;
        box-shadow: @box-shadow-menu;
diff --git a/src/widgets/MenuSelectWidget.js b/src/widgets/MenuSelectWidget.js
index da3f9f5..a11d3a2 100644
--- a/src/widgets/MenuSelectWidget.js
+++ b/src/widgets/MenuSelectWidget.js
@@ -325,7 +325,7 @@
  * @inheritdoc
  */
 OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) {
-       var change;
+       var change, belowHeight, aboveHeight;
 
        visible = ( visible === undefined ? !this.visible : !!visible ) && 
!!this.items.length;
        change = visible !== this.isVisible();
@@ -335,8 +335,15 @@
                this.warnedUnattached = true;
        }
 
-       if ( change && visible && ( this.width || this.$floatableContainer ) ) {
-               this.setIdealSize( this.width || 
this.$floatableContainer.width() );
+       if ( change ) {
+               if ( visible && ( this.width || this.$floatableContainer ) ) {
+                       this.setIdealSize( this.width || 
this.$floatableContainer.width() );
+               }
+               if ( visible ) {
+                       // Reset position before showing the popup again. It's 
possible we no longer need to flip
+                       // (e.g. if the user scrolled).
+                       this.setVerticalPosition( 'below' );
+               }
        }
 
        // Parent method
@@ -350,6 +357,22 @@
                        this.togglePositioning( !!this.$floatableContainer );
                        this.toggleClipping( true );
 
+                       if ( this.isClippedVertically() ) {
+                               // If opening the menu downwards causes it to 
be clipped, flip it to open upwards instead
+                               belowHeight = this.$element.height();
+                               this.setVerticalPosition( 'above' );
+                               if ( this.isClippedVertically() ) {
+                                       // If opening upwards also causes it to 
be clipped, flip it to open in whichever direction
+                                       // we have more space
+                                       aboveHeight = this.$element.height();
+                                       if ( aboveHeight < belowHeight ) {
+                                               this.setVerticalPosition( 
'below' );
+                                       }
+                               }
+                       }
+                       // Note that we do not flip the menu's opening 
direction if the clipping changes
+                       // later (e.g. after the user scrolls), that seems like 
it would be annoying
+
                        this.$focusOwner.attr( 'aria-expanded', 'true' );
 
                        if ( this.getSelectedItem() ) {
diff --git a/src/widgets/PopupWidget.js b/src/widgets/PopupWidget.js
index ce1ef5a..322187a 100644
--- a/src/widgets/PopupWidget.js
+++ b/src/widgets/PopupWidget.js
@@ -50,6 +50,9 @@
  *            of the popup with the center of $floatableContainer.
  * 'force-left': Alias for 'forwards' in LTR and 'backwards' in RTL
  * 'force-right': Alias for 'backwards' in RTL and 'forwards' in LTR
+ * @cfg {boolean} [autoFlip=true] Whether to automatically switch the popup's 
position between
+ *  'above' and 'below', or between 'before' and 'after', if there is not 
enough space in the
+ *  desired direction to display the popup without clipping
  * @cfg {jQuery} [$container] Constrain the popup to the boundaries of the 
specified container.
  *  See the [OOjs UI docs on MediaWiki][3] for an example.
  *  [3]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Popups#containerExample
@@ -102,6 +105,7 @@
        this.toggleAnchor( config.anchor === undefined || config.anchor );
        this.setAlignment( config.align || 'center' );
        this.setPosition( config.position || 'below' );
+       this.setAutoFlip( config.autoFlip === undefined || config.autoFlip );
        this.$body.addClass( 'oo-ui-popupWidget-body' );
        this.$anchor.addClass( 'oo-ui-popupWidget-anchor' );
        this.$popup
@@ -259,6 +263,7 @@
                this.anchored = show;
        }
 };
+
 /**
  * Change which edge the anchor appears on.
  *
@@ -299,7 +304,7 @@
  * @inheritdoc
  */
 OO.ui.PopupWidget.prototype.toggle = function ( show ) {
-       var change;
+       var change, normalHeight, oppositeHeight, normalWidth, oppositeWidth;
        show = show === undefined ? !this.isVisible() : !!show;
 
        change = show !== this.isVisible();
@@ -311,6 +316,12 @@
        if ( show && !this.$floatableContainer && this.isElementAttached() ) {
                // Fall back to the parent node if the floatableContainer is 
not set
                this.setFloatableContainer( this.$element.parent() );
+       }
+
+       if ( change && show && this.autoFlip ) {
+               // Reset auto-flipping before showing the popup again. It's 
possible we no longer need to flip
+               // (e.g. if the user scrolled).
+               this.isAutoFlipped = false;
        }
 
        // Parent method
@@ -326,6 +337,54 @@
                        }
                        this.updateDimensions();
                        this.toggleClipping( true );
+
+                       if ( this.autoFlip ) {
+                               if ( this.popupPosition === 'above' || 
this.popupPosition === 'below' ) {
+                                       if ( this.isClippedVertically() ) {
+                                               // If opening the popup in the 
normal direction causes it to be clipped, open
+                                               // in the opposite one instead
+                                               normalHeight = 
this.$element.height();
+                                               this.isAutoFlipped = 
!this.isAutoFlipped;
+                                               this.position();
+                                               if ( this.isClippedVertically() 
) {
+                                                       // If that also causes 
it to be clipped, open in whichever direction
+                                                       // we have more space
+                                                       oppositeHeight = 
this.$element.height();
+                                                       if ( oppositeHeight < 
normalHeight ) {
+                                                               
this.isAutoFlipped = !this.isAutoFlipped;
+                                                               this.position();
+                                                       }
+                                               }
+                                       }
+                               }
+                               if ( this.popupPosition === 'before' || 
this.popupPosition === 'after' ) {
+                                       if ( this.isClippedHorizontally() ) {
+                                               // If opening the popup in the 
normal direction causes it to be clipped, open
+                                               // in the opposite one instead
+                                               normalWidth = 
this.$element.width();
+                                               this.isAutoFlipped = 
!this.isAutoFlipped;
+                                               // Due to T180173 horizontally 
clipped PopupWidgets have messed up dimensions,
+                                               // which causes positioning to 
be off. Toggle clipping back and fort to work around.
+                                               this.toggleClipping( false );
+                                               this.position();
+                                               this.toggleClipping( true );
+                                               if ( 
this.isClippedHorizontally() ) {
+                                                       // If that also causes 
it to be clipped, open in whichever direction
+                                                       // we have more space
+                                                       oppositeWidth = 
this.$element.width();
+                                                       if ( oppositeWidth < 
normalWidth ) {
+                                                               
this.isAutoFlipped = !this.isAutoFlipped;
+                                                               // Due to 
T180173 horizontally clipped PopupWidgets have messed up dimensions,
+                                                               // which causes 
positioning to be off. Toggle clipping back and fort to work around.
+                                                               
this.toggleClipping( false );
+                                                               this.position();
+                                                               
this.toggleClipping( true );
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+
                        this.emit( 'ready' );
                } else {
                        this.toggleClipping( false );
@@ -395,9 +454,15 @@
 OO.ui.PopupWidget.prototype.computePosition = function () {
        var direction, align, vertical, start, end, near, far, sizeProp, 
popupSize, anchorSize, anchorPos,
                anchorOffset, anchorMargin, parentPosition, positionProp, 
positionAdjustment, floatablePos,
-               offsetParentPos, containerPos,
+               offsetParentPos, containerPos, popupPosition,
                popupPos = {},
                anchorCss = { left: '', right: '', top: '', bottom: '' },
+               popupPositionOppositeMap = {
+                       above: 'below',
+                       below: 'above',
+                       before: 'after',
+                       after: 'before'
+               },
                alignMap = {
                        ltr: {
                                'force-left': 'backwards',
@@ -439,8 +504,13 @@
        } );
 
        align = alignMap[ direction ][ this.align ] || this.align;
+       popupPosition = this.popupPosition;
+       if ( this.isAutoFlipped ) {
+               popupPosition = popupPositionOppositeMap[ popupPosition ];
+       }
+
        // If the popup is positioned before or after, then the anchor 
positioning is vertical, otherwise horizontal
-       vertical = this.popupPosition === 'before' || this.popupPosition === 
'after';
+       vertical = popupPosition === 'before' || popupPosition === 'after';
        start = vertical ? 'top' : ( direction === 'rtl' ? 'right' : 'left' );
        end = vertical ? 'bottom' : ( direction === 'rtl' ? 'left' : 'right' );
        near = vertical ? 'top' : 'left';
@@ -448,9 +518,9 @@
        sizeProp = vertical ? 'Height' : 'Width';
        popupSize = vertical ? ( this.height || this.$popup.height() ) : 
this.width;
 
-       this.setAnchorEdge( anchorEdgeMap[ this.popupPosition ] );
-       this.horizontalPosition = vertical ? this.popupPosition : hPosMap[ 
align ];
-       this.verticalPosition = vertical ? vPosMap[ align ] : 
this.popupPosition;
+       this.setAnchorEdge( anchorEdgeMap[ popupPosition ] );
+       this.horizontalPosition = vertical ? popupPosition : hPosMap[ align ];
+       this.verticalPosition = vertical ? vPosMap[ align ] : popupPosition;
 
        // Parent method
        parentPosition = 
OO.ui.mixin.FloatableElement.prototype.computePosition.call( this );
@@ -582,6 +652,21 @@
 };
 
 /**
+ * Set popup auto-flipping.
+ *
+ * @param {boolean} autoFlip Whether to automatically switch the popup's 
position between
+ *  'above' and 'below', or between 'before' and 'after', if there is not 
enough space in the
+ *  desired direction to display the popup without clipping
+ */
+OO.ui.PopupWidget.prototype.setAutoFlip = function ( autoFlip ) {
+       autoFlip = !!autoFlip;
+
+       if ( this.autoFlip !== autoFlip ) {
+               this.autoFlip = autoFlip;
+       }
+};
+
+/**
  * Get an ID of the body element, this can be used as the
  * `aria-describedby` attribute for an input field.
  *

-- 
To view, visit https://gerrit.wikimedia.org/r/385309
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ifa26336b501c8aac0aebc8f9fe1120adc4b6c8bf
Gerrit-PatchSet: 4
Gerrit-Project: oojs/ui
Gerrit-Branch: master
Gerrit-Owner: Bartosz DziewoƄski <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: Jforrester <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to