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

IgniteUI / igniteui-angular / 28013325390

23 Jun 2026 08:34AM UTC coverage: 90.139% (-0.02%) from 90.154%
28013325390

Pull #17324

github

web-flow
Merge 690ff31c5 into 01244911c
Pull Request #17324: fix(skills): omit column widths by default in generated grid code

14880 of 17339 branches covered (85.82%)

Branch coverage included in aggregate %.

29947 of 32392 relevant lines covered (92.45%)

34656.81 hits per line

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

90.29
/projects/igniteui-angular/splitter/src/splitter/splitter.component.ts
1
import { AfterContentInit, Component, ContentChildren, ElementRef, EventEmitter, HostBinding, HostListener, Input, NgZone, Output, QueryList, booleanAttribute, forwardRef, DOCUMENT, inject, ChangeDetectionStrategy } from '@angular/core';
2
import { DragDirection, IDragMoveEventArgs, IDragStartEventArgs, IgxDragDirective, IgxDragIgnoreDirective } from 'igniteui-angular/directives';
3
import { IgxSplitterPaneComponent } from './splitter-pane/splitter-pane.component';
4
import { take } from 'rxjs';
5

6
/**
7
 * An enumeration that defines the `SplitterComponent` panes orientation.
8
 */
9
export enum SplitterType {
3✔
10
    Horizontal,
3✔
11
    Vertical
3✔
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',
48
    changeDetection: ChangeDetectionStrategy.Eager,
49
    imports: [forwardRef(() => IgxSplitBarComponent)]
119✔
50
})
51
export class IgxSplitterComponent implements AfterContentInit {
3✔
52
    public document = inject(DOCUMENT);
24✔
53
    private elementRef = inject(ElementRef);
24✔
54
    private zone = inject(NgZone);
24✔
55

56
    /**
57
     * Gets the list of splitter panes.
58
     *
59
     * @example
60
     * ```typescript
61
     * const panes = this.splitter.panes;
62
     * ```
63
     */
64
    @ContentChildren(IgxSplitterPaneComponent, { read: IgxSplitterPaneComponent })
65
    public panes!: QueryList<IgxSplitterPaneComponent>;
66

67
    /**
68
    * @hidden
69
    * @internal
70
    */
71
    @HostBinding('class.igx-splitter')
72
    public cssClass = 'igx-splitter';
24✔
73

74
    /**
75
     * @hidden @internal
76
     * Gets/Sets the `overflow` property of the current splitter.
77
     */
78
    @HostBinding('style.overflow')
79
    public overflow = 'hidden';
24✔
80

81
    /**
82
     * @hidden @internal
83
     * Sets/Gets the `display` property of the current splitter.
84
     */
85
    @HostBinding('style.display')
86
    public display = 'flex';
24✔
87

88
    /**
89
     * @hidden
90
     * @internal
91
     */
92
    @HostBinding('attr.aria-orientation')
93
    public get orientation() {
94
        return this.type === SplitterType.Horizontal ? 'horizontal' : 'vertical';
172✔
95
    }
96

97
    /**
98
     * Event fired when resizing of panes starts.
99
     *
100
     * @example
101
     * ```html
102
     * <igx-splitter (resizeStart)='resizeStart($event)'>
103
     *  <igx-splitter-pane>...</igx-splitter-pane>
104
     * </igx-splitter>
105
     * ```
106
     */
107
    @Output()
108
    public resizeStart = new EventEmitter<ISplitterBarResizeEventArgs>();
24✔
109

110
    /**
111
     * Event fired when resizing of panes is in progress.
112
     *
113
     * @example
114
     * ```html
115
     * <igx-splitter (resizing)='resizing($event)'>
116
     *  <igx-splitter-pane>...</igx-splitter-pane>
117
     * </igx-splitter>
118
     * ```
119
     */
120
    @Output()
121
    public resizing = new EventEmitter<ISplitterBarResizeEventArgs>();
24✔
122

123

124
    /**
125
     * Event fired when resizing of panes ends.
126
     *
127
     * @example
128
     * ```html
129
     * <igx-splitter (resizeEnd)='resizeEnd($event)'>
130
     *  <igx-splitter-pane>...</igx-splitter-pane>
131
     * </igx-splitter>
132
     * ```
133
     */
134
    @Output()
135
    public resizeEnd = new EventEmitter<ISplitterBarResizeEventArgs>();
24✔
136

137
    private _type: SplitterType = SplitterType.Horizontal;
24✔
138

139
    /**
140
     * @hidden @internal
141
     * A field that holds the initial size of the main splitter pane in each pair of panes divided by a splitter bar.
142
     */
143
    private initialPaneSize!: number;
144

145
    /**
146
     * @hidden @internal
147
     * A field that holds the initial size of the sibling pane in each pair of panes divided by a gripper.
148
     * @memberof SplitterComponent
149
     */
150
    private initialSiblingSize!: number;
151

152
    /**
153
     * @hidden @internal
154
     * The main pane in each pair of panes divided by a gripper.
155
     */
156
    private pane!: IgxSplitterPaneComponent;
157

158
    /**
159
     * The sibling pane in each pair of panes divided by a splitter bar.
160
     */
161
    private sibling!: IgxSplitterPaneComponent;
162
    /**
163
     * Gets/Sets the splitter orientation.
164
     *
165
     * @example
166
     * ```html
167
     * <igx-splitter [type]="type">...</igx-splitter>
168
     * ```
169
     */
170
    @Input()
171
    public get type() {
172
        return this._type;
696✔
173
    }
174
    public set type(value) {
175
        this._type = value;
29✔
176
        this.resetPaneSizes();
29✔
177
        this.panes?.notifyOnChanges();
29✔
178
    }
179

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

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

202
    /** @hidden @internal */
203
    public ngAfterContentInit(): void {
204
        this.zone.onStable.pipe(take(1)).subscribe(() => {
24✔
205
            this.initPanes();
24✔
206
        });
207
        this.panes.changes.subscribe(() => {
24✔
208
            this.initPanes();
9✔
209
        });
210
    }
211

212
    /**
213
     * @hidden @internal
214
     * This method performs  initialization logic when the user starts dragging the splitter bar between each pair of panes.
215
     * @param pane - the main pane associated with the currently dragged bar.
216
     */
217
    public onMoveStart(pane: IgxSplitterPaneComponent) {
218
        const panes = this.panes.toArray();
18✔
219
        this.pane = pane;
18✔
220
        this.sibling = panes[panes.indexOf(this.pane) + 1];
18✔
221

222
        const paneRect = this.pane.element.getBoundingClientRect();
18✔
223
        this.initialPaneSize = this.type === SplitterType.Horizontal ? paneRect.width : paneRect.height;
18✔
224

225
        const siblingRect = this.sibling.element.getBoundingClientRect();
18✔
226
        this.initialSiblingSize = this.type === SplitterType.Horizontal ? siblingRect.width : siblingRect.height;
18✔
227
        const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
18✔
228
        this.resizeStart.emit(args);
18✔
229
    }
230

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

239
        this.pane.dragSize = paneSize + 'px';
18✔
240
        this.sibling.dragSize = siblingSize + 'px';
18✔
241

242
        const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
18✔
243
        this.resizing.emit(args);
18✔
244
    }
