diff --git a/spec/tooltip-manager-spec.coffee b/spec/tooltip-manager-spec.coffee index 4649479f5..6c15b0608 100644 --- a/spec/tooltip-manager-spec.coffee +++ b/spec/tooltip-manager-spec.coffee @@ -16,23 +16,50 @@ describe "TooltipManager", -> hover = (element, fn) -> element.dispatchEvent(new CustomEvent('mouseenter', bubbles: false)) element.dispatchEvent(new CustomEvent('mouseover', bubbles: true)) - advanceClock(manager.defaults.delay.show) + advanceClock(manager.hoverDefaults.delay.show) fn() element.dispatchEvent(new CustomEvent('mouseleave', bubbles: false)) element.dispatchEvent(new CustomEvent('mouseout', bubbles: true)) - advanceClock(manager.defaults.delay.hide) + advanceClock(manager.hoverDefaults.delay.hide) describe "::add(target, options)", -> - it "creates a tooltip when hovering over the target element if no trigger is specified", -> - manager.add element, title: "Title" - hover element, -> - expect(document.body.querySelector(".tooltip")).toHaveText("Title") + describe "when the trigger is 'hover' (the default)", -> + it "creates a tooltip when hovering over the target element", -> + manager.add element, title: "Title" + hover element, -> + expect(document.body.querySelector(".tooltip")).toHaveText("Title") - it "creates a tooltip immediately if the trigger type is manual", -> - disposable = manager.add element, title: "Title", trigger: "manual" - expect(document.body.querySelector(".tooltip")).toHaveText("Title") - disposable.dispose() - expect(document.body.querySelector(".tooltip")).toBeNull() + describe "when the trigger is 'manual'", -> + it "creates a tooltip immediately and only hides it on dispose", -> + disposable = manager.add element, title: "Title", trigger: "manual" + expect(document.body.querySelector(".tooltip")).toHaveText("Title") + disposable.dispose() + expect(document.body.querySelector(".tooltip")).toBeNull() + + describe "when the trigger is 'click'", -> + it "shows and hides the tooltip when the target element is clicked", -> + disposable = manager.add element, title: "Title", trigger: "click" + expect(document.body.querySelector(".tooltip")).toBeNull() + element.click() + expect(document.body.querySelector(".tooltip")).not.toBeNull() + element.click() + expect(document.body.querySelector(".tooltip")).toBeNull() + + # Hide the tooltip when clicking anywhere but inside the tooltip element + element.click() + expect(document.body.querySelector(".tooltip")).not.toBeNull() + document.body.querySelector(".tooltip").click() + expect(document.body.querySelector(".tooltip")).not.toBeNull() + document.body.querySelector(".tooltip").firstChild.click() + expect(document.body.querySelector(".tooltip")).not.toBeNull() + document.body.click() + expect(document.body.querySelector(".tooltip")).toBeNull() + + # Tooltip can show again after hiding due to clicking outside of the tooltip + element.click() + expect(document.body.querySelector(".tooltip")).not.toBeNull() + element.click() + expect(document.body.querySelector(".tooltip")).toBeNull() it "allows a custom item to be specified for the content of the tooltip", -> tooltipElement = document.createElement('div') diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 92a8d1ad3..ca6a342f4 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -153,7 +153,7 @@ class AtomEnvironment extends Model @keymaps = new KeymapManager({@configDirPath, resourcePath, notificationManager: @notifications}) - @tooltips = new TooltipManager(keymapManager: @keymaps) + @tooltips = new TooltipManager(keymapManager: @keymaps, viewRegistry: @views) @commands = new CommandRegistry @commands.attach(@window) diff --git a/src/tooltip-manager.coffee b/src/tooltip-manager.coffee index dbb0b7c44..7226ca9ee 100644 --- a/src/tooltip-manager.coffee +++ b/src/tooltip-manager.coffee @@ -46,14 +46,15 @@ Tooltip = null module.exports = class TooltipManager defaults: - delay: - show: 1000 - hide: 100 + trigger: 'hover' container: 'body' html: true placement: 'auto top' viewportPadding: 2 + hoverDefaults: + {delay: {show: 1000, hide: 100}} + constructor: ({@keymapManager, @viewRegistry}) -> # Essential: Add a tooltip to the given element. @@ -92,7 +93,11 @@ class TooltipManager else if keystroke? options.title = getKeystroke(bindings) - tooltip = new Tooltip(target, _.defaults(options, @defaults), @viewRegistry) + options = _.defaults(options, @defaults) + if options.trigger is 'hover' + options = _.defaults(options, @hoverDefaults) + + tooltip = new Tooltip(target, options, @viewRegistry) hideTooltip = -> tooltip.leave(currentTarget: target) diff --git a/src/tooltip.js b/src/tooltip.js index eba47ad3b..1baede496 100644 --- a/src/tooltip.js +++ b/src/tooltip.js @@ -65,6 +65,14 @@ Tooltip.prototype.init = function (element, options) { if (trigger === 'click') { this.disposables.add(listen(this.element, 'click', this.options.selector, this.toggle.bind(this))) + this.hideOnClickOutsideOfTooltip = (event) => { + const tooltipElement = this.getTooltipElement() + if (tooltipElement === event.target) return + if (tooltipElement.contains(event.target)) return + if (this.element === event.target) return + if (this.element.contains(event.target)) return + this.hide() + } } else if (trigger === 'manual') { this.show() } else { @@ -183,8 +191,11 @@ Tooltip.prototype.leave = function (event) { Tooltip.prototype.show = function () { if (this.hasContent() && this.enabled) { - var tip = this.getTooltipElement() + if (this.hideOnClickOutsideOfTooltip) { + window.addEventListener('click', this.hideOnClickOutsideOfTooltip, true) + } + var tip = this.getTooltipElement() var tipId = this.getUID('tooltip') this.setContent() @@ -316,6 +327,12 @@ Tooltip.prototype.setContent = function () { } Tooltip.prototype.hide = function (callback) { + this.inState = {} + + if (this.hideOnClickOutsideOfTooltip) { + window.removeEventListener('click', this.hideOnClickOutsideOfTooltip, true) + } + this.tip && this.tip.classList.remove('in') if (this.hoverState !== 'in') this.tip && this.tip.remove() @@ -445,7 +462,7 @@ Tooltip.prototype.destroy = function () { Tooltip.prototype.getDelegateComponent = function (element) { var component = tooltipComponentsByElement.get(element) if (!component) { - component = new Tooltip(element, this.getDelegateOptions()) + component = new Tooltip(element, this.getDelegateOptions(), this.viewRegistry) tooltipComponentsByElement.set(element, component) } return component