diff --git a/README.md b/README.md index d4fa6dac..fd4fbf74 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ The tether-dialog component supports all of the modal-dialog properties specifie Property | Purpose --------------------- | ------------- `hasOverlay` | Toggles presence of overlay div in DOM +`mouseleaveToClose` | Closes the dialog on a `mouseleave` event. Useful when providing 'pop-out' modal functionality for a specific area of the page. (default: `false`) `tetherClassPrefix` | Proxies to Hubspot Tether* `offset` | Proxies to Hubspot Tether* `targetOffset` | Proxies to Hubspot Tether* diff --git a/addon/components/tether-dialog.js b/addon/components/tether-dialog.js index 60b106f9..b6a2a6ac 100644 --- a/addon/components/tether-dialog.js +++ b/addon/components/tether-dialog.js @@ -19,6 +19,11 @@ export default ModalDialog.extend({ hasOverlay: true, target: 'viewport', // element, css selector, view instance, 'viewport', or 'scroll-handle' + mouseleaveToClose: false, + + // if `mouseleaveToClose`, cache this in didInsertElement so that each dialog has a unqiue listener namespace + _eventListenerNameSpace: null, + tetherClassPrefix: 'ember-tether', // offset - passed in // targetOffset - passed in @@ -28,6 +33,34 @@ export default ModalDialog.extend({ if (isIOS && get(this, 'hasOverlay')) { Ember.$('div[data-ember-modal-dialog-overlay]').css('cursor', 'pointer'); } - }) + }), + + didInsertElement() { + this._super(...arguments); + + if (this.get('mouseleaveToClose')) { + this._eventListenerNameSpace = `ember-modal-dialog.${this.elementId}`; + + Ember.$('.ember-modal-dialog').on(`mouseleave.${this._eventListenerNameSpace}`, (event) => { + // DON'T close if the mouse is leaving the target and entering the target + if (!event.relatedTarget || !Ember.$(event.relatedTarget).hasClass('ember-tether-target')) { + this.send('close'); + } + }); + Ember.$(this.get('target')).on(`mouseleave.${this._eventListenerNameSpace}`, (event) => { + // DON'T close if the mouse is leaving the target and entering the dialog + if (!event.relatedTarget || !Ember.$(event.relatedTarget).hasClass('ember-modal-dialog')) { + this.send('close'); + } + }); + + } + }, + + willDestroyElement() { + this._super(...arguments); + Ember.$('.ember-modal-dialog').off(`mouseleave.${this._eventListenerNameSpace}`); + Ember.$(this.get('target')).off(`mouseleave.${this._eventListenerNameSpace}`); + } }); diff --git a/tests/acceptance/tether-dialog-test.js b/tests/acceptance/tether-dialog-test.js index 209dfd22..5a74b682 100644 --- a/tests/acceptance/tether-dialog-test.js +++ b/tests/acceptance/tether-dialog-test.js @@ -114,6 +114,37 @@ test('target - view', function(assert) { }); }); +test('mouseleaveToClose functions with manual close button', function(assert) { + const openSelector = '#targetedMouseleaveToClose'; + const dialogText = 'Target w/ mouseleaveToClose'; + + assert.dialogOpensAndCloses({ + openSelector, + dialogText, + closeSelector: dialogCloseButton, + hasOverlay: false, + tethered: true + }); +}); + +test('mouseleaveToClose closes when a mouseleave event occurs on the dialog', function(assert) { + const openSelector = '#targetedMouseleaveToClose'; + const dialogText = 'Target w/ mouseleaveToClose'; + + click(openSelector).then(() => { + return assert.closesOnMouseleave(dialogSelector, dialogText); + }); +}); + +test('mouseleaveToClose closes when a mouseleave event occurs on the element used to open', function(assert) { + const openSelector = '#targetedMouseleaveToClose'; + const dialogText = 'Target w/ mouseleaveToClose'; + + click(openSelector).then(() => { + return assert.closesOnMouseleave(openSelector, dialogText); + }); +}); + test('subclassed modal', function(assert) { assert.dialogOpensAndCloses({ openSelector: '#example-subclass button', diff --git a/tests/dummy/app/controllers/application.js b/tests/dummy/app/controllers/application.js index 7b47301a..8a246db4 100644 --- a/tests/dummy/app/controllers/application.js +++ b/tests/dummy/app/controllers/application.js @@ -12,6 +12,7 @@ export default Ember.Controller.extend({ isShowingTargetSelector: false, isShowingTargetView: false, isShowingTargetElement: false, + isShowingMouseleaveToClose: false, isShowingSubclassed: false, isShowingInPlace: false, isInPlace: true, @@ -91,6 +92,9 @@ export default Ember.Controller.extend({ } this.toggleProperty('isShowingTargetElement'); }, + toggleMouseleaveToClose() { + this.toggleProperty('isShowingMouseleaveToClose'); + }, toggleSubclassed() { this.toggleProperty('isShowingSubclassed'); }, @@ -125,6 +129,11 @@ export default Ember.Controller.extend({ this.set('isShowingTargetElement', false); this.set('exampleTargetAttachment', 'middle left'); this.set('exampleAttachment', 'middle right'); + }, + closeMouseleaveToClose() { + this.set('isShowingMouseleaveToClose', false); + this.set('exampleTargetAttachment', 'middle left'); + this.set('exampleAttachment', 'middle right'); } } }); diff --git a/tests/dummy/app/templates/-tether-dialog.hbs b/tests/dummy/app/templates/-tether-dialog.hbs index 85e1b0e6..67528a12 100644 --- a/tests/dummy/app/templates/-tether-dialog.hbs +++ b/tests/dummy/app/templates/-tether-dialog.hbs @@ -155,6 +155,30 @@ {{/if}} +
Target w/ mouseleaveToClose - Selector: '#targetedMouseleaveToClose'
+Target Attachment: {{exampleTargetAttachment}}
+Attachment: {{exampleAttachment}}
+ + {{/tether-dialog}} + {{!-- END-SNIPPET --}} + {{/if}} +