Merged PR 896: #2253 UiOverlayTrigger erstellt und UiTooltip angepasst

#2253 UiOverlayTrigger erstellt und UiTooltip angepasst

Related work items: #2253
This commit is contained in:
Andreas Schickinger
2021-10-07 12:08:10 +00:00
committed by Lorenz Hilpert
parent d96729fb89
commit 2c3115dc47
27 changed files with 173 additions and 359 deletions

View File

@@ -59,9 +59,9 @@
<ng-container *ngIf="!!(statusActions$ | async)?.length">
<button
class="cta-status-dropdown"
[uiDropdownTrigger]="statusDropdown"
[uiOverlayTrigger]="statusDropdown"
[disabled]="changeStatusDisabled$ | async"
#dropdown="uiDropdownTrigger"
#dropdown="uiOverlayTrigger"
>
{{ orderItem?.processingStatus | processingStatus }}
<ui-icon [rotate]="dropdown.opened ? '270deg' : '90deg'" icon="arrow_head"></ui-icon>
@@ -116,8 +116,8 @@
<div class="label">Abholfrist</div>
<div *ngIf="!(changeDateLoader$ | async)" class="value">
<button
[uiDropdownTrigger]="deadlineDropdown"
#deadlineDropdownTrigger="uiDropdownTrigger"
[uiOverlayTrigger]="deadlineDropdown"
#deadlineDropdownTrigger="uiOverlayTrigger"
[disabled]="!!orderItem?.features?.paid || (changeDateDisabled$ | async)"
class="cta-pickup-deadline"
>
@@ -141,8 +141,8 @@
<button
class="cta-datepicker"
[disabled]="changeDateDisabled$ | async"
[uiDatepickerTrigger]="uiDatepicker"
#datepicker="uiDatepickerTrigger"
[uiOverlayTrigger]="uiDatepicker"
#datepicker="uiOverlayTrigger"
>
<span>
{{ orderItem?.estimatedShippingDate | date: 'dd.MM.yy' }}

View File

@@ -64,7 +64,7 @@
<ng-template #vslLieferdatum>
<ui-form-control class="datepicker" label="vsl. Lieferdatum" variant="inline">
<button class="date-btn" type="button" [uiDatepickerTrigger]="uiDatepicker" #datepicker="uiDatepickerTrigger">
<button class="date-btn" type="button" [uiOverlayTrigger]="uiDatepicker" #datepicker="uiOverlayTrigger">
<strong>
{{ items[i]?.estimatedShippingDate | date: 'dd.MM.yy' }}
</strong>

View File

