Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tagless component) popperClass is now class and id is passed to attachment #45

Merged
merged 1 commit into from
Aug 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 41 additions & 64 deletions addon/components/ember-attacher-inner.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { cancel, debounce, later, next } from '@ember/runloop';
import { computed, observer } from '@ember/object';
import Component from '@ember/component';
import { assert } from '@ember/debug';
import { htmlSafe } from '@ember/string';
import layout from '../templates/components/ember-attacher-inner';

Expand Down Expand Up @@ -53,31 +52,8 @@ export default Component.extend({
this._hide = this._hide.bind(this);
},

didInsertElement() {
this._super(...arguments);

// The Popper does not exist until after the element has been inserted
next(() => {
this._addListenersForShowEvents();

if (this.get('isShown') && this.get('_target')) {
this._addListenersforHideEvents();

this._show();
} else {
// When we first render the popper, it has no width if isVisible is false. This can cause
// the popper to be positioned too far to the right, such that when it expands, it will
// become larger than its parent. This, in turn, causes the parent to expand to accommodate
// the popper, which may now be off screen. To get around this, we just remove the
// positioning from the element to the safest position available: 0x0. The popper will then
// update its position from this._show()
this.element.parentNode.style.transform = null;
}
});
},

_addListenersForShowEvents() {
const target = this.get('_target');
const target = this.get('target');
const showOn = this.get('_showOn');

if (!target) {
Expand All @@ -104,6 +80,10 @@ export default Component.extend({

const target = this._currentTarget;

if (target === null) {
return;
}

[this._hideListenersOnTargetByEvent, this._showListenersOnTargetByEvent]
.forEach(eventToListener => {
Object.keys(eventToListener).forEach(event => {
Expand All @@ -130,27 +110,6 @@ export default Component.extend({
_showOn: computed('showOn', function() {
return this.get('showOn').split(' ');
}),
_target: computed('target', function() {
const target = this.get('target');

let popperTarget;

// If there is no target, set the target to the parent element
if (!target) {
popperTarget = this._initialParentNode;
} else if (target instanceof Element) {
popperTarget = target;
} else {
const nodes = document.querySelectorAll(target);

assert(`ember-attacher with target selector "${target}" found ${nodes.length}`
+ 'possible targets when there should be exactly 1', nodes.length === 1);

popperTarget = nodes[0];
}

return popperTarget;
}),

// The circle element needs a special duration that is slightly faster than the popper's
// transition, this prevents text from appearing outside the circle as it fills the background
Expand Down Expand Up @@ -182,23 +141,41 @@ export default Component.extend({
function() {
this._removeEventListeners();

// Regardless of whether or not the attachment is hidden, we want to add the show listeners
if (!this.get('target')) {
return;
}

const isInitialTarget = this._currentTarget === null;

this._addListenersForShowEvents();

if (!this._isHidden) {
this._addListenersforHideEvents();
this._addListenersForHideEvents();

} else if (isInitialTarget && this.get('isShown')) {
this._addListenersForHideEvents();

this._show();
} else {
// When we first render the popper, it has no width if isVisible is false. This can cause
// the popper to be positioned too far to the right, such that when it expands, it will
// become larger than its parent. This, in turn, causes the parent to expand to accommodate
// the popper, which may now be off screen. To get around this, we just remove the
// positioning from the element to the safest position available: 0x0. The popper will then
// update its position from this._show()
this.element.parentNode.style.transform = null;
}
}
),

_isShownChanged: observer('isShown', function() {
if (this.get('isShown')) {
if (this._isHidden) {
this._addListenersforHideEvents();
const isShown = this.get('isShown');

this._show();
}
} else if (!this._isHidden) {
if (isShown === true && this._isHidden) {
this._addListenersForHideEvents();

this._show();
} else if (isShown === false && !this._isHidden) {
this._hide();
}
}),
Expand All @@ -212,11 +189,11 @@ export default Component.extend({
cancel(this._isVisibleTimeout);

// The attachment is already visible or the target has been destroyed
if (!this._isHidden || !this.get('_target')) {
if (!this._isHidden || !this.get('target')) {
return;
}

this._addListenersforHideEvents();
this._addListenersForHideEvents();

const showDelay = parseInt(this.get('showDelay'));

Expand All @@ -226,7 +203,7 @@ export default Component.extend({
_show() {
// The target of interactive tooltips receive the 'active' class
if (this.get('interactive')) {
this.get('_target').classList.add('active');
this.get('target').classList.add('active');
}

// Make the attachment visible immediately so transition animations can take place
Expand Down Expand Up @@ -255,9 +232,9 @@ export default Component.extend({
this._isHidden = false;
},

_addListenersforHideEvents() {
_addListenersForHideEvents() {
const hideOn = this.get('_hideOn');
const target = this.get('_target');
const target = this.get('target');

if (hideOn.indexOf('click') !== -1) {
const showOnClickListener = this._showListenersOnTargetByEvent['click'];
Expand Down Expand Up @@ -306,7 +283,7 @@ export default Component.extend({
},

_hideIfMouseOutsideTargetOrAttachment(event) {
const target = this.get('_target');
const target = this.get('target');

// If cursor is not on the attachment or target, hide the element
if (!target.contains(event.target)
Expand All @@ -326,7 +303,7 @@ export default Component.extend({
const { clientX, clientY } = event;

const attachmentPosition = this.element.getBoundingClientRect();
const targetPosition = this.get('_target').getBoundingClientRect();
const targetPosition = this.get('target').getBoundingClientRect();

const isBetweenLeftAndRight = clientX > Math.min(attachmentPosition.left, targetPosition.left)
&& clientX < Math.max(attachmentPosition.right, targetPosition.right);
Expand Down Expand Up @@ -367,7 +344,7 @@ export default Component.extend({

_hideOnLostFocus(event) {
if (event.relatedTarget === null
|| (!this.get('_target').contains(event.relatedTarget)
|| (!this.get('target').contains(event.relatedTarget)
&& !this.element.contains(event.relatedTarget))) {
this._hideAfterDelay();
}
Expand All @@ -382,7 +359,7 @@ export default Component.extend({
cancel(this._isVisibleTimeout);

// The attachment is already hidden or the target was destroyed
if (this._isHidden || !this.get('_target')) {
if (this._isHidden || !this.get('target')) {
return;
}

Expand Down Expand Up @@ -410,7 +387,7 @@ export default Component.extend({
},

_removeListenersForHideEvents() {
const target = this.get('_target');
const target = this.get('target');
const showOn = this.get('_showOn');

// Switch clicking back to a show event
Expand Down
18 changes: 15 additions & 3 deletions addon/components/ember-attacher.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ const DEFAULTS = {
showOn: 'mouseenter focus'
};

// TODO(kjb) see if there is a way to only pull generateGuid
import Ember from 'ember';
const { generateGuid } = Ember;

export default Component.extend({
layout,

tagName: '',

/**
* ================== PUBLIC CONFIG OPTIONS ==================
*/
Expand Down Expand Up @@ -63,20 +69,26 @@ export default Component.extend({
showDelay: DEFAULTS.showDelay,
showDuration: DEFAULTS.showDuration,
showOn: DEFAULTS.showOn,
target: computed(function() {
return (typeof FastBoot === 'undefined') ? this.element.parentNode : null;
}),
target: null,

/**
* ================== PRIVATE IMPLEMENTATION DETAILS ==================
*/

actions: {
onFoundTarget(target) {
this.set('_target', target);
}
},

// Part of the Component superclass. isVisible == false sets 'display: none'
isVisible: alias('renderInPlace'),

init() {
this._super(...arguments);

this.id = this.id || generateGuid();

let options = getOwner(this).resolveRegistration('config:environment').emberAttacher;

// If no emberAttacher hash was found, do nothing
Expand Down
8 changes: 5 additions & 3 deletions addon/templates/components/ember-attacher.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{{#ember-popper eventsEnabled=false
{{#ember-popper class=class
eventsEnabled=false
id=id
modifiers=_modifiers
onFoundTarget=(action 'onFoundTarget')
placement=placement
class=popperClass
popperContainer=popperContainer
renderInPlace=renderInPlace
target=target as |emberPopper|}}
Expand All @@ -22,7 +24,7 @@
showDelay=showDelay
showDuration=showDuration
showOn=showOn
target=target as |inner|}}
target=_target as |inner|}}
{{yield (hash emberPopper=emberPopper
hide=inner.hide)}}
{{/ember-attacher-inner}}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"ember-cli-babel": "^6.8.1",
"ember-cli-htmlbars": "^2.0.2",
"ember-cli-sass": "^7.0.0",
"ember-popper": "^0.5.1",
"ember-popper": "^0.6.0",
"ember-truth-helpers": "^1.3.0"
},
"devDependencies": {
Expand Down
29 changes: 8 additions & 21 deletions tests/integration/components/ember-attacher-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import hbs from 'htmlbars-inline-precompile';
import wait from 'ember-test-helpers/wait';
import { click, find } from 'ember-native-dom-helpers';
import { moduleForComponent, test } from 'ember-qunit';

Expand All @@ -12,7 +11,7 @@ test('it renders', function(assert) {

this.render(hbs`
<div>
{{#ember-attacher popperClass="hello"}}
{{#ember-attacher class='hello'}}
popper text
{{/ember-attacher}}
</div>
Expand Down Expand Up @@ -58,14 +57,15 @@ test('nested attachers open and close as expected', async function(assert) {

{{#ember-attacher hideOn=hideOn
isShown=parentIsShown
popperClass="parent"
class='parent'
showOn=showOn}}
<button id="openChild" {{action 'openChildPopover'}}>
Open child

{{#ember-attacher hideOn=hideOn
{{#ember-attacher hideDuration=0
hideOn=hideOn
isShown=childIsShown
popperClass="child"
class='child'
showOn=showOn}}
<button id="closeChild" {{action 'closeChildPopover'}}>
Close child
Expand All @@ -76,9 +76,6 @@ test('nested attachers open and close as expected', async function(assert) {
</button>
`);

// Need to wait for didInsertElement to process
await wait();

let child = find('.child', document.documentElement);
let innerChildAttacher = find('.inner', child);
let parent = find('.parent', document.documentElement);
Expand Down Expand Up @@ -116,25 +113,20 @@ test('isShown works with showOn/hideOn set to "click"', async function(assert) {

{{#ember-attacher hideOn=hideOn
isShown=isShown
popperClass="hello"
class='hello'
showOn=showOn}}
isShown w/ hideOn/ShowOn of 'click'
{{/ember-attacher}}
</button>
`);

// Need to wait for didInsertElement to process
await wait();

let popper = find('.hello', document.documentElement);
let innerAttacher = find('.inner', popper);

assert.equal(innerAttacher.style.display, '', 'Initially shown');

await click(find('#toggle-show', document.documentElement));

await wait();

assert.equal(innerAttacher.style.display, 'none', 'Now hidden');

await click(find('#toggle-show', document.documentElement));
Expand Down Expand Up @@ -165,7 +157,7 @@ test('isShown works with showOn/hideOn set to "none"', async function(assert) {

{{#ember-attacher hideOn=hideOn
isShown=isShown
popperClass="hello"
class='hello'
showOn=showOn}}
isShown w/ hideOn/ShowOn of 'none'

Expand Down Expand Up @@ -204,25 +196,20 @@ test('showOn/hideOn set to "click"', async function(assert) {
Click me, captain!

{{#ember-attacher hideOn=hideOn
popperClass="hello"
class='hello'
showOn=showOn}}
showOn/hideOn "click"
{{/ember-attacher}}
</button>
`);

// Need to wait for didInsertElement to add the click listener
await wait();

let popper = find('.hello', document.documentElement);
let innerAttacher = find('.inner', popper);

assert.equal(innerAttacher.style.display, 'none', 'Initially hidden');

await click(find('#click-toggle', document.documentElement));

await wait();

assert.equal(innerAttacher.style.display, '', 'Now shown');

await click(find('#click-toggle', document.documentElement));
Expand Down
Loading