• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.52
/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.ts
1
import {
2
    ChangeDetectorRef,
3
    Directive,
4
    ElementRef,
5
    EventEmitter,
6
    HostBinding,
7
    HostListener,
8
    Inject,
9
    Input,
10
    OnDestroy,
11
    OnInit,
12
    Optional,
13
    Output
14
} from '@angular/core';
15
import { AbsoluteScrollStrategy } from '../../services/overlay/scroll/absolute-scroll-strategy';
16
import { CancelableBrowserEventArgs, IBaseEventArgs, PlatformUtil } from '../../core/utils';
17
import { ConnectedPositioningStrategy } from '../../services/overlay/position/connected-positioning-strategy';
18
import { filter, first, takeUntil } from 'rxjs/operators';
19
import { IgxNavigationService, IToggleView } from '../../core/navigation';
20
import { IgxOverlayService } from '../../services/overlay/overlay';
21
import { IPositionStrategy } from '../../services/overlay/position/IPositionStrategy';
22
import { OffsetMode, OverlayClosingEventArgs, OverlayEventArgs, OverlaySettings } from '../../services/overlay/utilities';
23
import { Subscription, Subject, MonoTypeOperatorFunction } from 'rxjs';
24

25
export interface ToggleViewEventArgs extends IBaseEventArgs {
26
    /** Id of the toggle view */
27
    id: string;
28
    /* blazorSuppress */
29
    event?: Event;
30
}
31

32
export interface ToggleViewCancelableEventArgs extends ToggleViewEventArgs, CancelableBrowserEventArgs { }
33