@@ -1,6 +1,7 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
import { UiDatepickerModule } from '@ui/datepicker';
import { UiDropdownModule } from '@ui/dropdown';
import { UiFormControlModule } from '@ui/form-control';
@@ -14,6 +15,7 @@ import { SharedGoodsInOutOrderEditComponent } from './goods-in-out-order-edit.co
@NgModule({
imports: [
CommonModule,
UiCommonModule,
UiFormControlModule,
UiInputModule,
UiSelectModule,

View File

@@ -4,6 +4,7 @@ import { BlobImageDirective } from './blob-image.directive';
import { UiClickOutsideDirective } from './click-outside.directive';
import { UiFocusDirective } from './focus';
import { IsInViewportDirective } from './is-in-viewport.directive';
import { UiOverlayTriggerDirective } from './overlay-trigger';
import { GroupByPipe } from './pipes/group-by.pipe';
import { ReplacePipe } from './pipes/replace.pipe';
import { StripHtmlTagsPipe } from './pipes/strip-html-tags.pipe';
@@ -11,7 +12,14 @@ import { SubstrPipe } from './pipes/substr.pipe';
import { SkeletonLoaderComponent } from './skeleton-loader';
const components = [SkeletonLoaderComponent];
const directives = [UiClickOutsideDirective, IsInViewportDirective, BlobImageDirective, UiAutofocusDirective, UiFocusDirective];
const directives = [
UiClickOutsideDirective,
IsInViewportDirective,
BlobImageDirective,
UiAutofocusDirective,
UiFocusDirective,
UiOverlayTriggerDirective,
];
const pipes = [StripHtmlTagsPipe, SubstrPipe, GroupByPipe, ReplacePipe];
@NgModule({
imports: [],

View File

@@ -6,4 +6,5 @@ export * from './date';
export * from './skeleton-loader';
export * from './defs';
export * from './focus';
export * from './overlay-trigger';
// end:ng42.barrel

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './overlay-positions';
export * from './overlay-trigger';
export * from './overlay-trigger.directive';
// end:ng42.barrel

View File

@@ -0,0 +1,3 @@
export type UiOverlayPositionX = 'before' | 'after';
export type UiOverlayPositionY = 'above' | 'below';

View File

@@ -16,24 +16,23 @@ import {
} from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { UiDropdownComponent } from './dropdown.component';
import { UiOverlayTrigger } from './overlay-trigger';
@Directive({ selector: '[uiDropdownTrigger]', exportAs: 'uiDropdownTrigger' })
export class UiDropdownTriggerDirective implements OnInit, OnDestroy, OnChanges {
@Input('uiDropdownTrigger')
dropdown: UiDropdownComponent;
@Directive({ selector: '[uiOverlayTrigger]', exportAs: 'uiOverlayTrigger' })
export class UiOverlayTriggerDirective implements OnInit, OnDestroy, OnChanges {
@Input('uiOverlayTrigger')
component: UiOverlayTrigger;
@Input()
@HostBinding('disabled')
disabled: boolean;
private overlayRef: OverlayRef;
private dropdownRef: EmbeddedViewRef<any>;
private viewRef: EmbeddedViewRef<any>;
private _onDestroy$ = new Subject<void>();
get opened() {
return !!this.dropdownRef;
return !!this.viewRef;
}
constructor(
@@ -62,7 +61,7 @@ export class UiDropdownTriggerDirective implements OnInit, OnDestroy, OnChanges
@HostListener('click')
toggle() {
if (this.dropdownRef) {
if (this.viewRef) {
this.close();
} else {
this.open();
@@ -70,27 +69,24 @@ export class UiDropdownTriggerDirective implements OnInit, OnDestroy, OnChanges
}
open() {
const dropdownPortal = new TemplatePortal(this.dropdown.templateRef, this.viewContainerRef);
const dropdownPortal = new TemplatePortal(this.component.templateRef, this.viewContainerRef);
if (!!this.dropdownRef) {
if (!!this.viewRef) {
this.close();
}
this.updatePositionStrategy();
this.dropdownRef = this.overlayRef.attach(dropdownPortal);
this.dropdown.close = () => {
this.close();
};
this.viewRef = this.overlayRef.attach(dropdownPortal);
this.component.close = () => this.close();
this.cdr.markForCheck();
}
close() {
this.dropdownRef?.destroy();
this.viewRef?.destroy();
this.overlayRef.detach();
delete this.dropdownRef;
delete this.viewRef;
this.cdr.markForCheck();
}
@@ -111,15 +107,14 @@ export class UiDropdownTriggerDirective implements OnInit, OnDestroy, OnChanges
}
getPositionStrategy() {
let [originX, originFallbackX]: HorizontalConnectionPos[] = [this.dropdown.xPosition === 'before' ? 'start' : 'end', 'start'];
let [originX, originFallbackX]: HorizontalConnectionPos[] = [this.component.xPosition === 'before' ? 'start' : 'end', 'start'];
let [originY, overlayFallbackY]: VerticalConnectionPos[] = [this.dropdown.yPosition === 'above' ? 'top' : 'bottom', 'top'];
let [originY, overlayFallbackY]: VerticalConnectionPos[] = [this.component.yPosition === 'above' ? 'top' : 'bottom', 'top'];
let [overlayY, originFallbackY]: VerticalConnectionPos[] = [originY === 'bottom' ? 'top' : 'bottom', overlayFallbackY];
let [overlayX, overlayFallbackX] = [originX, originFallbackX];
let offsetY = this.dropdown.yOffset ?? 0;
let offsetX = this.dropdown.xOffset ?? 0;
let offsetY = this.component.yOffset ?? 0;
let offsetX = this.component.xOffset ?? 0;
return this.overlayPositionBuilder.flexibleConnectedTo(this.elementRef).withPositions([
{ originX, originY, overlayX, overlayY, offsetY, offsetX },

View File

@@ -0,0 +1,18 @@
import { ElementRef, TemplateRef } from '@angular/core';
import { UiOverlayPositionX, UiOverlayPositionY } from './overlay-positions';
export interface UiOverlayTrigger {
templateRef: TemplateRef<any>;
content: ElementRef;
xPosition: UiOverlayPositionX;
yPosition: UiOverlayPositionY;
xOffset: number;
yOffset: number;
close: () => void;
}

View File

@@ -1,148 +0,0 @@
import {
ConnectedPosition,
HorizontalConnectionPos,
Overlay,
OverlayPositionBuilder,
OverlayRef,
VerticalConnectionPos,
} from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import {
ChangeDetectorRef,
ComponentRef,
Directive,
ElementRef,
EmbeddedViewRef,
HostBinding,
HostListener,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChanges,
TemplateRef,
ViewContainerRef,
} from '@angular/core';
import { Subscription } from 'rxjs';
import { UiDatepickerComponent } from './datepicker.component';
@Directive({ selector: '[uiDatepickerTrigger]', exportAs: 'uiDatepickerTrigger' })
export class UiDatepickerTriggerDirective implements OnInit, OnDestroy, OnChanges {
@Input('uiDatepickerTrigger')
datepicker: UiDatepickerComponent;
@Input()
@HostBinding('disabled')
disabled: boolean;
private overlayRef: OverlayRef;
private datepickerRef: EmbeddedViewRef<any>;
private subscription = new Subscription();
get opened() {
return !!this.datepickerRef;
}
constructor(
private overlayPositionBuilder: OverlayPositionBuilder,
private viewContainerRef: ViewContainerRef,
private elementRef: ElementRef,
private overlay: Overlay,
private cdr: ChangeDetectorRef
) {}
ngOnChanges({ position }: SimpleChanges): void {
if (position) {
this.updatePosition();
}
}
ngOnInit() {
this.createOverlay();
}
ngOnDestroy() {
this.overlayRef.dispose();
this.subscription.unsubscribe();
}
@HostListener('click')
toggle() {
if (this.datepickerRef) {
this.close();
} else {
this.open();
}
}
open() {
const dropdownPortal = new TemplatePortal(this.datepicker.templateRef, this.viewContainerRef);
if (!!this.datepickerRef) {
this.close();
}
this.updatePositionStrategy();
this.datepickerRef = this.overlayRef.attach(dropdownPortal);
this.datepicker.close = () => this.close();
this.cdr.markForCheck();
}
close() {
this.datepickerRef?.destroy();
this.overlayRef.detach();
delete this.datepickerRef;
this.cdr.markForCheck();
}
createOverlay() {
this.overlayRef = this.overlay.create({
positionStrategy: this.getPositionStrategy(),
hasBackdrop: true,
backdropClass: 'backdrop-transparent',
});
this.subscription.add(this.overlayRef.backdropClick().subscribe(() => this.close()));
}
updatePositionStrategy() {
this.overlayRef.updatePositionStrategy(this.getPositionStrategy());
}
getPositionStrategy() {
let [originX, originFallbackX]: HorizontalConnectionPos[] = [this.datepicker.xPosition === 'before' ? 'start' : 'end', 'start'];
let [originY, overlayFallbackY]: VerticalConnectionPos[] = [this.datepicker.yPosition === 'above' ? 'top' : 'bottom', 'top'];
let [overlayY, originFallbackY]: VerticalConnectionPos[] = [originY === 'bottom' ? 'top' : 'bottom', overlayFallbackY];
let [overlayX, overlayFallbackX] = [originX, originFallbackX];
let offsetY = this.datepicker.yOffset ?? 0;
let offsetX = this.datepicker.xOffset ?? 0;
return this.overlayPositionBuilder.flexibleConnectedTo(this.elementRef).withPositions([
{ originX, originY, overlayX, overlayY, offsetY, offsetX },
{ originX: originFallbackX, originY, overlayX: overlayFallbackX, overlayY, offsetY, offsetX },
{
originX,
originY: originFallbackY,
overlayX,
overlayY: overlayFallbackY,
offsetY: -offsetY,
offsetX: -offsetX,
},
{
originX: originFallbackX,
originY: originFallbackY,
overlayX: overlayFallbackX,
overlayY: overlayFallbackY,
offsetY: -offsetY,
offsetX: -offsetX,
},
]);
}
updatePosition() {
this.overlayRef?.updatePositionStrategy(this.getPositionStrategy());
}
}

View File

@@ -13,7 +13,7 @@ import {
import { Datepicker } from './datepicker';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subscription } from 'rxjs';
import { DateAdapter } from '@ui/common';
import { DateAdapter, UiOverlayTrigger } from '@ui/common';
import { DatepickerPositionX, DatepickerPositionY } from './datepicker-positions';
@Component({
@@ -27,7 +27,7 @@ import { DatepickerPositionX, DatepickerPositionY } from './datepicker-positions
],
exportAs: 'uiDatepicker',
})
export class UiDatepickerComponent extends Datepicker implements ControlValueAccessor, OnInit, OnDestroy {
export class UiDatepickerComponent extends Datepicker implements UiOverlayTrigger, ControlValueAccessor, OnInit, OnDestroy {
@ViewChild(TemplateRef) templateRef: TemplateRef<any>;
@ContentChild('content') content: ElementRef;

View File

@@ -5,19 +5,11 @@ import { CommonModule } from '@angular/common';
import { UiDatepickerBodyComponent, UiDatepickerCellDirective, GetCellNamePipe } from './body';
import { UiDatepickerHeaderComponent } from './header';
import { IconModule } from '@libs/ui';
import { UiDatepickerTriggerDirective } from './datepicker-trigger.directive';
@NgModule({
imports: [CommonModule, IconModule],
exports: [UiDatepickerComponent, UiDatepickerTriggerDirective],
declarations: [
UiDatepickerComponent,
UiDatepickerBodyComponent,
UiDatepickerHeaderComponent,
UiDatepickerCellDirective,
GetCellNamePipe,
UiDatepickerTriggerDirective,
],
exports: [UiDatepickerComponent],
declarations: [UiDatepickerComponent, UiDatepickerBodyComponent, UiDatepickerHeaderComponent, UiDatepickerCellDirective, GetCellNamePipe],
providers: [],
})
export class UiDatepickerModule {}

View File

@@ -1,4 +1,5 @@
import { Component, Input, OnInit, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
import { Component, ElementRef, Input, OnInit, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
import { UiOverlayTrigger } from '@ui/common';
import { DropdownPositionX, DropdownPositionY } from './dropdown-positions';
@Component({
@@ -8,9 +9,11 @@ import { DropdownPositionX, DropdownPositionY } from './dropdown-positions';
encapsulation: ViewEncapsulation.None,
styleUrls: ['./dropdown.scss'],
})
export class UiDropdownComponent implements OnInit {
export class UiDropdownComponent implements UiOverlayTrigger, OnInit {
@ViewChild(TemplateRef) templateRef: TemplateRef<any>;
content: ElementRef<any>;
@Input()
xPosition: DropdownPositionX;

View File

@@ -1,12 +1,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { UiDropdownItemDirective } from './dropdown-item.directive';
import { UiDropdownTriggerDirective } from './dropdown-trigger.directive';
import { UiDropdownComponent } from './dropdown.component';
@NgModule({
declarations: [UiDropdownComponent, UiDropdownItemDirective, UiDropdownTriggerDirective],
declarations: [UiDropdownComponent, UiDropdownItemDirective],
imports: [CommonModule],
exports: [UiDropdownComponent, UiDropdownItemDirective, UiDropdownTriggerDirective],
exports: [UiDropdownComponent, UiDropdownItemDirective],
})
export class UiDropdownModule {}

View File

@@ -1,6 +1,5 @@
// start:ng42.barrel
export * from './dropdown-item.directive';
export * from './dropdown-trigger.directive';
export * from './dropdown.component';
export * from './dropdown.module';
// end:ng42.barrel

View File

@@ -16,15 +16,10 @@
</ui-autocomplete>
</ui-searchbox>
<ng-container *ngIf="showDescription && uiInput?.description">
<button
[uiTooltip]="tooltipContent"
[uiTooltipPosition]="{ offsetX: 20, offsetY: -20, originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom' }"
class="info-tooltip-button"
type="button"
>
<button [uiOverlayTrigger]="tooltipContent" #tooltip="uiOverlayTrigger" class="info-tooltip-button" type="button">
i
</button>
<ng-template #tooltipContent>
<ui-tooltip #tooltipContent yPosition="above" xPosition="after" [yOffset]="-16">
{{ uiInput.description }}
</ng-template>
</ui-tooltip>
</ng-container>

View File

@@ -5,9 +5,10 @@ import { UiFilterInputGroupMainComponent } from './filter-input-group-main.compo
import { UiAutocompleteModule } from '@ui/autocomplete';
import { UiSearchboxNextModule } from '@ui/searchbox';
import { UiTooltipModule } from 'apps/ui/tooltip/src/public-api';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [CommonModule, UiSearchboxNextModule, UiAutocompleteModule, UiTooltipModule],
imports: [CommonModule, UiCommonModule, UiSearchboxNextModule, UiAutocompleteModule, UiTooltipModule],
exports: [UiFilterInputGroupMainComponent],
declarations: [UiFilterInputGroupMainComponent],
})

View File

@@ -5,8 +5,8 @@
<button
class="cta-picker"
[class.open]="dpStartTrigger?.opened"
[uiDatepickerTrigger]="dpStart"
#dpStartTrigger="uiDatepickerTrigger"
[uiOverlayTrigger]="dpStart"
#dpStartTrigger="uiOverlayTrigger"
type="button"
>
<span>
@@ -32,8 +32,8 @@
<button
class="cta-picker"
[class.open]="dpStopTrigger?.opened"
[uiDatepickerTrigger]="dpStop"
#dpStopTrigger="uiDatepickerTrigger"
[uiOverlayTrigger]="dpStop"
#dpStopTrigger="uiOverlayTrigger"
type="button"
>
<span>

View File

@@ -1,5 +0,0 @@
.tooltip-caret {
@apply relative bg-dark-cerulean origin-center transform rotate-45;
width: 20px;
height: 20px;
}

View File

@@ -1,13 +0,0 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
@Component({
selector: 'ui-tooltip-caret',
template: '<div class="tooltip-caret"></div>',
styleUrls: ['tooltip-caret.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TooltipCaretComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

View File

@@ -0,0 +1,10 @@
<ng-template>
<div [class]="classList">
<svg viewBox="0 25 50 25" class="triangle">
<polygon points="0,50 50,50 25,25"></polygon>
</svg>
<div class="tooltip-box">
<p><ng-content></ng-content></p>
</div>
</div>
</ng-template>

View File

@@ -7,3 +7,53 @@
@apply m-0 text-white font-bold text-base;
}
}
.ui-tooltip-panel {
.triangle {
width: 30px;
z-index: -1;
polygon {
fill: #1f466c;
}
}
/* triangle top right*/
&.x-position-after.y-position-below {
margin-top: 3px;
.triangle {
@apply absolute;
top: -10px;
right: 2px;
}
}
/* triangle top left*/
&.x-position-before.y-position-below {
margin-top: 3px;
.triangle {
@apply absolute;
top: -10px;
left: 2px;
}
}
/* triangle bottom left*/
&.x-position-before.y-position-above {
margin-bottom: 3px;
.triangle {
@apply absolute transform rotate-180 origin-center;
bottom: -10px;
left: 2px;
}
}
/* triangle bottom right*/
&.x-position-after.y-position-above {
margin-bottom: 3px;
.triangle {
@apply absolute transform rotate-180 origin-center;
bottom: -10px;
right: 2px;
}
}
}

View File

@@ -1,16 +1,38 @@
import { ChangeDetectionStrategy, Component, Input, OnInit, TemplateRef } from '@angular/core';
import { ChangeDetectionStrategy, Component, ContentChild, ElementRef, Input, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { UiOverlayPositionX, UiOverlayPositionY, UiOverlayTrigger } from '@ui/common';
@Component({
selector: 'ui-tooltip',
template: `<div class="tooltip-box">
<p><ng-template *ngTemplateOutlet="content"></ng-template></p>
</div>`,
templateUrl: 'tooltip.component.html',
styleUrls: ['tooltip.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TooltipComponent implements OnInit {
@Input() content: string | TemplateRef<any> = '';
export class TooltipComponent implements UiOverlayTrigger, OnInit {
constructor() {}
@ViewChild(TemplateRef)
templateRef: TemplateRef<any>;
@ContentChild('content')
content: ElementRef<any>;
@Input()
xPosition: UiOverlayPositionX;
@Input()
yPosition: UiOverlayPositionY;
@Input()
xOffset: number;
@Input()
yOffset: number;
close: () => void;
get classList() {
return ['ui-tooltip-panel', `x-position-${this.xPosition}`, `y-position-${this.yPosition}`];
}
ngOnInit(): void {}
}

View File

@@ -1,51 +0,0 @@
import { Overlay, OverlayModule } from '@angular/cdk/overlay';
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator';
import { TooltipDirective } from './tooltip.directive';
describe('TooltipDirective', () => {
let spectator: SpectatorDirective<TooltipDirective>;
const createDirective = createDirectiveFactory({
directive: TooltipDirective,
imports: [OverlayModule],
});
beforeEach(() => {
spectator = createDirective(
`<div uiTooltip="Unit Test" [uiTooltipPosition]="{ offsetX: 0, offsetY: 0, originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom' }">Testing Tooltip</div>`
);
});
it('should get the instance', () => {
expect(spectator.directive).toBeDefined();
});
it('should call overlayRef.attach and caretOverlayRef.attach on mouseenter', () => {
const overlayRef = spectator.directive['overlayRef'];
const caretOverlayRef = spectator.directive['caretOverlayRef'];
spyOn(overlayRef, 'attach');
spyOn(caretOverlayRef, 'attach');
spectator.dispatchMouseEvent(spectator.element, 'mouseenter');
expect(overlayRef.attach).toHaveBeenCalled();
expect(caretOverlayRef.attach).toHaveBeenCalled();
});
it('should call overlayRef.detach and caretOverlayRef.detach on mouseout', () => {
const overlayRef = spectator.directive['overlayRef'];
const caretOverlayRef = spectator.directive['caretOverlayRef'];
spyOn(overlayRef, 'detach');
spyOn(caretOverlayRef, 'detach');
spectator.dispatchMouseEvent(spectator.element, 'mouseout');
expect(overlayRef.detach).toHaveBeenCalled();
expect(caretOverlayRef.detach).toHaveBeenCalled();
});
describe('ngOnInit', () => {
it('should create overlay and caretOverlay', () => {
const overlay = spectator.inject(Overlay);
const overlaySpy = spyOn(overlay, 'create');
spectator.directive.ngOnInit();
expect(overlaySpy).toHaveBeenCalledTimes(2);
});
});
});

View File

@@ -1,68 +0,0 @@
import { ConnectedPosition, Overlay, OverlayPositionBuilder, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { ComponentRef, Directive, ElementRef, HostListener, Input, OnInit, TemplateRef } from '@angular/core';
import { TooltipCaretComponent } from './tooltip-caret.component';
import { TooltipComponent } from './tooltip.component';
@Directive({ selector: '[uiTooltip]' })
export class TooltipDirective implements OnInit {
@Input('uiTooltip') content: string | TemplateRef<any>;
@Input('uiTooltipPosition') position: ConnectedPosition = {
offsetX: 0,
offsetY: 0,
originX: 'center',
originY: 'top',
overlayX: 'center',
overlayY: 'bottom',
};
private overlayRef: OverlayRef;
private caretOverlayRef: OverlayRef;
constructor(private overlayPositionBuilder: OverlayPositionBuilder, private elementRef: ElementRef, private overlay: Overlay) {}
@HostListener('mouseenter')
show() {
const tooltipPortal = new ComponentPortal(TooltipComponent);
const tooltipCaretPortal = new ComponentPortal(TooltipCaretComponent);
this.caretOverlayRef.attach(tooltipCaretPortal);
const tooltipRef: ComponentRef<TooltipComponent> = this.overlayRef.attach(tooltipPortal);
tooltipRef.instance.content = this.content;
}
@HostListener('mouseout')
hide() {
this.overlayRef.detach();
this.caretOverlayRef.detach();
}
ngOnInit() {
const positionStrategy = this.overlayPositionBuilder.flexibleConnectedTo(this.elementRef).withPositions([
{
offsetX: this.position.offsetX,
offsetY: this.position.offsetY,
originX: this.position.originX,
originY: this.position.originY,
overlayX: this.position.overlayX,
overlayY: this.position.overlayY,
},
]);
const caretPositionStrategy = this.overlayPositionBuilder.flexibleConnectedTo(this.elementRef).withPositions([
{
offsetX: 0,
offsetY: this.position.offsetY * 0.5,
originX: 'center',
originY: this.position.originY,
overlayX: 'center',
overlayY: this.position.overlayY,
},
]);
this.overlayRef = this.overlay.create({ positionStrategy });
this.caretOverlayRef = this.overlay.create({ positionStrategy: caretPositionStrategy });
}
}

View File

@@ -1,13 +1,11 @@
import { NgModule } from '@angular/core';
import { TooltipComponent } from './tooltip.component';
import { TooltipDirective } from './tooltip.directive';
import { OverlayModule } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common';
import { TooltipCaretComponent } from './tooltip-caret.component';
@NgModule({
declarations: [TooltipComponent, TooltipDirective, TooltipCaretComponent],
declarations: [TooltipComponent],
imports: [CommonModule, OverlayModule],
exports: [TooltipComponent, TooltipDirective, TooltipCaretComponent],
exports: [TooltipComponent],
})
export class UiTooltipModule {}

View File

@@ -2,7 +2,5 @@
* Public API Surface of tooltip
*/
export * from './lib/tooltip.directive';
export * from './lib/tooltip.component';
export * from './lib/tooltip-caret.component';
export * from './lib/tooltip.module';