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