34
@Directive({
35
    exportAs: 'toggle',
36
    selector: '[igxToggle]',
37
    standalone: true
38
})
39
export class IgxToggleDirective implements IToggleView, OnInit, OnDestroy {
2✔
40
    /**
41
     * Emits an event after the toggle container is opened.
42
     *
43
     * ```typescript
44
     * onToggleOpened(event) {
45
     *    alert("Toggle opened!");
46
     * }
47
     * ```
48
     *
49
     * ```html
50
     * <div
51
     *   igxToggle
52
     *   (opened)='onToggleOpened($event)'>
53
     * </div>
54
     * ```
55
     */
56
    @Output()
57
    public opened = new EventEmitter<ToggleViewEventArgs>();
448✔
58

59
    /**
60
     * Emits an event before the toggle container is opened.
61
     *
62
     * ```typescript
63
     * onToggleOpening(event) {
64
     *  alert("Toggle opening!");
65
     * }
66
     * ```
67
     *
68
     * ```html
69
     * <div
70
     *   igxToggle
71
     *   (opening)='onToggleOpening($event)'>
72
     * </div>
73
     * ```
74
     */
75
    @Output()
76
    public opening = new EventEmitter<ToggleViewCancelableEventArgs>();
448✔
77

78
    /**
79
     * Emits an event after the toggle container is closed.
80
     *
81
     * ```typescript
82
     * onToggleClosed(event) {
83
     *  alert("Toggle closed!");
84
     * }
85
     * ```
86
     *
87
     * ```html
88
     * <div
89
     *   igxToggle
90
     *   (closed)='onToggleClosed($event)'>
91
     * </div>
92
     * ```
93
     */
94
    @Output()
95
    public closed = new EventEmitter<ToggleViewEventArgs>();
448✔
96

97
    /**
98
     * Emits an event before the toggle container is closed.
99
     *
100
     * ```typescript
101
     * onToggleClosing(event) {
102
     *  alert("Toggle closing!");
103
     * }
104
     * ```
105
     *
106
     * ```html
107
     * <div
108
     *  igxToggle
109
     *  (closing)='onToggleClosing($event)'>
110
     * </div>
111
     * ```
112
     */
113
    @Output()
114
    public closing = new EventEmitter<ToggleViewCancelableEventArgs>();
448✔
115

116
    /**
117
     * Emits an event after the toggle element is appended to the overlay container.
118
     *
119
     * ```typescript
120
     * onAppended() {
121
     *  alert("Content appended!");
122
     * }
123
     * ```
124
     *
125
     * ```html
126
     * <div
127
     *   igxToggle
128
     *   (appended)='onToggleAppended()'>
129
     * </div>
130
     * ```
131
     */
132
    @Output()
133
    public appended = new EventEmitter<ToggleViewEventArgs>();
448✔
134

135
    /**
136
     * @hidden
137
     */
138
    public get collapsed(): boolean {
139
        return this._collapsed;
37,484✔
140
    }
141

142
    /**
143
     * Identifier which is registered into `IgxNavigationService`
144
     *
145
     * ```typescript
146
     * let myToggleId = this.toggle.id;
147
     * ```
148
     */
149
    @Input()
150
    public id: string;
151

152
    /**
153
     * @hidden
154
     */
155
    public get element(): HTMLElement {
156
        return this.elementRef.nativeElement;
2✔
157
    }
158

159
    /**
160
     * @hidden
161
     */
162
    @HostBinding('class.igx-toggle--hidden')
163
    @HostBinding('attr.aria-hidden')
164
    public get hiddenClass() {
165
        return this.collapsed;
9,468✔
166
    }
167

168
    @HostBinding('class.igx-toggle--hidden-webkit')
169
    public get hiddenWebkitClass() {
170
        const isSafari = this.platform?.isSafari;
5,958✔
171
        const browserVersion = this.platform?.browserVersion;
5,958✔
172

173
        return this.collapsed && isSafari && !!browserVersion && browserVersion < 17.5;
5,958!
174
    }
175

176
    /**
177
     * @hidden
178
     */
179
    @HostBinding('class.igx-toggle')
180
    public get defaultClass() {
181
        return !this.collapsed;
4,734✔
182
    }
183

184
    protected _overlayId: string;
185

186
    private _collapsed = true;
448✔
187
    protected destroy$ = new Subject<boolean>();
448✔
188
    private _overlaySubFilter: [MonoTypeOperatorFunction<OverlayEventArgs>, MonoTypeOperatorFunction<OverlayEventArgs>] = [
448✔
189
        filter(x => x.id === this._overlayId),
117✔
190
        takeUntil(this.destroy$)
191
    ];
192
    private _overlayOpenedSub: Subscription;
193
    private _overlayClosingSub: Subscription;
194
    private _overlayClosedSub: Subscription;
195
    private _overlayContentAppendedSub: Subscription;
196

197
    /**
198
     * @hidden
199
     */
200
    constructor(
201
        private elementRef: ElementRef,
448✔
202
        private cdr: ChangeDetectorRef,
448✔
203
        @Inject(IgxOverlayService) protected overlayService: IgxOverlayService,
448✔
204
        @Optional() private navigationService: IgxNavigationService,
448✔
205
        @Optional() private platform?: PlatformUtil
448✔
206
    ) {
207
    }
208

209
    /**
210
     * Opens the toggle.
211
     *
212
     * ```typescript
213
     * this.myToggle.open();
214
     * ```
215
     */
216
    public open(overlaySettings?: OverlaySettings) {
217
        //  if there is open animation do nothing
218
        //  if toggle is not collapsed and there is no close animation do nothing
219
        const info = this.overlayService.getOverlayById(this._overlayId);
41✔
220
        const openAnimationStarted = info?.openAnimationPlayer?.hasStarted() ?? false;
41✔
221
        const closeAnimationStarted = info?.closeAnimationPlayer?.hasStarted() ?? false;
41✔
222
        if (openAnimationStarted || !(this._collapsed || closeAnimationStarted)) {
41!
UNCOV
223
            return;
×
224
        }
225

226
        this._collapsed = false;
41✔
227
        this.cdr.detectChanges();
41✔
228

229
        if (!info) {
41✔
230
            this.unsubscribe();
41✔
231
            this.subscribe();
41✔
232
            this._overlayId = this.overlayService.attach(this.elementRef, overlaySettings);
41✔
233
        }
234

235
        const args: ToggleViewCancelableEventArgs = { cancel: false, owner: this, id: this._overlayId };
41✔
236
        this.opening.emit(args);
41✔
237
        if (args.cancel) {
41!
UNCOV
238
            this.unsubscribe();
×
UNCOV
239
            this.overlayService.detach(this._overlayId);
×
UNCOV
240
            this._collapsed = true;
×
UNCOV
241
            delete this._overlayId;
×
UNCOV
242
            this.cdr.detectChanges();
×
UNCOV
243
            return;
×
244
        }
245
        this.overlayService.show(this._overlayId, overlaySettings);
41✔
246
    }
247

248
    /**
249
     * Closes the toggle.
250
     *
251
     * ```typescript
252
     * this.myToggle.close();
253
     * ```
254
     */
255
    public close(event?: Event) {
256
        //  if toggle is collapsed do nothing
257
        //  if there is close animation do nothing, toggle will close anyway
258
        const info = this.overlayService.getOverlayById(this._overlayId);
68✔
259
        const closeAnimationStarted = info?.closeAnimationPlayer?.hasStarted() || false;
68✔
260
        if (this._collapsed || closeAnimationStarted) {
68✔
261
            return;
31✔
262
        }
263

264
        this.overlayService.hide(this._overlayId, event);
37✔
265
    }
266

267
    /**
268
     * Opens or closes the toggle, depending on its current state.
269
     *
270
     * ```typescript
271
     * this.myToggle.toggle();
272
     * ```
273
     */
274
    public toggle(overlaySettings?: OverlaySettings) {
275
        //  if toggle is collapsed call open
276
        //  if there is running close animation call open
UNCOV
277
        if (this.collapsed || this.isClosing) {
×
UNCOV
278
            this.open(overlaySettings);
×
279
        } else {
UNCOV
280
            this.close();
×
281
        }
282
    }
283

284
    /** @hidden @internal */
285
    public get isClosing() {
286
        const info = this.overlayService.getOverlayById(this._overlayId);
4✔
287
        return info ? info.closeAnimationPlayer?.hasStarted() : false;
4!
288
    }
289

290
    /**
291
     * Returns the id of the overlay the content is rendered in.
292
     * ```typescript
293
     * this.myToggle.overlayId;
294
     * ```
295
     */
296
    public get overlayId() {
297
        return this._overlayId;
38✔
298
    }
299

300
    /**
301
     * Repositions the toggle.
302
     * ```typescript
303
     * this.myToggle.reposition();
304
     * ```
305
     */
306
    public reposition() {
UNCOV
307
        this.overlayService.reposition(this._overlayId);
×
308
    }
309

310
    /**
311
     * Offsets the content along the corresponding axis by the provided amount with optional
312
     * offsetMode that determines whether to add (by default) or set the offset values with OffsetMode.Add and OffsetMode.Set
313
     */
314
    public setOffset(deltaX: number, deltaY: number, offsetMode?: OffsetMode) {
UNCOV
315
        this.overlayService.setOffset(this._overlayId, deltaX, deltaY, offsetMode);
×
316
    }
317

318
    /**
319
     * @hidden
320
     */
321
    public ngOnInit() {
322
        if (this.navigationService && this.id) {
411✔
323
            this.navigationService.add(this.id, this);
149✔
324
        }
325
    }
326

327
    /**
328
     * @hidden
329
     */
330
    public ngOnDestroy() {
331
        if (this.navigationService && this.id) {
448✔
332
            this.navigationService.remove(this.id);
186✔
333
        }
334
        if (this._overlayId) {
448✔
335
            this.overlayService.detach(this._overlayId);
3✔
336
        }
337
        this.unsubscribe();
448✔
338
        this.destroy$.next(true);
448✔
339
        this.destroy$.complete();
448✔
340
    }
341

342
    private overlayClosed = (e) => {
448✔
343
        this._collapsed = true;
38✔
344
        this.cdr.detectChanges();
38✔
345
        this.unsubscribe();
38✔
346
        this.overlayService.detach(this.overlayId);
38✔
347
        const args: ToggleViewEventArgs = { owner: this, id: this._overlayId, event: e.event };
38✔
348
        delete this._overlayId;
38✔
349
        this.closed.emit(args);
38✔
350
        this.cdr.markForCheck();
38✔
351
    };
352

353
    private subscribe() {
354
        this._overlayContentAppendedSub = this.overlayService
41✔
355
            .contentAppended
356
            .pipe(first(), takeUntil(this.destroy$))
357
            .subscribe(() => {
358
                const args: ToggleViewEventArgs = { owner: this, id: this._overlayId };
41✔
359
                this.appended.emit(args);
41✔
360
            });
361

362
        this._overlayOpenedSub = this.overlayService
41✔
363
            .opened
364
            .pipe(...this._overlaySubFilter)
365
            .subscribe(() => {
366
                const args: ToggleViewEventArgs = { owner: this, id: this._overlayId };
41✔
367
                this.opened.emit(args);
41✔
368
            });
369

370
        this._overlayClosingSub = this.overlayService
41✔
371
            .closing
372
            .pipe(...this._overlaySubFilter)
373
            .subscribe((e: OverlayClosingEventArgs) => {
374
                const args: ToggleViewCancelableEventArgs = { cancel: false, event: e.event, owner: this, id: this._overlayId };
38✔
375
                this.closing.emit(args);
38✔
376
                e.cancel = args.cancel;
38✔
377

378
                //  in case event is not canceled this will close the toggle and we need to unsubscribe.
379
                //  Otherwise if for some reason, e.g. close on outside click, close() gets called before
380
                //  closed was fired we will end with calling closing more than once
381
                if (!e.cancel) {
38✔
382
                    this.clearSubscription(this._overlayClosingSub);
38✔
383
                }
384
            });
385

386
        this._overlayClosedSub = this.overlayService
41✔
387
            .closed
388
            .pipe(...this._overlaySubFilter)
389
            .subscribe(this.overlayClosed);
390
    }
391

392
    private unsubscribe() {
393
        this.clearSubscription(this._overlayOpenedSub);
527✔
394
        this.clearSubscription(this._overlayClosingSub);
527✔
395
        this.clearSubscription(this._overlayClosedSub);
527✔
396
        this.clearSubscription(this._overlayContentAppendedSub);
527✔
397
    }
398

399
    private clearSubscription(subscription: Subscription) {
400
        if (subscription && !subscription.closed) {
2,146✔
401
            subscription.unsubscribe();
123✔
402
        }
403
    }
404
}
405

