From 615a2ca1b6ca76e70195e474633fa15b20993687 Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Sun, 14 May 2017 19:40:23 -0400 Subject: [PATCH] Add support for an `overlayPosition` property which supports values of `'parent'` or `'sibling'` - this property affects the relative DOM elements for non-tethered use cases - `parent`, the default, uses a structure of: ``` wrapper > overlay > container ``` - `sibling`, uses a structure of: `wrapper > overlay > container` ``` wrapper > overlay > container ``` --- README.md | 25 ++++++------ addon/components/basic-dialog.js | 6 +-- addon/components/modal-dialog.js | 23 ++++++++++- addon/templates/components/basic-dialog.hbs | 43 ++++++++++++++------- addon/templates/components/modal-dialog.hbs | 1 + tests/acceptance/basic-test.js | 28 ++++++++++++++ tests/dummy/app/templates/index.hbs | 25 ++++++++++-- 7 files changed, 117 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 358e081..0c96225 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ Property | Purpose `onClickOverlay` | An action to be called when the overlay is clicked. If this action is specified, clicking the overlay will invoke it instead of `onClose`. `clickOutsideToClose` | Indicates whether clicking outside a modal *without* an overlay should close the modal. Useful if your modal isn't the focus of interaction, and you want hover effects to still work outside the modal. `renderInPlace` | A boolean, when true renders the modal without wormholing or tethering, useful for including a modal in a style guide +`overlayPosition` | either `'parent'` or `'sibling'`, to control whether the overlay div is rendered as a parent element of the container div or as a sibling to it (default: `'parent'`) `containerClass` | CSS class name(s) to append to container divs. Set this from template. `containerClassNames` | CSS class names to append to container divs. This is a concatenated property, so it does **not** replace the default container class (default: `'ember-modal-dialog'`. If you subclass this component, you may define this in your subclass.) `overlayClass` | CSS class name(s) to append to overlay divs. Set this from template. @@ -120,44 +121,44 @@ Property | Purpose ## Which Component Should I Use? -Various modal use cases are best supported by different DOM structures. Ember Modal Dialog provides the following components: +Various modal use cases are best supported by different DOM structures. Ember Modal Dialog's `modal-dialog` component provides the following capabilities: -- modal-dialog: Uses ember-wormhole to append the following nested divs to the destination element: wrapper div > overlay div > container div +- modal-dialog without passing a `tetherTarget`: Uses ember-wormhole to append the following parent divs to the destination element: wrapper div > overlay div > container div ![](tests/dummy/public/modal-dialog.png) -- tether-dialog: Uses ember-tether to display modal container div. Uses ember-wormhole to append optional overlay div to the destination element. Requires separate installation of [ember-tether](//github.com/yapplabs/ember-tether) dependency. +This can be customized (see `overlayPosition`). + +- modal-dialog, with a `tetherTarget` provided: Uses ember-tether to display modal container div. Uses ember-wormhole to append optional overlay div to the destination element. Requires separate installation of [ember-tether](//github.com/yapplabs/ember-tether) dependency. ![](tests/dummy/public/tether-dialog.png) ## Positioning -Ember Modal Dialog provides `attachment` and `targetAttachment` properties to configure positioning of the modal dialog near its target. To provide consistency with Hubspot Tether, Ember Modal Dialog uses the same syntax for these properties: "top|middle|bottom left|center|right|elementCenter"... e.g. `'middle left'` - -### Positioning Your Modal Dialog - With the default SCSS provided, your modal will be centered in the viewport. By adjusting the CSS, you can adjust this logic. Pass a `tetherTarget` in order to position our modal in relation to the target and enable your modal remain positioned near their targets when users scroll or resize the window. +Use `attachment` and `targetAttachment` properties to configure positioning of the modal dialog near its target. Ember Modal Dialog uses the syntax from Hubspot Tether for these properties: "top|middle|bottom left|center|right|elementCenter"... e.g. `'middle left'` + To enable this behavior, install ember-tether as a dependency of **your ember app**. `ember install ember-tether` -Then use the tether-dialog component for any modals you wish to position this way: +Then pass a selector as `tetherTarget` for the modal you wish to position this way: ```htmlbars -{{#tether-dialog - target='#target-element-id' +{{#modal-dialog + tetherTarget='#target-element-id' targetAttachment='middle right' attachment='middle left'}} I am a modal that will remain tethered to the right of the element with id 'target-element-id' -{{/tether-dialog}} +{{/modal-dialog}} ``` #### Caveats -Event delegation originating from content inside ember-tether blocks will only work for Ember apps that use Ember's default root element of the `body` tag. This is because the Hubspot Tether library appends its positioned elements to the body tag. +Event delegation originating from content inside ember-tether blocks will only work for Ember apps that use Ember's default root element of the `body` tag. This is because, generally speaking, the Hubspot Tether library appends its positioned elements to the body element. If you are not overriding the default root element, then don't worry and carry on. ember-tether will work just fine for you. diff --git a/addon/components/basic-dialog.js b/addon/components/basic-dialog.js index 7fd8e09..07027e7 100644 --- a/addon/components/basic-dialog.js +++ b/addon/components/basic-dialog.js @@ -42,9 +42,9 @@ export default Ember.Component.extend({ clickOutsideToClose: false, hasOverlay: true, isCentered: true, - overlayDOMPosition: null, - isOverlaySibling: computed('overlayDOMPosition', function() { - return this.get('overlayDOMPosition') === 'sibling'; + overlayPosition: null, + isOverlaySibling: computed('overlayPosition', function() { + return this.get('overlayPosition') === 'sibling'; }), makeOverlayClickableOnIOS: Ember.on('didInsertElement', function() { diff --git a/addon/components/modal-dialog.js b/addon/components/modal-dialog.js index eb4656e..bb52342 100644 --- a/addon/components/modal-dialog.js +++ b/addon/components/modal-dialog.js @@ -2,7 +2,10 @@ import Ember from 'ember'; import layout from '../templates/components/modal-dialog'; const { computed, inject, isEmpty } = Ember; const { dasherize } = Ember.String; -import { deprecate } from '@ember/debug'; +import { deprecate, warn } from '@ember/debug'; +import { DEBUG } from '@glimmer/env'; + +const VALID_OVERLAY_POSITIONS = ['parent', 'sibling']; export default Ember.Component.extend({ tagName: '', @@ -20,7 +23,22 @@ export default Ember.Component.extend({ } return 'ember-modal-dialog/-basic-dialog'; }), - + didReceiveAttrs() { + this._super(...arguments); + if (DEBUG) { + this.validateProps(); + } + }, + validateProps() { + let overlayPosition = this.get('overlayPosition'); + if (VALID_OVERLAY_POSITIONS.indexOf(overlayPosition) === -1) { + warn( + `overlayPosition value '${overlayPosition}' is not valid (valid values [${VALID_OVERLAY_POSITIONS.join(', ')}])`, + false, + { id: 'ember-modal-dialog.validate-overlay-position'} + ); + } + }, // onClose - set this from templates close: computed('onClose', { get() { @@ -88,6 +106,7 @@ export default Ember.Component.extend({ hasOverlay: true, translucentOverlay: false, + overlayPosition: 'parent', // `parent` or `sibling` clickOutsideToClose: false, renderInPlace: false, tetherTarget: null, diff --git a/addon/templates/components/basic-dialog.hbs b/addon/templates/components/basic-dialog.hbs index aaa98b7..5dbae32 100644 --- a/addon/templates/components/basic-dialog.hbs +++ b/addon/templates/components/basic-dialog.hbs @@ -1,16 +1,9 @@ {{#ember-wormhole to=destinationElementId}} -
- {{#if hasOverlay}} -
- {{#ember-modal-dialog-positioned-container - class=containerClassNamesString - targetAttachment=targetAttachment - target=legacyTarget - }} - {{yield}} - {{/ember-modal-dialog-positioned-container}} -
- {{else}} + {{#if isOverlaySibling}} +
+ {{#if hasOverlay}} +
+ {{/if}} {{#ember-modal-dialog-positioned-container class=containerClassNamesString targetAttachment=targetAttachment @@ -18,6 +11,28 @@ }} {{yield}} {{/ember-modal-dialog-positioned-container}} - {{/if}} -
+
+ {{else}} +
+ {{#if hasOverlay}} +
+ {{#ember-modal-dialog-positioned-container + class=containerClassNamesString + targetAttachment=targetAttachment + target=legacyTarget + }} + {{yield}} + {{/ember-modal-dialog-positioned-container}} +
+ {{else}} + {{#ember-modal-dialog-positioned-container + class=containerClassNamesString + targetAttachment=targetAttachment + target=legacyTarget + }} + {{yield}} + {{/ember-modal-dialog-positioned-container}} + {{/if}} +
+ {{/if}} {{/ember-wormhole}} diff --git a/addon/templates/components/modal-dialog.hbs b/addon/templates/components/modal-dialog.hbs index d534e70..6631dc7 100644 --- a/addon/templates/components/modal-dialog.hbs +++ b/addon/templates/components/modal-dialog.hbs @@ -9,6 +9,7 @@ translucentOverlay=translucentOverlay clickOutsideToClose=clickOutsideToClose destinationElementId=destinationElementId + overlayPosition=overlayPosition tetherTarget=tetherTarget legacyTarget=target diff --git a/tests/acceptance/basic-test.js b/tests/acceptance/basic-test.js index 3368fb1..1298a69 100644 --- a/tests/acceptance/basic-test.js +++ b/tests/acceptance/basic-test.js @@ -67,6 +67,34 @@ test('modal without overlay', async function(assert) { }); }); +test('modal with overlay', async function(assert) { + await assert.dialogOpensAndCloses({ + openSelector: '#example-translucent button', + dialogText: 'With Translucent Overlay', + closeSelector: overlaySelector + }); + + await assert.dialogOpensAndCloses({ + openSelector: '#example-translucent button', + dialogText: 'With Translucent Overlay', + closeSelector: dialogCloseButton + }); +}); + +test('modal with sibling overlay', async function(assert) { + await assert.dialogOpensAndCloses({ + openSelector: '#example-overlay-sibling button', + dialogText: 'With Translucent Overlay as Sibling', + closeSelector: overlaySelector + }); + + await assert.dialogOpensAndCloses({ + openSelector: '#example-overlay-sibling button', + dialogText: 'With Translucent Overlay as Sibling', + closeSelector: dialogCloseButton + }); +}); + test('clicking translucent overlay triggers callback', async function(assert) { window.onClickOverlayCallbackCalled = false; diff --git a/tests/dummy/app/templates/index.hbs b/tests/dummy/app/templates/index.hbs index 79d4cb0..11b713d 100644 --- a/tests/dummy/app/templates/index.hbs +++ b/tests/dummy/app/templates/index.hbs @@ -81,6 +81,25 @@ {{/if}} +
+

With Overlay as a Sibling in the DOM

+ + {{code-snippet name='translucent-modal-dialog-sibling.hbs'}} + {{#if isShowingSibling}} + {{!-- BEGIN-SNIPPET translucent-modal-dialog-sibling --}} + {{#modal-dialog + onClose=(action (mut isShowingSibling) false) + translucentOverlay=true + overlayPosition='sibling' + }} +

Stop! Modal Time!

+

With Translucent Overlay as Sibling

+ + {{/modal-dialog}} + {{!-- END-SNIPPET --}} + {{/if}} +
+

Custom Styles

@@ -204,9 +223,9 @@ onClose='toggleCenteredScrolling' translucentOverlay=true targetAttachment='none' - container-class='centered-scrolling-container' - overlay-class='centered-scrolling-overlay' - wrapper-class='centered-scrolling-wrapper' + containerClass='centered-scrolling-container' + overlayClass='centered-scrolling-overlay' + wrapperClass='centered-scrolling-wrapper' }}

Really Long Content To Demonstrate Scrolling