• 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

2.87
/projects/igniteui-angular/src/lib/splitter/splitter.component.ts
1
import { DOCUMENT, NgFor, NgIf } from '@angular/common';
2
import { AfterContentInit, Component, ContentChildren, ElementRef, EventEmitter, HostBinding, HostListener, Inject, Input, Output, QueryList, booleanAttribute, forwardRef } from '@angular/core';
3
import { DragDirection, IDragMoveEventArgs, IDragStartEventArgs, IgxDragDirective, IgxDragIgnoreDirective } from '../directives/drag-drop/drag-drop.directive';
4
import { IgxSplitterPaneComponent } from './splitter-pane/splitter-pane.component';
5

6
/**
7
 * An enumeration that defines the `SplitterComponent` panes orientation.
8
 */
9
export enum SplitterType {
2✔
10
    Horizontal,
2✔
11
    Vertical
2✔
12
}
13

14
export declare interface ISplitterBarResizeEventArgs {
15
    pane: IgxSplitterPaneComponent;
16
    sibling: IgxSplitterPaneComponent;
17
}
18

19
/**
20
 * Provides a framework for a simple layout, splitting the view horizontally or vertically
21
 * into multiple smaller resizable and collapsible areas.
22
 *
23
 * @igxModule IgxSplitterModule
24
 *
25
 * @igxParent Layouts
26
 *
27
 * @igxTheme igx-splitter-theme
28
 *
29
 * @igxKeywords splitter panes layout
30
 *
31
 * @igxGroup presentation
32
 *
33
 * @example
34
 * ```html
35
 * <igx-splitter>
36
 *  <igx-splitter-pane>
37
 *      ...
38
 *  </igx-splitter-pane>
39
 *  <igx-splitter-pane>
40
 *      ...
41
 *  </igx-splitter-pane>
42
 * </igx-splitter>
43
 * ```
44
 */
45
@Component({
46
    selector: 'igx-splitter',
47
    templateUrl: './splitter.component.html',
UNCOV
48
    imports: [NgFor, NgIf, forwardRef(() => IgxSplitBarComponent)]
×
49
})
50
export class IgxSplitterComponent implements AfterContentInit {
2✔
51
    /**
52
     * Gets the list of splitter panes.
53
     *
54
     * @example
55
     * ```typescript
56
     * const panes = this.splitter.panes;
57
     * ```
58
     */
59
    @ContentChildren(IgxSplitterPaneComponent, { read: IgxSplitterPaneComponent })
60
    public panes!: QueryList<IgxSplitterPaneComponent>;
61

62
    /**
63
    * @hidden
64
    * @internal
65
    */
66
    @HostBinding('class.igx-splitter')
UNCOV
67
    public cssClass = 'igx-splitter';
×
68

69
    /**
70
     * @hidden @internal
71
     * Gets/Sets the `overflow` property of the current splitter.
72
     */
73
    @HostBinding('style.overflow')
UNCOV
74
    public overflow = 'hidden';
×
75

76
    /**
77
     * @hidden @internal
78
     * Sets/Gets the `display` property of the current splitter.
79
     */
80
    @HostBinding('style.display')
UNCOV
81
    public display = 'flex';
×
82

83
    /**
84
     * @hidden
85
     * @internal
86
     */
87
    @HostBinding('attr.aria-orientation')
88
    public get orientation() {
UNCOV
89
        return this.type === SplitterType.Horizontal ? 'horizontal' : 'vertical';
×
90
    }
91

92
    /**
93
     * Event fired when resizing of panes starts.
94
     *
95
     * @example
96
     * ```html
97
     * <igx-splitter (resizeStart)='resizeStart($event)'>
98
     *  <igx-splitter-pane>...</igx-splitter-pane>
99
     * </igx-splitter>
100
     * ```
101
     */
102
    @Output()
UNCOV
103
    public resizeStart = new EventEmitter<ISplitterBarResizeEventArgs>();
×
104

105
    /**
106
     * Event fired when resizing of panes is in progress.
107
     *
108
     * @example
109
     * ```html
110
     * <igx-splitter (resizing)='resizing($event)'>
111
     *  <igx-splitter-pane>...</igx-splitter-pane>
112
     * </igx-splitter>
113
     * ```
114
     */
115
    @Output()
UNCOV
116
    public resizing = new EventEmitter<ISplitterBarResizeEventArgs>();
×
117

118

119
    /**
120
     * Event fired when resizing of panes ends.
121
     *
122
     * @example
123
     * ```html
124
     * <igx-splitter (resizeEnd)='resizeEnd($event)'>
125
     *  <igx-splitter-pane>...</igx-splitter-pane>
126
     * </igx-splitter>
127
     * ```
128
     */
129
    @Output()
UNCOV
130
    public resizeEnd = new EventEmitter<ISplitterBarResizeEventArgs>();
×
131

UNCOV
132
    private _type: SplitterType = SplitterType.Horizontal;
×
133

134
    /**
135
     * @hidden @internal
136
     * A field that holds the initial size of the main `IgxSplitterPaneComponent` in each pair of panes divided by a splitter bar.
137
     */
138
    private initialPaneSize!: number;
139

140
    /**
141
     * @hidden @internal
142
     * A field that holds the initial size of the sibling pane in each pair of panes divided by a gripper.
143
     * @memberof SplitterComponent
144
     */
145
    private initialSiblingSize!: number;
146

147
    /**
148
     * @hidden @internal
149
     * The main pane in each pair of panes divided by a gripper.
150
     */
151
    private pane!: IgxSplitterPaneComponent;
152

153
    /**
154
     * The sibling pane in each pair of panes divided by a splitter bar.
155
     */
156
    private sibling!: IgxSplitterPaneComponent;
157

UNCOV
158
    constructor(@Inject(DOCUMENT) public document, private elementRef: ElementRef) { }
×
159
    /**
160
     * Gets/Sets the splitter orientation.
161
     *
162
     * @example
163
     * ```html
164
     * <igx-splitter [type]="type">...</igx-splitter>
165
     * ```
166
     */
167
    @Input()
168
    public get type() {
UNCOV
169
        return this._type;
×
170
    }
171
    public set type(value) {
UNCOV
172
        this._type = value;
×
UNCOV
173
        this.resetPaneSizes();
×
UNCOV
174
        this.panes?.notifyOnChanges();
×
175
    }
176

177
    /**
178
     * Sets the visibility of the handle and expanders in the splitter bar.
179
     * False by default
180
     * 
181
     * @example
182
     * ```html
183
     * <igx-splitter [nonCollapsible]='true'>
184
     * </igx-splitter>
185
     * ```
186
     */
187
    @Input({ transform: booleanAttribute })
UNCOV
188
    public nonCollapsible = false; // Input to toggle showing/hiding expanders
×
189

190
    /**
191
     * @hidden @internal
192
     * Gets the `flex-direction` property of the current `SplitterComponent`.
193
     */
194
    @HostBinding('style.flex-direction')
195
    public get direction(): string {
UNCOV
196
        return this.type === SplitterType.Horizontal ? 'row' : 'column';
×
197
    }
198

199
    /** @hidden @internal */
200
    public ngAfterContentInit(): void {
UNCOV
201
        this.initPanes();
×
UNCOV
202
        this.panes.changes.subscribe(() => {
×
UNCOV
203
            this.initPanes();
×
204
        });
205
    }
206

207
    /**
208
     * @hidden @internal
209
     * This method performs  initialization logic when the user starts dragging the splitter bar between each pair of panes.
210
     * @param pane - the main pane associated with the currently dragged bar.
211
     */
212
    public onMoveStart(pane: IgxSplitterPaneComponent) {
UNCOV
213
        const panes = this.panes.toArray();
×
UNCOV
214
        this.pane = pane;
×
UNCOV
215
        this.sibling = panes[panes.indexOf(this.pane) + 1];
×
216

UNCOV
217
        const paneRect = this.pane.element.getBoundingClientRect();
×
UNCOV
218
        this.initialPaneSize = this.type === SplitterType.Horizontal ? paneRect.width : paneRect.height;
×
219

UNCOV
220
        const siblingRect = this.sibling.element.getBoundingClientRect();
×
UNCOV
221
        this.initialSiblingSize = this.type === SplitterType.Horizontal ? siblingRect.width : siblingRect.height;
×
UNCOV
222
        const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
×
UNCOV
223
        this.resizeStart.emit(args);
×
224
    }
225

226
    /**
227
     * @hidden @internal
228
     * This method performs calculations concerning the sizes of each pair of panes when the bar between them is dragged.
229
     * @param delta - The difference along the X (or Y) axis between the initial and the current point when dragging the bar.
230
     */
231
    public onMoving(delta: number) {
UNCOV
232
        const [ paneSize, siblingSize ] = this.calcNewSizes(delta);
×
233

UNCOV
234
        this.pane.dragSize = paneSize + 'px';
×
UNCOV
235
        this.sibling.dragSize = siblingSize + 'px';
×
236

UNCOV
237
        const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
×
UNCOV
238
        this.resizing.emit(args);
×
239
    }
240

241
    public onMoveEnd(delta: number) {
UNCOV
242
        const [ paneSize, siblingSize ] = this.calcNewSizes(delta);
×
243

UNCOV
244
        if (this.pane.isPercentageSize) {
×
245
            // handle % resizes
UNCOV
246
            const totalSize = this.getTotalSize();
×
UNCOV
247
            const percentPaneSize = (paneSize / totalSize) * 100;
×
UNCOV
248
            this.pane.size = percentPaneSize + '%';
×
249
        } else {
250
            // px resize
UNCOV
251
            this.pane.size = paneSize + 'px';
×
252
        }
253

UNCOV
254
        if (this.sibling.isPercentageSize) {
×
255
            // handle % resizes
UNCOV
256
            const totalSize = this.getTotalSize();
×
UNCOV
257
            const percentSiblingPaneSize = (siblingSize / totalSize) * 100;
×
UNCOV
258
            this.sibling.size = percentSiblingPaneSize + '%';
×
259
        } else {
260
            // px resize
261
            this.sibling.size = siblingSize + 'px';
×
262
        }
UNCOV
263
        this.pane.dragSize = null;
×
UNCOV
264
        this.sibling.dragSize = null;
×
265

UNCOV
266
        const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
×
UNCOV
267
        this.resizeEnd.emit(args);
×
268
    }
269

270
    /** @hidden @internal */
271
    public getPaneSiblingsByOrder(order: number, barIndex: number): Array<IgxSplitterPaneComponent> {
UNCOV
272
        const panes = this.panes.toArray();
×
UNCOV
273
        const prevPane = panes[order - barIndex - 1];
×
UNCOV
274
        const nextPane = panes[order - barIndex];
×
UNCOV
275
        const siblings = [prevPane, nextPane];
×
UNCOV
276
        return siblings;
×
277
    }
278

279
    private getTotalSize() {
UNCOV
280
        const computed = this.document.defaultView.getComputedStyle(this.elementRef.nativeElement);
×
UNCOV
281
        const totalSize = this.type === SplitterType.Horizontal ? computed.getPropertyValue('width') : computed.getPropertyValue('height');
×
UNCOV
282
        return parseFloat(totalSize);
×
283
    }
284

285

286
    /**
287
     * @hidden @internal
288
     * This method inits panes with properties.
289
     */
290
    private initPanes() {
UNCOV
291
        this.panes.forEach(pane => {
×
UNCOV
292
            pane.owner = this;
×
UNCOV
293
            if (this.type === SplitterType.Horizontal) {
×
UNCOV
294
                pane.minWidth = pane.minSize ?? '0';
×
UNCOV
295
                pane.maxWidth = pane.maxSize ?? '100%';
×
296
            } else {
UNCOV
297
                pane.minHeight = pane.minSize ?? '0';
×
UNCOV
298
                pane.maxHeight = pane.maxSize ?? '100%';
×
299
            }
300
        });
UNCOV
301
        this.assignFlexOrder();
×
UNCOV
302
        if (this.panes.filter(x => x.collapsed).length > 0) {
×
303
            // if any panes are collapsed, reset sizes.
UNCOV
304
            this.resetPaneSizes();
×
305
        }
306
    }
307

308
    /**
309
     * @hidden @internal
310
     * This method reset pane sizes.
311
     */
312
    private resetPaneSizes() {
UNCOV
313
        if (this.panes) {
×
314
            // if type is changed runtime, should reset sizes.
UNCOV
315
            this.panes.forEach(x => {
×
UNCOV
316
                x.size = 'auto'
×
UNCOV
317
                x.minWidth = '0';
×
UNCOV
318
                x.maxWidth = '100%';
×
UNCOV
319
                x.minHeight = '0';
×
UNCOV
320
                x.maxHeight = '100%';
×
321
            });
322
        }
323
    }
324

325
    /**
326
     * @hidden @internal
327
     * This method assigns the order of each pane.
328
     */
329
    private assignFlexOrder() {
UNCOV
330
        let k = 0;
×
UNCOV
331
        this.panes.forEach((pane: IgxSplitterPaneComponent) => {
×
UNCOV
332
            pane.order = k;
×
UNCOV
333
            k += 2;
×
334
        });
335
    }
336

337
    /**
338
     * @hidden @internal
339
     * Calculates new sizes for the panes based on move delta and initial sizes
340
     */
341
    private calcNewSizes(delta: number): [number, number] {
UNCOV
342
        const min = parseInt(this.pane.minSize, 10) || 0;
×
UNCOV
343
        const minSibling = parseInt(this.sibling.minSize, 10) || 0;
×
UNCOV
344
        const max = parseInt(this.pane.maxSize, 10) || this.initialPaneSize + this.initialSiblingSize - minSibling;
×
UNCOV
345
        const maxSibling = parseInt(this.sibling.maxSize, 10) || this.initialPaneSize + this.initialSiblingSize - min;
×
346

UNCOV
347
        if (delta < 0) {
×
UNCOV
348
            const maxPossibleDelta = Math.min(
×
349
                max - this.initialPaneSize,
350
                this.initialSiblingSize - minSibling,
351
            )
UNCOV
352
            delta = Math.min(maxPossibleDelta, Math.abs(delta)) * -1;
×
353
        } else {
UNCOV
354
            const maxPossibleDelta = Math.min(
×
355
                this.initialPaneSize - min,
356
                maxSibling - this.initialSiblingSize
357
            )
UNCOV
358
            delta = Math.min(maxPossibleDelta, Math.abs(delta));
×
359
        }
UNCOV
360
        return [this.initialPaneSize - delta, this.initialSiblingSize + delta];
×
361
    }
362
}
363

364
/**
365
 * @hidden @internal
366
 * Represents the draggable bar that visually separates panes and allows for changing their sizes.
367
 */
368
@Component({
369
    selector: 'igx-splitter-bar',
370
    templateUrl: './splitter-bar.component.html',
371
    imports: [IgxDragDirective, IgxDragIgnoreDirective]
372
})
373
export class IgxSplitBarComponent {
2✔
374
    /**
375
     * Set css class to the host element.
376
     */
377
    @HostBinding('class.igx-splitter-bar-host')
UNCOV
378
    public cssClass = 'igx-splitter-bar-host';
×
379

380
     /**
381
     * Sets the visibility of the handle and expanders in the splitter bar.
382
     */
383
    @Input({ transform: booleanAttribute })
384
    public nonCollapsible;
385

386
    /**
387
     * Gets/Sets the orientation.
388
     */
389
    @Input()
UNCOV
390
    public type: SplitterType = SplitterType.Horizontal;
×
391

392
    /**
393
     * Sets/gets the element order.
394
     */
395
    @HostBinding('style.order')
396
    @Input()
397
    public order!: number;
398

399
    /**
400
     * @hidden
401
     * @internal
402
     */
403
    @HostBinding('attr.tabindex')
404
    public get tabindex() {
UNCOV
405
        return this.resizeDisallowed ? null : 0;
×
406
    }
407

408
    /**
409
     * @hidden
410
     * @internal
411
     */
412
    @HostBinding('attr.aria-orientation')
413
    public get orientation() {
UNCOV
414
        return this.type === SplitterType.Horizontal ? 'horizontal' : 'vertical';
×
415
    }
416

417
    /**
418
     * @hidden
419
     * @internal
420
     */
421
    public get cursor() {
UNCOV
422
        if (this.resizeDisallowed) {
×
UNCOV
423
            return '';
×
424
        }
UNCOV
425
        return this.type === SplitterType.Horizontal ? 'col-resize' : 'row-resize';
×
426
    }
427

428
    /**
429
     * Sets/gets the `SplitPaneComponent` associated with the current `SplitBarComponent`.
430
     *
431
     * @memberof SplitBarComponent
432
     */
433
    @Input()
434
    public pane!: IgxSplitterPaneComponent;
435

436
    /**
437
     * Sets/Gets the `SplitPaneComponent` sibling components associated with the current `SplitBarComponent`.
438
     */
439
    @Input()
440
    public siblings!: Array<IgxSplitterPaneComponent>;
441

442
    /**
443
     * An event that is emitted whenever we start dragging the current `SplitBarComponent`.
444
     */
445
    @Output()
UNCOV
446
    public moveStart = new EventEmitter<IgxSplitterPaneComponent>();
×
447

448
    /**
449
     * An event that is emitted while we are dragging the current `SplitBarComponent`.
450
     */
451
    @Output()
UNCOV
452
    public moving = new EventEmitter<number>();
×
453

454
    @Output()
UNCOV
455
    public movingEnd = new EventEmitter<number>();
×
456

457
    /**
458
     * A temporary holder for the pointer coordinates.
459
     */
460
    private startPoint!: number;
461

UNCOV
462
    private interactionKeys = new Set('right down left up arrowright arrowdown arrowleft arrowup'.split(' '));
×
463

464
    /**
465
     * @hidden @internal
466
     */
467
    public get prevButtonHidden() {
UNCOV
468
        return this.siblings[0].collapsed && !this.siblings[1].collapsed;
×
469
    }
470

471
    /**
472
     * @hidden @internal
473
     */
474
    @HostListener('keydown', ['$event'])
475
    public keyEvent(event: KeyboardEvent) {
UNCOV
476
        const key = event.key.toLowerCase();
×
UNCOV
477
        const ctrl = event.ctrlKey;
×
UNCOV
478
        event.stopPropagation();
×
UNCOV
479
        if (this.interactionKeys.has(key)) {
×
UNCOV
480
            event.preventDefault();
×
481
        }
UNCOV
482
        switch (key) {
×
483
            case 'arrowup':
484
            case 'up':
UNCOV
485
                if (this.type === SplitterType.Vertical) {
×
UNCOV
486
                    if (ctrl) {
×
UNCOV
487
                        this.onCollapsing(false);
×
UNCOV
488
                        break;
×
489
                    }
UNCOV
490
                    if (!this.resizeDisallowed) {
×
UNCOV
491
                        event.preventDefault();
×
UNCOV
492
                        this.moveStart.emit(this.pane);
×
UNCOV
493
                        this.moving.emit(10);
×
494
                    }
495
                }
UNCOV
496
                break;
×
497
            case 'arrowdown':
498
            case 'down':
UNCOV
499
                if (this.type === SplitterType.Vertical) {
×
UNCOV
500
                    if (ctrl) {
×
UNCOV
501
                        this.onCollapsing(true);
×
UNCOV
502
                        break;
×
503
                    }
UNCOV
504
                    if (!this.resizeDisallowed) {
×
UNCOV
505
                        event.preventDefault();
×
UNCOV
506
                        this.moveStart.emit(this.pane);
×
UNCOV
507
                        this.moving.emit(-10);
×
508
                    }
509
                }
UNCOV
510
                break;
×
511
            case 'arrowleft':
512
            case 'left':
UNCOV
513
                if (this.type === SplitterType.Horizontal) {
×
UNCOV
514
                    if (ctrl) {
×
UNCOV
515
                        this.onCollapsing(false);
×
UNCOV
516
                        break;
×
517
                    }
UNCOV
518
                    if (!this.resizeDisallowed) {
×
UNCOV
519
                        event.preventDefault();
×
UNCOV
520
                        this.moveStart.emit(this.pane);
×
UNCOV
521
                        this.moving.emit(10);
×
522
                    }
523
                }
UNCOV
524
                break;
×
525
            case 'arrowright':
526
            case 'right':
UNCOV
527
                if (this.type === SplitterType.Horizontal) {
×
UNCOV
528
                    if (ctrl) {
×
UNCOV
529
                        this.onCollapsing(true);
×
UNCOV
530
                        break;
×
531
                    }
UNCOV
532
                    if (!this.resizeDisallowed) {
×
UNCOV
533
                        event.preventDefault();
×
UNCOV
534
                        this.moveStart.emit(this.pane);
×
UNCOV
535
                        this.moving.emit(-10);
×
536
                    }
537
                }
UNCOV
538
                break;
×
539
            default:
540
                break;
×
541
        }
542
    }
543

544
    /**
545
     * @hidden @internal
546
     */
547
    public get dragDir() {
UNCOV
548
        return this.type === SplitterType.Horizontal ? DragDirection.VERTICAL : DragDirection.HORIZONTAL;
×
549
    }
550

551
    /**
552
     * @hidden @internal
553
     */
554
    public get nextButtonHidden() {
UNCOV
555
        return this.siblings[1].collapsed && !this.siblings[0].collapsed;
×
556
    }
557

558
    /**
559
     * @hidden @internal
560
     */
561
    public onDragStart(event: IDragStartEventArgs) {
UNCOV
562
        if (this.resizeDisallowed) {
×
UNCOV
563
            event.cancel = true;
×
UNCOV
564
            return;
×
565
        }
566
        this.startPoint = this.type === SplitterType.Horizontal ? event.startX : event.startY;
×
567
        this.moveStart.emit(this.pane);
×
568
    }
569

570
    /**
571
     * @hidden @internal
572
     */
573
    public onDragMove(event: IDragMoveEventArgs) {
574
        const isHorizontal = this.type === SplitterType.Horizontal;
×
575
        const curr = isHorizontal ? event.pageX : event.pageY;
×
576
        const delta = this.startPoint - curr;
×
577
        if (delta !== 0) {
×
578
            this.moving.emit(delta);
×
579
            event.cancel = true;
×
580
            event.owner.element.nativeElement.style.transform = '';
×
581
        }
582
    }
583

584
    public onDragEnd(event: any) {
585
        const isHorizontal = this.type === SplitterType.Horizontal;
×
586
        const curr = isHorizontal ? event.pageX : event.pageY;
×
587
        const delta = this.startPoint - curr;
×
588
        if (delta !== 0) {
×
589
            this.movingEnd.emit(delta);
×
590
        }
591
    }
592

593
    protected get resizeDisallowed() {
UNCOV
594
        const relatedTabs = this.siblings;
×
UNCOV
595
        return !!relatedTabs.find(x => x.resizable === false || x.collapsed === true);
×
596
    }
597

598
    /**
599
     * @hidden @internal
600
     */
601
    public onCollapsing(next: boolean) {
UNCOV
602
        const prevSibling = this.siblings[0];
×
UNCOV
603
        const nextSibling = this.siblings[1];
×
604
        let target;
UNCOV
605
        if (next) {
×
606
            // if next is clicked when prev pane is hidden, show prev pane, else hide next pane.
UNCOV
607
            target = prevSibling.collapsed ? prevSibling : nextSibling;
×
608
        } else {
609
            // if prev is clicked when next pane is hidden, show next pane, else hide prev pane.
UNCOV
610
            target = nextSibling.collapsed ? nextSibling : prevSibling;
×
611
        }
UNCOV
612
        target.toggle();
×
613
    }
614
}
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