From 3410ff2e643fa9ed839e4ba742be35193d975fa2 Mon Sep 17 00:00:00 2001 From: Aman Agrawal Date: Sat, 10 Apr 2021 13:30:39 +0000 Subject: [PATCH] buddy_tooltips: Destroy tooltips if `reference` is not visible. We destroy the tooltips for which `reference` was either removed from DOM or is hidden. We only need to do this for tooltips contained in simplebar containers for which tooltips can overflow the boundary of the simplebar container. There are 4 approaches we could have done this: 1. Asked tippy.js maintainers to do this for us. In https://github.com/atomiks/tippyjs/issues/938 the maintainer said that it is the responsibility of the user to do so. 2. Tracked whenever we update the DOM for such elements and hide tooltips when we were hiding the `reference` elements. This had various problems like it is hard trigger events when certain elements have been removed from DOM when `html()` method is used to render new content. 3. Run an `optimized` periodic job to destroy tooltips when `reference` elements are hidden. This isn't a good method to do this since it sucks power and adds latency. 4. Use a `MutationObserver` on the parent element and watch for changes. This methods seems to work well with no bad side effects. We use this approach. --- static/js/click_handlers.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/static/js/click_handlers.js b/static/js/click_handlers.js index 0677dd27b7..c929b20a7a 100644 --- a/static/js/click_handlers.js +++ b/static/js/click_handlers.js @@ -529,6 +529,7 @@ export function initialize() { function do_render_buddy_list_tooltip(elem, title_data) { let placement = "left"; + let observer; if (window.innerWidth < media_breakpoints_num.md) { // On small devices display tooltips based on available space. // This will default to "bottom" placement for this tooltip. @@ -546,6 +547,32 @@ export function initialize() { showOnCreate: true, onHidden: (instance) => { instance.destroy(); + observer.disconnect(); + }, + onShow: (instance) => { + // For both buddy list and top left corner pm list, `target_node` + // is their parent `ul` element. We cannot use MutationObserver + // directly on the reference element because it will be removed + // and we need to attach it on an element which will remain in the + // DOM which is their parent `ul`. + const target_node = $(instance.reference).parents("ul").get(0); + // We only need to know if any of the `li` elements were removed. + const config = {attributes: false, childList: true, subtree: false}; + const callback = function (mutationsList) { + for (const mutation of mutationsList) { + // Hide instance if reference is in the removed node list. + if ( + Array.prototype.includes.call( + mutation.removedNodes, + instance.reference.parentElement, + ) + ) { + instance.hide(); + } + } + }; + observer = new MutationObserver(callback); + observer.observe(target_node, config); }, }); }