1+ import { DOCUMENT } from '@angular/common' ;
12import {
23 AfterViewInit ,
34 ChangeDetectorRef ,
45 ComponentRef ,
6+ computed ,
57 DestroyRef ,
68 Directive ,
9+ effect ,
710 ElementRef ,
8- HostBinding ,
911 inject ,
10- Inject ,
11- Input ,
12- OnChanges ,
12+ input ,
13+ model ,
1314 OnDestroy ,
1415 OnInit ,
1516 Renderer2 ,
16- SimpleChanges ,
1717 TemplateRef ,
1818 ViewContainerRef
1919} from '@angular/core' ;
2020import { takeUntilDestroyed } from '@angular/core/rxjs-interop' ;
21- import { DOCUMENT } from '@angular/common' ;
2221import { debounceTime , filter , finalize } from 'rxjs/operators' ;
2322import { createPopper , Instance , Options } from '@popperjs/core' ;
2423
2524import { Triggers } from '../coreui.types' ;
25+ import { IListenersConfig , IntersectionService , ListenersService } from '../services' ;
26+ import { ElementRefDirective } from '../shared' ;
2627import { PopoverComponent } from './popover/popover.component' ;
27- import { IListenersConfig , ListenersService } from '../services/listeners.service' ;
28- import { IntersectionService } from '../services' ;
2928
3029@Directive ( {
3130 selector : '[cPopover]' ,
3231 exportAs : 'cPopover' ,
33- providers : [ ListenersService ] ,
34- standalone : true
32+ providers : [ ListenersService , IntersectionService ] ,
33+ standalone : true ,
34+ host : { '[attr.aria-describedby]' : 'ariaDescribedBy' }
3535} )
36- export class PopoverDirective implements OnChanges , OnDestroy , OnInit , AfterViewInit {
37-
36+ export class PopoverDirective implements OnDestroy , OnInit , AfterViewInit {
3837 /**
3938 * Content of popover
4039 * @type {string | TemplateRef }
4140 */
42- @Input ( 'cPopover' ) content : string | TemplateRef < any > = '' ;
41+ readonly content = input < string | TemplateRef < any > | undefined > ( undefined , { alias : 'cPopover' } ) ;
42+
43+ contentEffect = effect ( ( ) => {
44+ if ( this . content ( ) ) {
45+ this . destroyTooltipElement ( ) ;
46+ }
47+ } ) ;
4348
4449 /**
4550 * Optional popper Options object, takes precedence over cPopoverPlacement prop
4651 * @type Partial<Options>
4752 */
48- @Input ( 'cPopoverOptions' )
49- set popperOptions ( value : Partial < Options > ) {
50- this . _popperOptions = { ...this . _popperOptions , placement : this . placement , ...value } ;
51- } ;
53+ readonly popperOptions = input < Partial < Options > > ( { } , { alias : 'cPopoverOptions' } ) ;
5254
53- get popperOptions ( ) : Partial < Options > {
54- return { placement : this . placement , ...this . _popperOptions } ;
55- }
55+ popperOptionsEffect = effect ( ( ) => {
56+ this . _popperOptions = {
57+ ...this . _popperOptions ,
58+ placement : this . placement ( ) ,
59+ ...this . popperOptions ( )
60+ } ;
61+ } ) ;
62+
63+ popperOptionsComputed = computed ( ( ) => {
64+ return { placement : this . placement ( ) , ...this . _popperOptions } ;
65+ } ) ;
5666
5767 /**
5868 * 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.
69+ * @type : 'top' | 'bottom' | 'left' | 'right'
70+ * @default : 'top'
5971 */
60- @Input ( 'cPopoverPlacement' ) placement : 'top' | 'bottom' | 'left' | 'right' = 'top' ;
72+ readonly placement = input < 'top' | 'bottom' | 'left' | 'right' > ( 'top' , { alias : 'cPopoverPlacement' } ) ;
73+
74+ /**
75+ * ElementRefDirective for positioning the tooltip on reference element
76+ * @type : ElementRefDirective
77+ * @default : undefined
78+ */
79+ readonly reference = input < ElementRefDirective | undefined > ( undefined , { alias : 'cTooltipRef' } ) ;
80+
81+ readonly referenceRef = computed ( ( ) => this . reference ( ) ?. elementRef ?? this . hostElement ) ;
82+
6183 /**
6284 * Sets which event handlers you’d like provided to your toggle prop. You can specify one trigger or an array of them.
63- * @type { 'hover' | 'focus' | 'click' }
85+ * @type : 'Triggers | Triggers[]
6486 */
65- @ Input ( 'cPopoverTrigger' ) trigger ?: Triggers | Triggers [ ] = 'hover' ;
87+ readonly trigger = input < Triggers | Triggers [ ] > ( 'hover' , { alias : 'cPopoverTrigger' } ) ;
6688
6789 /**
6890 * Toggle the visibility of popover component.
91+ * @type boolean
6992 */
70- @Input ( 'cPopoverVisible' )
71- set visible ( value : boolean ) {
72- this . _visible = value ;
73- }
74-
75- get visible ( ) {
76- return this . _visible ;
77- }
93+ readonly visible = model ( false , { alias : 'cPopoverVisible' } ) ;
7894
79- private _visible = false ;
95+ visibleEffect = effect ( ( ) => {
96+ this . visible ( ) ? this . addTooltipElement ( ) : this . removeTooltipElement ( ) ;
97+ } ) ;
8098
81- @ HostBinding ( 'attr.aria-describedby' ) get ariaDescribedBy ( ) : string | null {
82- return this . popoverId ? this . popoverId : null ;
99+ get ariaDescribedBy ( ) : string | null {
100+ return this . tooltipId ? this . tooltipId : null ;
83101 }
84102
85- private popover ! : HTMLDivElement ;
86- private popoverId ! : string ;
87- private popoverRef ! : ComponentRef < PopoverComponent > ;
103+ private tooltip ! : HTMLDivElement ;
104+ private tooltipId ! : string ;
105+ private tooltipRef ! : ComponentRef < PopoverComponent > ;
88106 private popperInstance ! : Instance ;
89107
90108 private _popperOptions : Partial < Options > = {
@@ -99,9 +117,9 @@ export class PopoverDirective implements OnChanges, OnDestroy, OnInit, AfterView
99117 } ;
100118
101119 readonly #destroyRef = inject ( DestroyRef ) ;
120+ readonly #document = inject ( DOCUMENT ) ;
102121
103122 constructor (
104- @Inject ( DOCUMENT ) private document : Document ,
105123 private renderer : Renderer2 ,
106124 private hostElement : ElementRef ,
107125 private viewContainerRef : ViewContainerRef ,
@@ -114,15 +132,9 @@ export class PopoverDirective implements OnChanges, OnDestroy, OnInit, AfterView
114132 this . intersectionServiceSubscribe ( ) ;
115133 }
116134
117- ngOnChanges ( changes : SimpleChanges ) : void {
118- if ( changes [ 'visible' ] ) {
119- changes [ 'visible' ] . currentValue ? this . addPopoverElement ( ) : this . removePopoverElement ( ) ;
120- }
121- }
122-
123135 ngOnDestroy ( ) : void {
124136 this . clearListeners ( ) ;
125- this . destroyPopoverElement ( ) ;
137+ this . destroyTooltipElement ( ) ;
126138 }
127139
128140 ngOnInit ( ) : void {
@@ -132,18 +144,15 @@ export class PopoverDirective implements OnChanges, OnDestroy, OnInit, AfterView
132144 private setListeners ( ) : void {
133145 const config : IListenersConfig = {
134146 hostElement : this . hostElement ,
135- trigger : this . trigger ,
147+ trigger : this . trigger ( ) ,
136148 callbackToggle : ( ) => {
137- this . visible = ! this . visible ;
138- this . visible ? this . addPopoverElement ( ) : this . removePopoverElement ( ) ;
149+ this . visible . set ( ! this . visible ( ) ) ;
139150 } ,
140151 callbackOff : ( ) => {
141- this . visible = false ;
142- this . removePopoverElement ( ) ;
152+ this . visible . set ( false ) ;
143153 } ,
144154 callbackOn : ( ) => {
145- this . visible = true ;
146- this . addPopoverElement ( ) ;
155+ this . visible . set ( true ) ;
147156 }
148157 } ;
149158 this . listenersService . setListeners ( config ) ;
@@ -154,93 +163,93 @@ export class PopoverDirective implements OnChanges, OnDestroy, OnInit, AfterView
154163 }
155164
156165 private intersectionServiceSubscribe ( ) : void {
157- this . intersectionService . createIntersectionObserver ( this . hostElement ) ;
166+ this . intersectionService . createIntersectionObserver ( this . referenceRef ( ) ) ;
158167 this . intersectionService . intersecting$
159168 . pipe (
160- filter ( next => next . hostElement === this . hostElement ) ,
169+ filter ( ( next ) => next . hostElement === this . referenceRef ( ) ) ,
161170 debounceTime ( 100 ) ,
162171 finalize ( ( ) => {
163- this . intersectionService . unobserve ( this . hostElement ) ;
172+ this . intersectionService . unobserve ( this . referenceRef ( ) ) ;
164173 } ) ,
165174 takeUntilDestroyed ( this . #destroyRef)
166175 )
167- . subscribe ( next => {
168- this . visible = next . isIntersecting ? this . visible : false ;
169- ! this . visible && this . removePopoverElement ( ) ;
176+ . subscribe ( ( next ) => {
177+ this . visible . set ( next . isIntersecting ? this . visible ( ) : false ) ;
170178 } ) ;
171179 }
172180
173181 private getUID ( prefix : string ) : string {
174182 let uid = prefix ?? 'random-id' ;
175183 do {
176184 uid = `${ prefix } -${ Math . floor ( Math . random ( ) * 1000000 ) . toString ( 10 ) } ` ;
177- } while ( this . document . getElementById ( uid ) ) ;
185+ } while ( this . # document. getElementById ( uid ) ) ;
178186
179187 return uid ;
180188 }
181189
182- private createPopoverElement ( ) : void {
183- if ( ! this . popoverRef ) {
184- this . popoverRef = this . viewContainerRef . createComponent < PopoverComponent > ( PopoverComponent ) ;
190+ private createTooltipElement ( ) : void {
191+ if ( ! this . tooltipRef ) {
192+ this . tooltipRef = this . viewContainerRef . createComponent < PopoverComponent > ( PopoverComponent ) ;
185193 // this.viewContainerRef.detach();
186194 }
187195 }
188196
189- private destroyPopoverElement ( ) : void {
190- this . popover ?. remove ( ) ;
191- this . popoverRef ?. destroy ( ) ;
197+ private destroyTooltipElement ( ) : void {
198+ this . tooltip ?. remove ( ) ;
199+ this . tooltipRef ?. destroy ( ) ;
192200 // @ts -ignore
193- this . popoverRef = undefined ;
201+ this . tooltipRef = undefined ;
194202 this . popperInstance ?. destroy ( ) ;
195203 this . viewContainerRef ?. detach ( ) ;
196204 this . viewContainerRef ?. clear ( ) ;
197205 }
198206
199- private addPopoverElement ( ) : void {
200- if ( ! this . popoverRef ) {
201- this . createPopoverElement ( ) ;
207+ private addTooltipElement ( ) : void {
208+ if ( ! this . content ( ) ) {
209+ this . destroyTooltipElement ( ) ;
210+ return ;
211+ }
212+
213+ if ( ! this . tooltipRef ) {
214+ this . createTooltipElement ( ) ;
202215 }
203- this . popoverRef . instance . content = this . content ;
204- this . popover = this . popoverRef . location . nativeElement ;
205- this . renderer . addClass ( this . popover , 'd-none' ) ;
206- this . renderer . addClass ( this . popover , 'fade' ) ;
216+
217+ this . tooltipRef ?. setInput ( 'content' , this . content ( ) ?? '' ) ;
218+
219+ this . tooltip = this . tooltipRef ?. location . nativeElement ;
220+ this . renderer . addClass ( this . tooltip , 'd-none' ) ;
221+ this . renderer . addClass ( this . tooltip , 'fade' ) ;
207222
208223 this . popperInstance ?. destroy ( ) ;
209224
210- setTimeout ( ( ) => {
211- this . popperInstance = createPopper (
212- this . hostElement . nativeElement ,
213- this . popover ,
214- { ...this . popperOptions }
215- ) ;
216- this . viewContainerRef . insert ( this . popoverRef . hostView ) ;
217- this . renderer . appendChild ( this . document . body , this . popover ) ;
218- if ( ! this . visible ) {
219- this . removePopoverElement ( ) ;
220- return ;
221- }
222- setTimeout ( ( ) => {
223- this . popoverId = this . getUID ( 'popover' ) ;
224- this . popoverRef . instance . id = this . popoverId ;
225- if ( ! this . visible ) {
226- this . removePopoverElement ( ) ;
227- return ;
228- }
229- this . renderer . removeClass ( this . popover , 'd-none' ) ;
230- this . popoverRef . instance . visible = this . visible ;
231- this . popperInstance . forceUpdate ( ) ;
232- this . changeDetectorRef . markForCheck ( ) ;
233- } , 100 ) ;
225+ this . viewContainerRef . insert ( this . tooltipRef . hostView ) ;
226+ this . renderer . appendChild ( this . #document. body , this . tooltip ) ;
227+
228+ this . popperInstance = createPopper ( this . referenceRef ( ) . nativeElement , this . tooltip , {
229+ ...this . popperOptionsComputed ( )
234230 } ) ;
231+
232+ if ( ! this . visible ( ) ) {
233+ this . removeTooltipElement ( ) ;
234+ return ;
235+ }
236+ setTimeout ( ( ) => {
237+ this . tooltipId = this . getUID ( 'popover' ) ;
238+ this . tooltipRef ?. setInput ( 'id' , this . tooltipId ) ;
239+ this . renderer . removeClass ( this . tooltip , 'd-none' ) ;
240+ this . tooltipRef ?. setInput ( 'visible' , this . visible ( ) ) ;
241+ this . popperInstance ?. forceUpdate ( ) ;
242+ this . changeDetectorRef ?. markForCheck ( ) ;
243+ } , 100 ) ;
235244 }
236245
237- private removePopoverElement ( ) : void {
238- this . popoverId = '' ;
239- if ( ! this . popoverRef ) {
246+ private removeTooltipElement ( ) : void {
247+ this . tooltipId = '' ;
248+ if ( ! this . tooltipRef ) {
240249 return ;
241250 }
242- this . popoverRef . instance . visible = false ;
243- this . popoverRef . instance . id = undefined ;
251+ this . tooltipRef . setInput ( ' visible' , false ) ;
252+ this . tooltipRef . setInput ( 'id' , undefined ) ;
244253 this . changeDetectorRef . markForCheck ( ) ;
245254 setTimeout ( ( ) => {
246255 this . viewContainerRef ?. detach ( ) ;
0 commit comments