245

246
    public onMoveEnd(delta: number) {
247
        let [ paneSize, siblingSize ] = this.calcNewSizes(delta);
6✔
248

249
        if (paneSize + siblingSize > this.getTotalSize() && delta < 0) {
6✔
250
            siblingSize = this.getTotalSize() - paneSize;
1✔
251
        } else if (paneSize + siblingSize > this.getTotalSize() && delta > 0) {
5!
252
            paneSize = this.getTotalSize() - siblingSize;
×
253
        }
254

255
        if (this.pane.isPercentageSize) {
6✔
256
            // handle % resizes
257
            const totalSize = this.getTotalSize();
5✔
258
            const percentPaneSize = (paneSize / totalSize) * 100;
5✔
259
            this.pane.size = percentPaneSize + '%';
5✔
260
        } else {
261
            // px resize
262
            this.pane.size = paneSize + 'px';
1✔
263
        }
264

265
        if (this.sibling.isPercentageSize) {
6✔
266
            // handle % resizes
267
            const totalSize = this.getTotalSize();
4✔
268
            const percentSiblingPaneSize = (siblingSize / totalSize) * 100;
4✔
269
            this.sibling.size = percentSiblingPaneSize + '%';
4✔
270
        } else {
271
            // px resize
272
            this.sibling.size = siblingSize + 'px';
2✔
273
        }
274
        this.pane.dragSize = null;
6✔
275
        this.sibling.dragSize = null;
6✔
276

277
        const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
6✔
278
        this.resizeEnd.emit(args);
6✔
279
    }
280

281
    /** @hidden @internal */
282
    public getPaneSiblingsByOrder(order: number, barIndex: number): Array<IgxSplitterPaneComponent> {
283
        const panes = this.panes.toArray();
220✔
284
        const prevPane = panes[order - barIndex - 1];
220✔
285
        const nextPane = panes[order - barIndex];
220✔
286
        const siblings = [prevPane, nextPane];
220✔
287
        return siblings;
220✔
288
    }
289

290
    private getTotalSize() {
291
        const computed = this.document.defaultView.getComputedStyle(this.elementRef.nativeElement);
22✔
292
        const totalSize = this.type === SplitterType.Horizontal ? computed.getPropertyValue('width') : computed.getPropertyValue('height');
22✔
293
        return parseFloat(totalSize);
22✔
294
    }
295

296

297
    /**
298
     * @hidden @internal
299
     * This method inits panes with properties.
300
     */
301
    private initPanes() {
302
        this.panes.forEach(pane => {
33✔
303
            pane.owner = this;
74✔
304
            if (this.type === SplitterType.Horizontal) {
74✔
305
                pane.minWidth = pane.minSize ?? '0';
55✔
306
                pane.maxWidth = pane.maxSize ?? '100%';
55✔
307
            } else {
308
                pane.minHeight = pane.minSize ?? '0';
19✔
309
                pane.maxHeight = pane.maxSize ?? '100%';
19✔
310
            }
311
        });
312
        this.assignFlexOrder();
33✔
313
        if (this.panes.filter(x => x.collapsed).length > 0) {
74✔
314
            // if any panes are collapsed, reset sizes.
315
            this.resetPaneSizes();
2✔
316
        }
317
    }
318

319
    /**
320
     * @hidden @internal
321
     * This method reset pane sizes.
322
     */
323
    private resetPaneSizes() {
324
        if (this.panes) {
31✔
325
            // if type is changed runtime, should reset sizes.
326
            this.panes.forEach(x => {
9✔
327
                x.size = 'auto'
21✔
328
                x.minWidth = '0';
21✔
329
                x.maxWidth = '100%';
21✔
330
                x.minHeight = '0';
21✔
331
                x.maxHeight = '100%';
21✔
332
            });
333
        }
334
    }
335

336
    /**
337
     * @hidden @internal
338
     * This method assigns the order of each pane.
339
     */
340
    private assignFlexOrder() {
341
        let k = 0;
33✔
342
        this.panes.forEach((pane: IgxSplitterPaneComponent) => {
33✔
343
            pane.order = k;
74✔
344
            k += 2;
74✔
345
        });
346
    }
347

348
    /**
349
     * @hidden @internal
350
     * Calculates new sizes for the panes based on move delta and initial sizes
351
     */
352
    private calcNewSizes(delta: number): [number, number] {
353
        const min = parseInt(this.pane.minSize, 10) || 0;
24✔
354
        const minSibling = parseInt(this.sibling.minSize, 10) || 0;
24✔
355
        const max = parseInt(this.pane.maxSize, 10) || this.initialPaneSize + this.initialSiblingSize - minSibling;
24✔
356
        const maxSibling = parseInt(this.sibling.maxSize, 10) || this.initialPaneSize + this.initialSiblingSize - min;
24✔
357

358
        if (delta < 0) {
24✔
359
            const maxPossibleDelta = Math.min(
16✔
360
                max - this.initialPaneSize,
361
                this.initialSiblingSize - minSibling,
362
            )
363
            delta = Math.min(maxPossibleDelta, Math.abs(delta)) * -1;
16✔
364
        } else {
365
            const maxPossibleDelta = Math.min(
8✔
366
                this.initialPaneSize - min,
367
                maxSibling - this.initialSiblingSize
368
            )
369
            delta = Math.min(maxPossibleDelta, Math.abs(delta));
8✔
370
        }
371
        return [this.initialPaneSize - delta, this.initialSiblingSize + delta];
24✔
372
    }
373
}
374

375
/**
376
 * @hidden @internal
377
 * Represents the draggable bar that visually separates panes and allows for changing their sizes.
378
 */
