Skip to content

Commit

Permalink
Merge pull request #92 from a15n/ember-popover
Browse files Browse the repository at this point in the history
ember-popovers
  • Loading branch information
Duncan Walker authored Oct 5, 2016
2 parents 4837695 + 70608ed commit 0171fbf
Show file tree
Hide file tree
Showing 26 changed files with 1,441 additions and 466 deletions.
43 changes: 31 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Ember-tooltips [![Build Status](https://travis-ci.org/sir-dunxalot/ember-tooltips.svg)](https://travis-ci.org/sir-dunxalot/ember-tooltips) [![npm](https://img.shields.io/npm/v/ember-tooltips.svg)](https://www.npmjs.com/package/ember-tooltips)
Ember-tooltips (and popovers) [![Build Status](https://travis-ci.org/sir-dunxalot/ember-tooltips.svg)](https://travis-ci.org/sir-dunxalot/ember-tooltips) [![npm](https://img.shields.io/npm/v/ember-tooltips.svg)](https://www.npmjs.com/package/ember-tooltips)
======

Render tooltips on components and other HTML elements using HTMLBars.
Render tooltips and popovers on components and other HTML elements using HTMLBars.

## Installation

Expand Down Expand Up @@ -87,9 +87,13 @@ You can also specify the ID of the element to attach the tooltip to:

The `target` property must be an ID, including the `#`.

### Popover on Element

Popovers can be created with `{{popover-on-element}}` and `{{popover-on-component}}` with the same `target` behavior as tooltips.

## Options

Options are set as attributes on the tooltip components. Current tooltip properties this addon supports are:
Options are set as attributes on the tooltip/popover components. Current tooltip/popover properties this addon supports are:

- [class](#class)
- [delay](#delay)
Expand All @@ -102,7 +106,8 @@ Options are set as attributes on the tooltip components. Current tooltip propert
- [side](#side)
- [showOn](#show-on)
- [spacing](#spacing)
- [tooltipIsVisible](#tooltip-is-visible)
- [isShown](#is-shown)
- [hideDelay (popover only)](#hide-delay)

#### Class

Expand Down Expand Up @@ -202,7 +207,7 @@ The event that the tooltip will hide and show for. Possible options are:

This event is overwritten by the individual [`hideOn`](#hide-on) and [`showOn`](#show-on) properties. In effect, setting `event` sets `hideOn` and `shownOn` for you.

The tooltip can also be shown programatically by passing in the `tooltipIsVisible` property, [documented here](#tooltip-is-visible).
The tooltip can also be shown programatically by passing in the `isShown` property, [documented here](#is-shown).

#### Hide on

Expand Down Expand Up @@ -301,7 +306,7 @@ Sets the number of pixels the tooltip will render from the target element. A hig
{{tooltip-on-component spacing=20}}
```

#### Tooltip is visible
#### Tooltip is shown

| Type | Boolean |
|---------|---------|
Expand All @@ -313,9 +318,23 @@ This can be useful alongside `event='none'` when you only want to toolip to show

```hbs
{{!--Binds the tooltip visibility to the showTooltip property--}}
{{tooltip-on-component tooltipIsVisible=showTooltip}}
{{tooltip-on-component isShown=showTooltip}}
```

#### Hide delay

| Type | Number |
|---------|---------|
| Default | 250 |

**POPOVER ONLY:** The number of milliseconds before the popover will hide after the user hovers away from the popover and the popover target. This is only applicable when `event='hover'`.

```hbs
{{popover-on-component event="hover" hideDelay=300}}
```

![popover-hover](https://cloud.githubusercontent.com/assets/7050871/18113238/e010ee64-6ee2-11e6-9ff1-a0c674a6d702.gif)

### Setting Defaults

You can set the default for any option by extending the `{{tooltip-on-element}}` component:
Expand All @@ -333,14 +352,14 @@ export default TooltipOnElementComponent.extend({

## Actions

Four actions are available for you to hook onto through the tooltip lifecycle:
Four actions are available for you to hook onto through the tooltip/popover lifecycle:

```hbs
{{tooltip-on-component
onTooltipDestroy='onTooltipDestroy'
onTooltipHide='onTooltipHide'
onTooltipRender='onTooltipRender'
onTooltipShow='onTooltipShow'
onDestroy='onDestroy'
onHide='onHide'
onRender='onRender'
onShow='onShow'
}}
```

Expand Down
8 changes: 8 additions & 0 deletions addon/components/popover-on-component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import PopoverOnElementComponent from 'ember-tooltips/components/popover-on-element';
import { onComponentTarget } from 'ember-tooltips/utils';

export default PopoverOnElementComponent.extend({

target: onComponentTarget,

});
122 changes: 122 additions & 0 deletions addon/components/popover-on-element.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import Ember from 'ember';
import TooltipAndPopoverComponent from 'ember-tooltips/components/tooltip-and-popover';
import layout from 'ember-tooltips/templates/components/popover-on-element';

const { $, run } = Ember;

export default TooltipAndPopoverComponent.extend({

/* Options */
hideDelay: 250,

/* Properties */
layout,
classNames: ['ember-popover'],
_isMouseInside: false,
didInsertElement() {
this._super(...arguments);

const event = this.get('event');
const target = this.get('target');
const $target = $(target);
const $popover = this.$();

if (event === 'none') {

return;

} else if (event === 'hover') {

const _showOn = this.get('_showOn');
const _hideOn = this.get('_hideOn');

// _showOn == 'mouseenter'
$target.on(_showOn, () => this.show());

// _hideOn == 'mouseleave'
$target.add($popover).on(_hideOn, () => {
run.later(() => {
if (!this.get('_isMouseInside')) {
this.hide();
}
}, +this.get('hideDelay'));
});

// we must use mouseover/mouseout because they correctly
// register hover interactivity when spacing='0'
$target.add($popover).on('mouseover', () => this.set('_isMouseInside', true));
$target.add($popover).on('mouseout', () => this.set('_isMouseInside', false));

} else if (event === 'click') {

$(document).on(`click.${target}`, (event) => {
// this lightweight, name-spaced click handler is necessary to determine
// if a click is NOT on $target and NOT an ancestor of $target.
// If so then it must be a click elsewhere and should close the popover
// see... https://css-tricks.com/dangers-stopping-event-propagation/
const isClickedElementElsewhere = this._isElementElsewhere(event.target);
const isPopoverShown = this.get('isShown');

if (isClickedElementElsewhere && isPopoverShown) {
this.hide();
}
});

// we use mousedown because it occurs before the focus event
$target.on('mousedown', (event) => {
// $target.on('mousedown') is called when the $popover is
// clicked because the $popover is contained within the $target.
// This will ignores those types of clicks.
const isMouseDownElementInPopover = this._isElementInPopover(event.target);
if (isMouseDownElementInPopover) {
return;
}
this.toggle();
});
}

$target.on('focus', () => this.show());

$popover.on('focusout', () => {
// use a run.later() to allow the 'focusout' event to finish handling
run.later(() => {
const isFocusedElementElsewhere = this._isElementElsewhere(document.activeElement);
if (isFocusedElementElsewhere) {
this.hide();
}
});
});
},
willDestroyElement() {
this._super(...arguments);

const target = this.get('target');
const $target = $(target);
const $popover = this.$();
const _showOn = this.get('_showOn');
const _hideOn = this.get('_hideOn');

$target.add($popover).off(`${_showOn} mouseover ${_hideOn} mouseout mousedown focus focusout`);

$(document).off(`click.${target}`);
},
_isElementInPopover(newElement) {
// determines if newElement is $popover or contained within $popover
const $popover = this.$();
return $popover.is(newElement) || $popover.find(newElement).length;
},
_isElementElsewhere(newElement) {
// determines if newElement is not $target, not $popover, and not contained within either
const $target = $(this.get('target'));

const isNewElementOutsideTarget = !$target.is(newElement) && !$target.find(newElement).length;
const isNewElementOutsidePopover = !this._isElementInPopover(newElement);

return isNewElementOutsideTarget && isNewElementOutsidePopover;
},
actions: {
hide() {
this.hide();
}
},
});
Loading

0 comments on commit 0171fbf

Please sign in to comment.