Skip to content

Commit 052675d

Browse files
committed
feat(tooltip): reference input for positioning the tooltip on reference element, refactor with signals
1 parent bf44bd0 commit 052675d

File tree

3 files changed

+82
-79
lines changed

3 files changed

+82
-79
lines changed

projects/coreui-angular/src/lib/popover/popover.directive.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ChangeDetectorRef, ElementRef, Renderer2, ViewContainerRef } from '@angular/core';
2+
import { TestBed } from '@angular/core/testing';
23
import { IntersectionService, ListenersService } from '../services';
34
import { PopoverDirective } from './popover.directive';
4-
import { TestBed } from '@angular/core/testing';
55

66
describe('PopoverDirective', () => {
77
let document: Document;
@@ -11,11 +11,11 @@ describe('PopoverDirective', () => {
1111
let changeDetectorRef: ChangeDetectorRef;
1212

1313
it('should create an instance', () => {
14-
const listenersService = new ListenersService(renderer);
1514
TestBed.configureTestingModule({
16-
providers: [IntersectionService]
15+
providers: [IntersectionService, Renderer2, ListenersService],
1716
});
1817
const intersectionService = TestBed.inject(IntersectionService);
18+
const listenersService = TestBed.inject(ListenersService);
1919
TestBed.runInInjectionContext(() => {
2020
const directive = new PopoverDirective(
2121
document,
@@ -24,7 +24,7 @@ describe('PopoverDirective', () => {
2424
viewContainerRef,
2525
listenersService,
2626
changeDetectorRef,
27-
intersectionService
27+
intersectionService,
2828
);
2929
expect(directive).toBeTruthy();
3030
});

projects/coreui-angular/src/lib/tooltip/tooltip.directive.spec.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ describe('TooltipDirective', () => {
1111
let changeDetectorRef: ChangeDetectorRef;
1212

1313
it('should create an instance', () => {
14-
const listenersService = new ListenersService(renderer);
1514
TestBed.configureTestingModule({
16-
providers: [IntersectionService]
15+
providers: [IntersectionService, Renderer2, ListenersService],
1716
});
1817
const intersectionService = TestBed.inject(IntersectionService);
18+
const listenersService = TestBed.inject(ListenersService);
1919
TestBed.runInInjectionContext(() => {
2020
const directive = new TooltipDirective(
2121
document,
@@ -24,10 +24,9 @@ describe('TooltipDirective', () => {
2424
viewContainerRef,
2525
listenersService,
2626
changeDetectorRef,
27-
intersectionService
27+
intersectionService,
2828
);
2929
expect(directive).toBeTruthy();
3030
});
31-
3231
});
3332
});

projects/coreui-angular/src/lib/tooltip/tooltip.directive.ts

Lines changed: 75 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
AfterViewInit,
33
ChangeDetectorRef,
44
ComponentRef,
5+
computed,
56
DestroyRef,
67
Directive,
78
effect,
@@ -10,75 +11,95 @@ import {
1011
inject,
1112
Inject,
1213
input,
13-
Input,
14-
OnChanges,
14+
model,
1515
OnDestroy,
1616
OnInit,
1717
Renderer2,
18-
SimpleChanges,
1918
TemplateRef,
20-
ViewContainerRef
19+
ViewContainerRef,
2120
} from '@angular/core';
22-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2321
import { DOCUMENT } from '@angular/common';
24-
import { debounceTime, filter, finalize } from 'rxjs/operators';
2522
import { createPopper, Instance, Options } from '@popperjs/core';
2623

2724
import { Triggers } from '../coreui.types';
2825
import { TooltipComponent } from './tooltip/tooltip.component';
29-
import { IListenersConfig, ListenersService } from '../services/listeners.service';
30-
import { IntersectionService } from '../services';
26+
import { IListenersConfig, IntersectionService, ListenersService } from '../services';
27+
import { debounceTime, filter, finalize } from 'rxjs/operators';
28+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
29+
import { ElementRefDirective } from '../shared';
3130

3231
@Directive({
3332
selector: '[cTooltip]',
3433
exportAs: 'cTooltip',
3534
providers: [ListenersService, IntersectionService],
36-
standalone: true
35+
standalone: true,
3736
})
38-
export class TooltipDirective implements OnChanges, OnDestroy, OnInit, AfterViewInit {
39-
37+
export class TooltipDirective implements OnDestroy, OnInit, AfterViewInit {
4038
/**
4139
* Content of tooltip
4240
* @type {string | TemplateRef}
4341
*/
44-
readonly content = input<string | TemplateRef<any>>('', { alias: 'cTooltip' });
42+
readonly content = input<string | TemplateRef<any> | undefined>(undefined, { alias: 'cTooltip' });
43+
44+
contentEffect = effect(() => {
45+
if (this.content()) {
46+
this.destroyTooltipElement();
47+
}
48+
});
4549

4650
/**
4751
* Optional popper Options object, takes precedence over cPopoverPlacement prop
4852
* @type Partial<Options>
4953
*/
50-
@Input('cTooltipOptions')
51-
set popperOptions(value: Partial<Options>) {
52-
this._popperOptions = { ...this._popperOptions, placement: this.placement, ...value };
53-
};
54+
readonly popperOptions = input<Partial<Options>>({}, { alias: 'cTooltipOptions' });
5455

55-
get popperOptions(): Partial<Options> {
56-
return { placement: this.placement, ...this._popperOptions };
57-
}
56+
popperOptionsEffect = effect(() => {
57+
this._popperOptions = {
58+
...this._popperOptions,
59+
placement: this.placement(),
60+
...this.popperOptions(),
61+
};
62+
});
63+
64+
popperOptionsComputed = computed(() => {
65+
return { placement: this.placement(), ...this._popperOptions };
66+
});
5867

5968
/**
6069
* Describes the placement of your component after Popper.js has applied all the modifiers that may have flipped or altered the originally provided placement property.
70+
* @type: 'top' | 'bottom' | 'left' | 'right'
71+
* @default: 'top'
72+
*/
73+
readonly placement = input<'top' | 'bottom' | 'left' | 'right'>('top', {
74+
alias: 'cTooltipPlacement',
75+
});
76+
77+
/**
78+
* ElementRefDirective for positioning the tooltip on reference element
79+
* @type: ElementRefDirective
80+
* @default: undefined
6181
*/
62-
@Input('cTooltipPlacement') placement: 'top' | 'bottom' | 'left' | 'right' = 'top';
82+
readonly reference = input<ElementRefDirective | undefined>(undefined, {
83+
alias: 'cTooltipRef',
84+
});
85+
86+
readonly referenceRef = computed(() => this.reference()?.elementRef ?? this.hostElement);
87+
6388
/**
6489
* Sets which event handlers you’d like provided to your toggle prop. You can specify one trigger or an array of them.
65-
* @type {'hover' | 'focus' | 'click'}
90+
* @type: 'Triggers | Triggers[]
6691
*/
67-
@Input('cTooltipTrigger') trigger: Triggers | Triggers[] = 'hover';
92+
readonly trigger = input<Triggers | Triggers[]>('hover', { alias: 'cTooltipTrigger' });
6893

6994
/**
7095
* Toggle the visibility of tooltip component.
96+
* @type boolean
7197
*/
72-
@Input('cTooltipVisible')
73-
set visible(value: boolean) {
74-
this._visible = value;
75-
}
98+
readonly visible = model(false, { alias: 'cTooltipVisible' });
7699

77-
get visible() {
78-
return this._visible;
79-
}
80-
81-
private _visible = false;
100+
visibleEffect = effect(() => {
101+
this.visible() ? this.addTooltipElement() : this.removeTooltipElement();
102+
});
82103

83104
@HostBinding('attr.aria-describedby') get ariaDescribedBy(): string | null {
84105
return this.tooltipId ? this.tooltipId : null;
@@ -94,10 +115,10 @@ export class TooltipDirective implements OnChanges, OnDestroy, OnInit, AfterView
94115
{
95116
name: 'offset',
96117
options: {
97-
offset: [0, 5]
98-
}
99-
}
100-
]
118+
offset: [0, 5],
119+
},
120+
},
121+
],
101122
};
102123

103124
readonly #destroyRef = inject(DestroyRef);
@@ -109,24 +130,13 @@ export class TooltipDirective implements OnChanges, OnDestroy, OnInit, AfterView
109130
private viewContainerRef: ViewContainerRef,
110131
private listenersService: ListenersService,
111132
private changeDetectorRef: ChangeDetectorRef,
112-
private intersectionService: IntersectionService
133+
private intersectionService: IntersectionService,
113134
) {}
114135

115-
contentEffect = effect(() => {
116-
this.destroyTooltipElement();
117-
this.content() ? this.addTooltipElement() : this.removeTooltipElement();
118-
});
119-
120136
ngAfterViewInit(): void {
121137
this.intersectionServiceSubscribe();
122138
}
123139

124-
ngOnChanges(changes: SimpleChanges): void {
125-
if (changes['visible']) {
126-
changes['visible'].currentValue ? this.addTooltipElement() : this.removeTooltipElement();
127-
}
128-
}
129-
130140
ngOnDestroy(): void {
131141
this.clearListeners();
132142
this.destroyTooltipElement();
@@ -139,19 +149,16 @@ export class TooltipDirective implements OnChanges, OnDestroy, OnInit, AfterView
139149
private setListeners(): void {
140150
const config: IListenersConfig = {
141151
hostElement: this.hostElement,
142-
trigger: this.trigger,
152+
trigger: this.trigger(),
143153
callbackToggle: () => {
144-
this.visible = !this.visible;
145-
this.visible ? this.addTooltipElement() : this.removeTooltipElement();
154+
this.visible.set(!this.visible());
146155
},
147156
callbackOff: () => {
148-
this.visible = false;
149-
this.removeTooltipElement();
157+
this.visible.set(false);
150158
},
151159
callbackOn: () => {
152-
this.visible = true;
153-
this.addTooltipElement();
154-
}
160+
this.visible.set(true);
161+
},
155162
};
156163
this.listenersService.setListeners(config);
157164
}
@@ -161,19 +168,18 @@ export class TooltipDirective implements OnChanges, OnDestroy, OnInit, AfterView
161168
}
162169

163170
private intersectionServiceSubscribe(): void {
164-
this.intersectionService.createIntersectionObserver(this.hostElement);
171+
this.intersectionService.createIntersectionObserver(this.referenceRef());
165172
this.intersectionService.intersecting$
166173
.pipe(
167-
filter(next => next.hostElement === this.hostElement),
174+
filter((next) => next.hostElement === this.referenceRef()),
168175
debounceTime(100),
169176
finalize(() => {
170-
this.intersectionService.unobserve(this.hostElement);
177+
this.intersectionService.unobserve(this.referenceRef());
171178
}),
172-
takeUntilDestroyed(this.#destroyRef)
179+
takeUntilDestroyed(this.#destroyRef),
173180
)
174-
.subscribe(next => {
175-
this.visible = next.isIntersecting ? this.visible : false;
176-
!this.visible && this.removeTooltipElement();
181+
.subscribe((next) => {
182+
this.visible.set(next.isIntersecting ? this.visible() : false);
177183
});
178184
}
179185

@@ -205,6 +211,7 @@ export class TooltipDirective implements OnChanges, OnDestroy, OnInit, AfterView
205211

206212
private addTooltipElement(): void {
207213
if (!this.content()) {
214+
this.destroyTooltipElement();
208215
return;
209216
}
210217

@@ -214,7 +221,7 @@ export class TooltipDirective implements OnChanges, OnDestroy, OnInit, AfterView
214221

215222
this.tooltipId = this.getUID('tooltip');
216223
this.tooltipRef.instance.id = this.tooltipId;
217-
this.tooltipRef.instance.content = this.content();
224+
this.tooltipRef.instance.content = this.content() ?? '';
218225

219226
this.tooltip = this.tooltipRef.location.nativeElement;
220227
this.renderer.addClass(this.tooltip, 'd-none');
@@ -225,24 +232,21 @@ export class TooltipDirective implements OnChanges, OnDestroy, OnInit, AfterView
225232
this.viewContainerRef.insert(this.tooltipRef.hostView);
226233
this.renderer.appendChild(this.document.body, this.tooltip);
227234

228-
this.popperInstance = createPopper(
229-
this.hostElement.nativeElement,
230-
this.tooltip,
231-
{ ...this.popperOptions }
232-
);
233-
if (!this.visible) {
235+
this.popperInstance = createPopper(this.referenceRef().nativeElement, this.tooltip, {
236+
...this.popperOptionsComputed(),
237+
});
238+
if (!this.visible()) {
234239
this.removeTooltipElement();
235240
return;
236241
}
237242
this.renderer.removeClass(this.tooltip, 'd-none');
238243
this.changeDetectorRef.markForCheck();
239244

240245
setTimeout(() => {
241-
this.tooltipRef && (this.tooltipRef.instance.visible = this.visible);
246+
this.tooltipRef && (this.tooltipRef.instance.visible = this.visible());
242247
this.popperInstance?.forceUpdate();
243248
this.changeDetectorRef?.markForCheck();
244249
}, 100);
245-
246250
}
247251

248252
private removeTooltipElement(): void {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy