Esanders has uploaded a new change for review. https://gerrit.wikimedia.org/r/267446
Change subject: DesktopContext: Float the context when it goes beyond the viewport ...................................................................... DesktopContext: Float the context when it goes beyond the viewport This is especially important when the focusable node is bigger than the viewport (e.g. infoboxes). This will also allow us to add a context for tables, which are often bigger than the viewport. Bug: T51922 Change-Id: I984b98bea5930bdd4e09705be039c357c7f664ca --- M src/ui/contexts/ve.ui.DesktopContext.js M src/ui/contexts/ve.ui.LinearContext.js M src/ui/styles/ve.ui.DesktopContext.css M src/ui/ve.ui.Surface.js 4 files changed, 130 insertions(+), 32 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/VisualEditor/VisualEditor refs/changes/46/267446/1 diff --git a/src/ui/contexts/ve.ui.DesktopContext.js b/src/ui/contexts/ve.ui.DesktopContext.js index 973b01c..cbbb8cc 100644 --- a/src/ui/contexts/ve.ui.DesktopContext.js +++ b/src/ui/contexts/ve.ui.DesktopContext.js @@ -20,8 +20,13 @@ // Properties this.popup = new OO.ui.PopupWidget( { $container: this.surface.$element } ); + this.position = null; + this.embeddable = null; + this.boundingRect = null; this.transitioning = null; + this.dimensions = null; this.suppressed = false; + this.onWindowScrollDebounced = ve.debounce( this.onWindowScroll.bind( this ) ); this.onWindowResizeHandler = this.onPosition.bind( this ); this.$window = $( this.getElementWindow() ); @@ -37,9 +42,12 @@ select: 'onModelSelect' } ); this.inspectors.connect( this, { - resize: 'setPopupSize' + resize: 'onInspectorResize' } ); - this.$window.on( 'resize', this.onWindowResizeHandler ); + this.$window.on( { + resize: this.onWindowResizeHandler, + scroll: this.onWindowScrollDebounced + } ); // Initialization this.$element @@ -136,7 +144,14 @@ ve.ui.DesktopContext.prototype.onInspectorOpening = function () { ve.ui.DesktopContext.super.prototype.onInspectorOpening.apply( this, arguments ); // Resize the popup before opening so the body height of the window is measured correctly - this.setPopupSize(); + this.setPopupSizeAndPosition(); +}; + +/** + * Handle inspector resize events + */ +ve.ui.DesktopContext.prototype.onInspectorResize = function () { + this.updateDimensionsDebounced(); }; /** @@ -182,8 +197,10 @@ * @inheritdoc */ ve.ui.DesktopContext.prototype.updateDimensions = function () { - var startAndEndRects, position, embeddable, middle, boundingRect, rtl, - surface, startingSelection, currentSelection, isTableSelection, focusedNode; + var startAndEndRects, position, middle, boundingRect, rtl, + surface, startingSelection, currentSelection, isTableSelection, focusedNode, + $container = this.inspector ? this.inspector.$frame : this.$group, + embeddable = false; // Parent method ve.ui.DesktopContext.super.prototype.updateDimensions.call( this ); @@ -260,14 +277,29 @@ } if ( position ) { - this.$element.css( { left: position.x, top: position.y } ); + this.position = position; } + if ( boundingRect ) { + this.boundingRect = boundingRect; + } + this.embeddable = embeddable; + this.dimensions = { + width: $container.outerWidth( true ), + height: $container.outerHeight( true ) + }; - // HACK: setPopupSize() has to be called at the end because it reads this.popup.align, - // which we set directly in the code above - this.setPopupSize(); + this.setPopupSizeAndPosition(); return this; +}; + +/** + * Handle window scroll events + * + * @param {jQuery.Event} e Scroll event + */ +ve.ui.DesktopContext.prototype.onWindowScroll = function () { + this.setPopupSizeAndPosition( true ); }; /** @@ -289,35 +321,91 @@ }; /** - * Resize the popup to match the size of its contents (menu or inspector). + * Apply the popup's size and position, within the bounds of the viewport + * + * @param {boolean} [repositionOnly] Reposition the popup only */ -ve.ui.DesktopContext.prototype.setPopupSize = function () { - var surface = this.surface, - viewport = surface.getViewportDimensions(), - $container = this.inspector ? this.inspector.$frame : this.$group; +ve.ui.DesktopContext.prototype.setPopupSizeAndPosition = function ( repositionOnly ) { + var floating, viewport, + margin = 10, + minimumVisibleHeight = 100, + surface = this.surface; - if ( !viewport ) { + if ( !this.isVisible() ) { + return; + } + + viewport = surface.getViewportDimensions(); + + if ( !viewport || !this.dimensions ) { // viewport can be null if the surface is not attached return; } - // PopupWidget normally is clippable, suppress that to be able to resize and scroll it into view. - // Needs to be repeated before every call, as it resets itself when the popup is shown or hidden. - this.popup.toggleClipping( false ); + if ( this.position ) { + floating = + ( !this.embeddable && this.position.y + this.dimensions.height > viewport.bottom - margin ) || + ( this.embeddable && this.position.y < viewport.top + margin ); + this.$element.toggleClass( 've-ui-desktopContext-floating', floating ); + this.popup.toggleAnchor( !floating && !this.embeddable ); - // We want to stop the popup from possibly being bigger than the viewport, - // as that can result in situations where it's impossible to reach parts - // of the popup. Limiting it to the window height would ignore toolbars - // and the find-replace dialog and suchlike. Therefore we set its max - // height to the surface's estimation of the actual viewport available to - // it. It's okay if the inspector goes off the edge of the viewport, so - // long as it's possible to scroll and get it all in view. - this.popup.setSize( - $container.outerWidth( true ), - Math.min( $container.outerHeight( true ), viewport.height ) - ); + if ( floating ) { + if ( this.embeddable ) { + if ( this.boundingRect.bottom - viewport.top - minimumVisibleHeight < this.dimensions.height + margin ) { + this.$element.toggleClass( 've-ui-desktopContext-floating', false ); + this.$element.css( { + left: this.position.x, + top: this.position.y + this.boundingRect.height - this.dimensions.height - minimumVisibleHeight, + bottom: '' + } ); + } else { + this.$element.css( { + left: this.position.x + viewport.left, + top: this.surface.toolbarHeight + margin, + bottom: '' + } ); + } + } else { + if ( viewport.bottom - this.boundingRect.top - minimumVisibleHeight < this.dimensions.height + margin ) { + this.$element.toggleClass( 've-ui-desktopContext-floating', false ); + this.$element.css( { + left: this.position.x, + top: this.boundingRect.top + minimumVisibleHeight, + bottom: '' + } ); + } else { + this.$element.css( { + left: this.position.x + viewport.left, + top: '', + bottom: this.dimensions.height + margin + } ); + } + } + } else { + this.$element.css( { + left: this.position.x, + top: this.position.y, + bottom: '' + } ); + } + } - this.popup.scrollElementIntoView(); + if ( !repositionOnly ) { + // PopupWidget normally is clippable, suppress that to be able to resize and scroll it into view. + // Needs to be repeated before every call, as it resets itself when the popup is shown or hidden. + this.popup.toggleClipping( false ); + + // We want to stop the popup from possibly being bigger than the viewport, + // as that can result in situations where it's impossible to reach parts + // of the popup. Limiting it to the window height would ignore toolbars + // and the find-replace dialog and suchlike. Therefore we set its max + // height to the surface's estimation of the actual viewport available to + // it. It's okay if the inspector goes off the edge of the viewport, so + // long as it's possible to scroll and get it all in view. + this.popup.setSize( this.dimensions.width, Math.min( this.dimensions.height, viewport.height ) ); + + this.popup.scrollElementIntoView(); + } }; /** @@ -327,7 +415,11 @@ // Disconnect this.surface.getView().disconnect( this ); this.surface.getModel().disconnect( this ); - this.$window.off( 'resize', this.onWindowResizeHandler ); + this.inspectors.disconnect( this ); + this.$window.off( { + resize: this.onWindowResizeHandler, + scroll: this.onWindowScrollDebounced + } ); // Parent method return ve.ui.DesktopContext.super.prototype.destroy.call( this ); diff --git a/src/ui/contexts/ve.ui.LinearContext.js b/src/ui/contexts/ve.ui.LinearContext.js index f476b5b..115ab13 100644 --- a/src/ui/contexts/ve.ui.LinearContext.js +++ b/src/ui/contexts/ve.ui.LinearContext.js @@ -310,6 +310,7 @@ */ ve.ui.LinearContext.prototype.destroy = function () { // Disconnect events + this.surface.getModel().disconnect( this ); this.inspectors.disconnect( this ); // Destroy inspectors WindowManager diff --git a/src/ui/styles/ve.ui.DesktopContext.css b/src/ui/styles/ve.ui.DesktopContext.css index 3df5ee1..bf5b657 100644 --- a/src/ui/styles/ve.ui.DesktopContext.css +++ b/src/ui/styles/ve.ui.DesktopContext.css @@ -38,6 +38,10 @@ background-image: none; } +.ve-ui-desktopContext-floating { + position: fixed; +} + .ve-ui-desktopContext > .oo-ui-popupWidget:not( .oo-ui-popupWidget-anchored ) .oo-ui-popupWidget-popup { margin-top: 0.25em; } diff --git a/src/ui/ve.ui.Surface.js b/src/ui/ve.ui.Surface.js index e0d8972..f2602e5 100644 --- a/src/ui/ve.ui.Surface.js +++ b/src/ui/ve.ui.Surface.js @@ -249,7 +249,7 @@ /** * Get vertical measurements of the visible area of the surface viewport * - * @return {Object|null} Object with top, bottom, and height properties. Null if the surface is not attached. + * @return {Object|null} Object with top, left, bottom, and height properties. Null if the surface is not attached. */ ve.ui.Surface.prototype.getViewportDimensions = function () { var top, bottom, @@ -264,6 +264,7 @@ return { top: top, + left: rect.left, bottom: bottom, height: bottom - top }; -- To view, visit https://gerrit.wikimedia.org/r/267446 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I984b98bea5930bdd4e09705be039c357c7f664ca Gerrit-PatchSet: 1 Gerrit-Project: VisualEditor/VisualEditor Gerrit-Branch: master Gerrit-Owner: Esanders <esand...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits