|
| 1 | +import {Component, ComponentRef, Directive, Input, ElementRef, ViewContainerRef, |
| 2 | + ChangeDetectorRef} from '@angular/core'; |
| 3 | +import {Overlay} from '@angular2-material/core/overlay/overlay'; |
| 4 | +import {OverlayState} from '@angular2-material/core/overlay/overlay-state'; |
| 5 | +import {OverlayRef} from '@angular2-material/core/overlay/overlay-ref'; |
| 6 | +import {ComponentPortal} from '@angular2-material/core/portal/portal'; |
| 7 | +import {OverlayConnectionPosition, OriginConnectionPosition} from |
| 8 | + '@angular2-material/core/overlay/position/connected-position'; |
| 9 | + |
| 10 | +export type TooltipPosition = 'before' | 'after' | 'above' | 'below'; |
| 11 | + |
| 12 | +@Directive({ |
| 13 | + selector: '[md-tooltip]', |
| 14 | + host: { |
| 15 | + '(mouseenter)': '_handleMouseEnter($event)', |
| 16 | + '(mouseleave)': '_handleMouseLeave($event)', |
| 17 | + } |
| 18 | +}) |
| 19 | +export class MdTooltip { |
| 20 | + visible: boolean = false; |
| 21 | + |
| 22 | + /** Allows the user to define the position of the tooltip relative to the parent element */ |
| 23 | + private _position: TooltipPosition = 'below'; |
| 24 | + @Input('tooltip-position') get position(): TooltipPosition { |
| 25 | + return this._position; |
| 26 | + } |
| 27 | + set position(value: TooltipPosition) { |
| 28 | + if (value !== this._position) { |
| 29 | + this._position = value; |
| 30 | + this._createOverlay(); |
| 31 | + this._updatePosition(); |
| 32 | + } |
| 33 | + } |
| 34 | + |
| 35 | + /** The message to be displayed in the tooltip */ |
| 36 | + private _message: string; |
| 37 | + @Input('md-tooltip') get message() { |
| 38 | + return this._message; |
| 39 | + } |
| 40 | + set message(value: string) { |
| 41 | + this._message = value; |
| 42 | + this._updatePosition(); |
| 43 | + } |
| 44 | + |
| 45 | + private _overlayRef: OverlayRef; |
| 46 | + |
| 47 | + constructor(private _overlay: Overlay, private _elementRef: ElementRef, |
| 48 | + private _viewContainerRef: ViewContainerRef, |
| 49 | + private _changeDetectionRef: ChangeDetectorRef) {} |
| 50 | + |
| 51 | + /** |
| 52 | + * Create overlay on init |
| 53 | + * TODO: @internal |
| 54 | + */ |
| 55 | + ngOnInit() { |
| 56 | + this._createOverlay(); |
| 57 | + } |
| 58 | + |
| 59 | + /** |
| 60 | + * Create the overlay config and position strategy |
| 61 | + */ |
| 62 | + private _createOverlay() { |
| 63 | + if (this._overlayRef) { |
| 64 | + if (this.visible) { |
| 65 | + // if visible, hide before destroying |
| 66 | + this.hide().then(() => this._createOverlay()); |
| 67 | + } else { |
| 68 | + // if not visible, dispose and recreate |
| 69 | + this._overlayRef.dispose(); |
| 70 | + this._overlayRef = null; |
| 71 | + this._createOverlay(); |
| 72 | + } |
| 73 | + } else { |
| 74 | + let origin = this._getOrigin(); |
| 75 | + let position = this._getOverlayPosition(); |
| 76 | + let strategy = this._overlay.position().connectedTo(this._elementRef, origin, position); |
| 77 | + let config = new OverlayState(); |
| 78 | + config.positionStrategy = strategy; |
| 79 | + this._overlay.create(config).then(ref => { |
| 80 | + this._overlayRef = ref; |
| 81 | + }); |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + /** |
| 86 | + * Returns the origin position based on the user's position preference |
| 87 | + */ |
| 88 | + private _getOrigin(): OriginConnectionPosition { |
| 89 | + switch (this.position) { |
| 90 | + case 'before': return { originX: 'start', originY: 'center' }; |
| 91 | + case 'after': return { originX: 'end', originY: 'center' }; |
| 92 | + case 'above': return { originX: 'center', originY: 'top' }; |
| 93 | + case 'below': return { originX: 'center', originY: 'bottom' }; |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + /** |
| 98 | + * Returns the overlay position based on the user's preference |
| 99 | + */ |
| 100 | + private _getOverlayPosition(): OverlayConnectionPosition { |
| 101 | + switch (this.position) { |
| 102 | + case 'before': return { overlayX: 'end', overlayY: 'center' }; |
| 103 | + case 'after': return { overlayX: 'start', overlayY: 'center' }; |
| 104 | + case 'above': return { overlayX: 'center', overlayY: 'bottom' }; |
| 105 | + case 'below': return { overlayX: 'center', overlayY: 'top' }; |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + /** |
| 110 | + * Shows the tooltip on mouse enter |
| 111 | + * @param event |
| 112 | + */ |
| 113 | + _handleMouseEnter(event: MouseEvent) { |
| 114 | + this.show(); |
| 115 | + } |
| 116 | + |
| 117 | + /** |
| 118 | + * Hides the tooltip on mouse leave |
| 119 | + * @param event |
| 120 | + */ |
| 121 | + _handleMouseLeave(event: MouseEvent) { |
| 122 | + this.hide(); |
| 123 | + } |
| 124 | + |
| 125 | + /** |
| 126 | + * Shows the tooltip and returns a promise that will resolve when the tooltip is visible |
| 127 | + */ |
| 128 | + show(): Promise<any> { |
| 129 | + if (!this.visible && this._overlayRef && !this._overlayRef.hasAttached()) { |
| 130 | + this.visible = true; |
| 131 | + let promise = this._overlayRef.attach(new ComponentPortal(TooltipComponent, |
| 132 | + this._viewContainerRef)); |
| 133 | + promise.then((ref: ComponentRef<TooltipComponent>) => { |
| 134 | + ref.instance.message = this.message; |
| 135 | + this._updatePosition(); |
| 136 | + }); |
| 137 | + return promise; |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + /** |
| 142 | + * Hides the tooltip and returns a promise that will resolve when the tooltip is hidden |
| 143 | + */ |
| 144 | + hide(): Promise<any> { |
| 145 | + if (this.visible && this._overlayRef && this._overlayRef.hasAttached()) { |
| 146 | + this.visible = false; |
| 147 | + return this._overlayRef.detach(); |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + /** |
| 152 | + * Shows/hides the tooltip and returns a promise that will resolve when it is done |
| 153 | + */ |
| 154 | + toggle(): Promise<any> { |
| 155 | + if (this.visible) { |
| 156 | + return this.hide(); |
| 157 | + } else { |
| 158 | + return this.show(); |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + /** |
| 163 | + * Updates the tooltip's position |
| 164 | + */ |
| 165 | + private _updatePosition() { |
| 166 | + if (this._overlayRef) { |
| 167 | + this._changeDetectionRef.detectChanges(); |
| 168 | + this._overlayRef.updatePosition(); |
| 169 | + } |
| 170 | + } |
| 171 | +} |
| 172 | + |
| 173 | +@Component({ |
| 174 | + moduleId: module.id, |
| 175 | + selector: 'md-tooltip-component', |
| 176 | + template: `<div class="md-tooltip">{{message}}</div>`, |
| 177 | + styleUrls: ['tooltip.css'], |
| 178 | +}) |
| 179 | +class TooltipComponent { |
| 180 | + message: string; |
| 181 | +} |
| 182 | + |
| 183 | +export const MD_TOOLTIP_DIRECTIVES = [MdTooltip]; |
0 commit comments