379
@Component({
380
    selector: 'igx-splitter-bar',
381
    templateUrl: './splitter-bar.component.html',
382
    changeDetection: ChangeDetectionStrategy.Eager,
383
    imports: [IgxDragDirective, IgxDragIgnoreDirective]
384
})
385
export class IgxSplitBarComponent {
3✔
386
    /**
387
     * Set css class to the host element.
388
     */
389
    @HostBinding('class.igx-splitter-bar-host')
390
    public cssClass = 'igx-splitter-bar-host';
31✔
391

392
     /**
393
     * Sets the visibility of the handle and expanders in the splitter bar.
394
     */
395
    @Input({ transform: booleanAttribute })
396
    public nonCollapsible;
397

398
    /**
399
     * Gets/Sets the orientation.
400
     */
401
    @Input()
402
    public type: SplitterType = SplitterType.Horizontal;
31✔
403

404
    /**
405
     * Sets/gets the element order.
406
     */
407
    @HostBinding('style.order')
408
    @Input()
409
    public order!: number;
410

411
    /**
412
     * @hidden
413
     * @internal
414
     */
415
    @HostBinding('attr.tabindex')
416
    public get tabindex() {
417
        return this.resizeDisallowed ? null : 0;
220✔
418
    }
419

420
    /**
421
     * @hidden
422
     * @internal
423
     */
424
    @HostBinding('attr.aria-orientation')
425
    public get orientation() {
426
        return this.type === SplitterType.Horizontal ? 'horizontal' : 'vertical';
220✔
427
    }
428

429
    /**
430
     * @hidden
431
     * @internal
432
     */
433
    public get cursor() {
434
        if (this.resizeDisallowed) {
222✔
435
            return '';
35✔
436
        }
437
        return this.type === SplitterType.Horizontal ? 'col-resize' : 'row-resize';
187✔
438
    }
439

440
    /**
441
     * Sets/gets the `SplitPaneComponent` associated with the current `SplitBarComponent`.
442
     *
443
     * @memberof SplitBarComponent
444
     */
445
    @Input()
446
    public pane!: IgxSplitterPaneComponent;
447

448
    /**
449
     * Sets/Gets the `SplitPaneComponent` sibling components associated with the current `SplitBarComponent`.
450
     */
451
    @Input()
452
    public siblings!: Array<IgxSplitterPaneComponent>;
453

454
    /**
455
     * An event that is emitted whenever we start dragging the current `SplitBarComponent`.
456
     */
457
    @Output()
458
    public moveStart = new EventEmitter<IgxSplitterPaneComponent>();
31✔
459

460
    /**
461
     * An event that is emitted while we are dragging the current `SplitBarComponent`.
462
     */
463
    @Output()
464
    public moving = new EventEmitter<number>();
31✔
465

466
    @Output()
467
    public movingEnd = new EventEmitter<number>();
31✔
468

469
    /**
470
     * A temporary holder for the pointer coordinates.
471
     */
472
    private startPoint!: number;
473

474
    private interactionKeys = new Set('right down left up arrowright arrowdown arrowleft arrowup'.split(' '));
31✔
475

476
    /**
477
     * @hidden @internal
478
     */
479
    public get prevButtonHidden() {
480
        return this.siblings[0]?.collapsed && !this.siblings[1]?.collapsed;
220✔
481
    }
482

483
    /**
484
     * @hidden @internal
485
     */
486
    @HostListener('keydown', ['$event'])
487
    public keyEvent(event: KeyboardEvent) {
488
        const key = event.key.toLowerCase();
17✔
489
        const ctrl = event.ctrlKey;
17✔
490
        event.stopPropagation();
17✔
491
        if (this.interactionKeys.has(key)) {
17✔
492
            event.preventDefault();
17✔
493
        }
494
        switch (key) {
17!
495
            case 'arrowup':
496
            case 'up':
497
                if (this.type === SplitterType.Vertical) {
3✔
498
                    if (ctrl) {
3✔
499
                        this.onCollapsing(false);
2✔
500
                        break;
2✔
501
                    }
502
                    if (!this.resizeDisallowed) {
1✔
503
                        event.preventDefault();
1✔
504
                        this.moveStart.emit(this.pane);
1✔
505
                        this.moving.emit(10);
1✔
506
                    }
507
                }
508
                break;
1✔
509
            case 'arrowdown':
510
            case 'down':
511
                if (this.type === SplitterType.Vertical) {
5✔
512
                    if (ctrl) {
5✔
513
                        this.onCollapsing(true);
2✔
514
                        break;
2✔
515
                    }
516
                    if (!this.resizeDisallowed) {
3✔
517
                        event.preventDefault();
2✔
518
                        this.moveStart.emit(this.pane);
2✔
519
                        this.moving.emit(-10);
2✔
520
                    }
521
                }
522
                break;
3✔
523
            case 'arrowleft':
524
            case 'left':
525
                if (this.type === SplitterType.Horizontal) {
3✔
526
                    if (ctrl) {
3✔
527
                        this.onCollapsing(false);
2✔
528
                        break;
2✔
529
                    }
530
                    if (!this.resizeDisallowed) {
1✔
531
                        event.preventDefault();
1✔
532
                        this.moveStart.emit(this.pane);
1✔
533
                        this.moving.emit(10);
1✔
534
                    }
535
                }
536
                break;
1✔
537
            case 'arrowright':
538
            case 'right':
539
                if (this.type === SplitterType.Horizontal) {
6✔
540
                    if (ctrl) {
6✔
541
                        this.onCollapsing(true);
2✔
542
                        break;
2✔
543
                    }
544
                    if (!this.resizeDisallowed) {
4✔
545
                        event.preventDefault();
2✔
546
                        this.moveStart.emit(this.pane);
2✔
547
                        this.moving.emit(-10);
2✔
548
                    }
549
                }
550
                break;
4✔
551
            default:
552
                break;
×
553
        }
554
    }
555

556
    /**
557
     * @hidden @internal
558
     */
559
    public get dragDir() {
560
        return this.type === SplitterType.Horizontal ? DragDirection.VERTICAL : DragDirection.HORIZONTAL;
220✔
561
    }
562

563
    /**
564
     * @hidden @internal
565
     */
566
    public get nextButtonHidden() {
567
        return this.siblings[1]?.collapsed && !this.siblings[0]?.collapsed;
220✔
568
    }
569

570
    /**
571
     * @hidden @internal
572
     */
573
    public onDragStart(event: IDragStartEventArgs) {
574
        if (this.resizeDisallowed) {
1✔
575
            event.cancel = true;
1✔
576
            return;
1✔
577
        }
578
        this.startPoint = this.type === SplitterType.Horizontal ? event.startX : event.startY;
×
579
        this.moveStart.emit(this.pane);
×
580
    }
581

582
    /**
583
     * @hidden @internal
584
     */
585
    public onDragMove(event: IDragMoveEventArgs) {
586
        const isHorizontal = this.type === SplitterType.Horizontal;
×
587
        const curr = isHorizontal ? event.pageX : event.pageY;
×
588
        const delta = this.startPoint - curr;
×
589
        if (delta !== 0) {
×
590
            this.moving.emit(delta);
×
591
            event.cancel = true;
×
592
            event.owner.element.nativeElement.style.transform = '';
×
593
        }
594
    }
595

596
    public onDragEnd(event: any) {
597
        const isHorizontal = this.type === SplitterType.Horizontal;
×
598
        const curr = isHorizontal ? event.pageX : event.pageY;
×
599
        const delta = this.startPoint - curr;
×
600
        if (delta !== 0) {
×
601
            this.movingEnd.emit(delta);
×
602
        }
603
    }
604

605
    protected get resizeDisallowed() {
606
        const relatedTabs = this.siblings;
452✔
607
        return !!relatedTabs.find(x => x?.resizable === false || x?.collapsed === true);
856✔
608
    }
609

610
    /**
611
     * @hidden @internal
612
     */
613
    public onCollapsing(next: boolean) {
614
        const prevSibling = this.siblings[0];
15✔
615
        const nextSibling = this.siblings[1];
15✔
616
        let target;
617
        if (next) {
15✔
618
            // if next is clicked when prev pane is hidden, show prev pane, else hide next pane.
619
            target = prevSibling.collapsed ? prevSibling : nextSibling;
7✔
620
        } else {
621
            // if prev is clicked when next pane is hidden, show next pane, else hide prev pane.
622
            target = nextSibling.collapsed ? nextSibling : prevSibling;
8✔
623
        }
624
        target.toggle();
15✔
625
    }
626
}
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

© 2026 Coveralls, Inc