diff --git a/css/_jitsi_popover.scss b/css/_jitsi_popover.scss
index 2397b295f..6f1250332 100644
--- a/css/_jitsi_popover.scss
+++ b/css/_jitsi_popover.scss
@@ -16,11 +16,31 @@
white-space: normal;
margin-top: -$popoverMenuPadding;
- &__menu-padding {
- height: $popoverMenuPadding;
- width: 100px;
+
+ &__menu-padding,
+ &__menu-padding-top {
position: absolute;
+ width: 100px;
+ }
+
+ /**
+ * Invisible padding is added to the bottom of the popover to extend its
+ * height so it does not close when moving the mouse from the trigger
+ * element towards the popover itself.
+ */
+ &__menu-padding {
bottom: -$popoverMenuPadding;
+ height: $popoverMenuPadding;
+ }
+
+ /**
+ * Invisible padding is added to the top of the popover to extend its height
+ * so it does not close automatically when its height is shrunk from showing
+ * less video statistics.
+ */
+ &__menu-padding-top {
+ height: 20px;
+ top: -20px;
}
&__showmore {
diff --git a/modules/UI/util/JitsiPopover.js b/modules/UI/util/JitsiPopover.js
index 3717b8f85..ad9bd2768 100644
--- a/modules/UI/util/JitsiPopover.js
+++ b/modules/UI/util/JitsiPopover.js
@@ -37,16 +37,15 @@ const positionConfigurations = {
$('.jitsipopover').css({
display: 'table',
- left: position.left,
- top: position.top
+ left: element.left,
+ top: element.top
});
// Move additional padding to the right edge of the popover and
// allow css to take care of width. The padding is used to maintain
// a hover state between the target and the popover.
- $('.jitsipopover > .jitsipopover__menu-padding').css({
- left: element.width
- });
+ $('.jitsipopover > .jitsipopover__menu-padding')
+ .css({ left: element.width });
// Find the distance from the top of the popover to the center of
// the target and use that value to position the arrow to point to
@@ -67,13 +66,21 @@ const positionConfigurations = {
at: "top",
collision: "fit",
using: function setPositionTop(position, elements) {
- var calcLeft = elements.target.left - elements.element.left +
- elements.target.width/2;
- $(".jitsipopover").css(
- {top: position.top, left: position.left, display: "table"});
- $(".jitsipopover > .arrow").css({left: calcLeft});
- $(".jitsipopover > .jitsipopover__menu-padding").css(
- {left: calcLeft - 50});
+ const { element, target } = elements;
+ const calcLeft = target.left - element.left + target.width / 2;
+ const paddingLeftPosition = calcLeft - 50;
+ const $jistiPopover = $('.jitsipopover');
+
+ $jistiPopover.css({
+ display: 'table',
+ left: element.left,
+ top: element.top
+ });
+ $jistiPopover.find('.arrow').css({ left: calcLeft });
+ $jistiPopover.find('.jitsipopover__menu-padding')
+ .css({ left: paddingLeftPosition });
+ $jistiPopover.find('.jitsipopover__menu-padding-top')
+ .css({ left: paddingLeftPosition });
}
}
};
@@ -93,8 +100,6 @@ export default (function () {
* Constructs new JitsiPopover and attaches it to the element
* @param element jquery selector
* @param options the options for the popover.
- * - {Function} onBeforePosition - function executed just before
- * positioning the popover. Useful for translation.
* @constructor
*/
function JitsiPopover(element, options)
@@ -132,6 +137,7 @@ export default (function () {
return (
`
+
${arrow}
@@ -176,65 +182,41 @@ export default (function () {
* Creates the popover html.
*/
JitsiPopover.prototype.createPopover = function () {
- $("body").append(this.template);
- let popoverElem = $(".jitsipopover > .jitsipopover__content");
+ let $popover = $('.jitsipopover');
- const { content } = this.options;
+ if (!$popover.length) {
+ $('body').append(this.template);
- if (React.isValidElement(content)) {
- /* jshint ignore:start */
- ReactDOM.render(
-
- { content }
- ,
- popoverElem.get(0),
- () => {
- // FIXME There seems to be odd timing interaction when a
- // React Component is manually removed from the DOM and then
- // created again, as the ReactDOM callback will fire before
- // render is called on the React Component. Using a timeout
- // looks to bypass this behavior, maybe by creating
- // different execution context. JitsiPopover should be
- // rewritten into react soon anyway or at least rewritten
- // so the html isn't completely torn down with each update.
- setTimeout(() => this._popoverCreated());
- });
- /* jshint ignore:end */
- return;
+ $popover = $('.jitsipopover');
+
+ $popover.on('mouseenter', () => {
+ this.popoverIsHovered = true;
+ if (typeof this.onHoverPopover === 'function') {
+ this.onHoverPopover(this.popoverIsHovered);
+ }
+ });
+
+ $popover.on('mouseleave', () => {
+ this.popoverIsHovered = false;
+ this.hide();
+ if (typeof this.onHoverPopover === 'function') {
+ this.onHoverPopover(this.popoverIsHovered);
+ }
+ });
}
- popoverElem.html(content);
- this._popoverCreated();
- };
+ const $popoverContent = $popover.find('.jitsipopover__content');
- /**
- * Adds listeners and executes callbacks after the popover has been created
- * and displayed.
- *
- * @private
- * @returns {void}
- */
- JitsiPopover.prototype._popoverCreated = function () {
- const { onBeforePosition } = this.options;
-
- if (typeof onBeforePosition === 'function') {
- onBeforePosition($(".jitsipopover"));
- }
-
- $('.jitsipopover').on('mouseenter', () => {
- this.popoverIsHovered = true;
- if (typeof this.onHoverPopover === 'function') {
- this.onHoverPopover(this.popoverIsHovered);
- }
- }).on('mouseleave', () => {
- this.popoverIsHovered = false;
- this.hide();
- if (typeof this.onHoverPopover === 'function') {
- this.onHoverPopover(this.popoverIsHovered);
- }
- });
-
- this.refreshPosition();
+ /* jshint ignore:start */
+ ReactDOM.render(
+
+ { this.options.content }
+ ,
+ $popoverContent.get(0),
+ () => {
+ this.refreshPosition();
+ });
+ /* jshint ignore:end */
};
/**
@@ -264,9 +246,9 @@ export default (function () {
*/
JitsiPopover.prototype.updateContent = function (content) {
this.options.content = content;
- if(!this.popoverShown)
+ if (!this.popoverShown) {
return;
- this.remove();
+ }
this.createPopover();
};
@@ -279,11 +261,9 @@ export default (function () {
JitsiPopover.prototype.remove = function () {
const $popover = $('.jitsipopover');
const $popoverContent = $popover.find('.jitsipopover__content');
- const attachedComponent = $popoverContent.get(0);
- if (attachedComponent) {
- // ReactDOM will no-op if no React Component is found.
- ReactDOM.unmountComponentAtNode(attachedComponent);
+ if ($popoverContent.length) {
+ ReactDOM.unmountComponentAtNode($popoverContent.get(0));
}
$popover.off();
diff --git a/modules/UI/videolayout/ConnectionIndicator.js b/modules/UI/videolayout/ConnectionIndicator.js
index be01f8dfb..cf78021a2 100644
--- a/modules/UI/videolayout/ConnectionIndicator.js
+++ b/modules/UI/videolayout/ConnectionIndicator.js
@@ -1,4 +1,4 @@
-/* global $, APP, interfaceConfig, JitsiMeetJS */
+/* global $, interfaceConfig, JitsiMeetJS */
/* jshint -W101 */
/* eslint-disable no-unused-vars */
@@ -113,7 +113,6 @@ ConnectionIndicator.prototype.create = function () {
this.popover = new JitsiPopover($(element), {
content: popoverContent,
skin: "black",
- onBeforePosition: el => APP.translation.translateElement(el),
position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top'
});
diff --git a/modules/UI/videolayout/RemoteVideo.js b/modules/UI/videolayout/RemoteVideo.js
index 9001cdb81..2517ae8f4 100644
--- a/modules/UI/videolayout/RemoteVideo.js
+++ b/modules/UI/videolayout/RemoteVideo.js
@@ -115,7 +115,6 @@ RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) {
content: popupMenuElement.outerHTML,
skin: "black",
hasArrow: false,
- onBeforePosition: el => APP.translation.translateElement(el),
position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top'
};
let element = $("#" + this.videoSpanId + " .remotevideomenu");