406
@Directive({
407
    exportAs: 'toggle-action',
408
    selector: '[igxToggleAction]',
409
    standalone: true
410
})
411
export class IgxToggleActionDirective implements OnInit {
2✔
412
    /**
413
     * Provide settings that control the toggle overlay positioning, interaction and scroll behavior.
414
     * ```typescript
415
     * const settings: OverlaySettings = {
416
     *      closeOnOutsideClick: false,
417
     *      modal: false
418
     *  }
419
     * ```
420
     * ---
421
     * ```html
422
     * <!--set-->
423
     * <div igxToggleAction [overlaySettings]="settings"></div>
424
     * ```
425
     */
426
    @Input()
427
    public overlaySettings: OverlaySettings;
428

429
    /**
430
     * Determines where the toggle element overlay should be attached.
431
     *
432
     * ```html
433
     * <!--set-->
434
     * <div igxToggleAction [igxToggleOutlet]="outlet"></div>
435
     * ```
436
     * Where `outlet` in an instance of `IgxOverlayOutletDirective` or an `ElementRef`
437
     */
438
    @Input('igxToggleOutlet')
439
    public outlet: IgxOverlayOutletDirective | ElementRef;
440

441
    /**
442
     * @hidden
443
     */
444
    @Input('igxToggleAction')
445
    public set target(target: any) {
UNCOV
446
        if (target !== null && target !== '') {
×
UNCOV
447
            this._target = target;
×
448
        }
449
    }
450

451
    /**
452
     * @hidden
453
     */
454
    public get target(): any {
UNCOV
455
        if (typeof this._target === 'string') {
×
UNCOV
456
            return this.navigationService.get(this._target);
×
457
        }
UNCOV
458
        return this._target;
×
459
    }
460

461
    protected _overlayDefaults: OverlaySettings;
462
    protected _target: IToggleView | string;
463

464
    constructor(private element: ElementRef, @Optional() private navigationService: IgxNavigationService) { }
149✔
465

466
    /**
467
     * @hidden
468
     */
469
    @HostListener('click')
470
    public onClick() {
UNCOV
471
        if (this.outlet) {
×
UNCOV
472
            this._overlayDefaults.outlet = this.outlet;
×
473
        }
474

UNCOV
475
        const clonedSettings = Object.assign({}, this._overlayDefaults, this.overlaySettings);
×
UNCOV
476
        this.updateOverlaySettings(clonedSettings);
×
UNCOV
477
        this.target.toggle(clonedSettings);
×
478
    }
479

480
    /**
481
     * @hidden
482
     */
483
    public ngOnInit() {
484
        const targetElement = this.element.nativeElement;
149✔
485
        this._overlayDefaults = {
149✔
486
            target: targetElement,
487
            positionStrategy: new ConnectedPositioningStrategy(),
488
            scrollStrategy: new AbsoluteScrollStrategy(),
489
            closeOnOutsideClick: true,
490
            modal: false,
491
            excludeFromOutsideClick: [targetElement as HTMLElement]
492
        };
493
    }
494

495
    /**
496
     * Updates provided overlay settings
497
     *
498
     * @param settings settings to update
499
     * @returns returns updated copy of provided overlay settings
500
     */
501
    protected updateOverlaySettings(settings: OverlaySettings): OverlaySettings {
UNCOV
502
        if (settings && settings.positionStrategy) {
×
UNCOV
503
            const positionStrategyClone: IPositionStrategy = settings.positionStrategy.clone();
×
UNCOV
504
            settings.target = this.element.nativeElement;
×
UNCOV
505
            settings.positionStrategy = positionStrategyClone;
×
506
        }
507

UNCOV
508
        return settings;
×
509
    }
510
}
511

512
/**
513
 * Mark an element as an igxOverlay outlet container.
514
 * Directive instance is exported as `overlay-outlet` to be assigned to templates variables:
515
 * ```html
516
 * <div igxOverlayOutlet #outlet="overlay-outlet"></div>
517
 * ```
518
 */
519
@Directive({
520
    exportAs: 'overlay-outlet',
521
    selector: '[igxOverlayOutlet]',
522
    standalone: true
523
})
524
export class IgxOverlayOutletDirective {
2✔
525
    constructor(public element: ElementRef<HTMLElement>) { }
188✔
526

527
    /** @hidden */
528
    public get nativeElement() {
529
        return this.element.nativeElement;
2,492✔
530
    }
531
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc