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

IgniteUI / igniteui-angular / 29587398373

17 Jul 2026 02:17PM UTC coverage: 90.116% (-0.02%) from 90.135%
29587398373

Pull #15125

github

web-flow
Merge ac93ee972 into 8ebabbb38
Pull Request #15125: refactor(*): bundle styles with components

14920 of 17397 branches covered (85.76%)

Branch coverage included in aggregate %.

30030 of 32483 relevant lines covered (92.45%)

34494.72 hits per line

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

90.22
/projects/igniteui-angular/splitter/src/splitter/splitter.component.ts
1
import {
2
    AfterContentInit,
3
    Component,
4
    ContentChildren,
5
    ElementRef,
6
    EventEmitter,
7
    HostBinding,
8
    HostListener,
9
    Input,
10
    Output,
11
    QueryList,
12
    booleanAttribute,
13
    forwardRef,
14
    DOCUMENT,
15
    inject,
16
    ChangeDetectionStrategy,
17
    ViewEncapsulation,
18
} from '@angular/core';
19
import {
20
    DragDirection,
21
    IDragMoveEventArgs,
22
    IDragStartEventArgs,
23
    IgxDragDirective,
24
    IgxDragIgnoreDirective
25
} from 'igniteui-angular/directives';
26
import { IgxSplitterPaneComponent } from './splitter-pane/splitter-pane.component';
27

28
/**
29
 * An enumeration that defines the `SplitterComponent` panes orientation.
30
 */
31
export enum SplitterType {
3✔
32
    Horizontal,
3✔
33
    Vertical
3✔
34
}
35

36
export declare interface ISplitterBarResizeEventArgs {
37
    pane: IgxSplitterPaneComponent;
38
    sibling: IgxSplitterPaneComponent;
39
}
40

41
/**
42
 * Provides a framework for a simple layout, splitting the view horizontally or vertically
43
 * into multiple smaller resizable and collapsible areas.
44
 *
45
 * @igxModule IgxSplitterModule
46
 *
47
 * @igxParent Layouts
48
 *
49
 * @igxTheme igx-splitter-theme
50
 *
51
 * @igxKeywords splitter panes layout
52
 *
53
 * @igxGroup presentation
54
 *
55
 * @example
56
 * ```html
57
 * <igx-splitter>
58
 *  <igx-splitter-pane>
59
 *      ...
60
 *  </igx-splitter-pane>
61
 *  <igx-splitter-pane>
62
 *      ...
63
 *  </igx-splitter-pane>
64
 * </igx-splitter>
65
 * ```
66
 */
67
@Component({
68
    selector: 'igx-splitter',
69
    templateUrl: './splitter.component.html',
70
    styleUrl: 'splitter.component.css',
71
    encapsulation: ViewEncapsulation.None,
72
    changeDetection: ChangeDetectionStrategy.Eager,
73
    imports: [forwardRef(() => IgxSplitBarComponent)]
124✔
74
})
75
export class IgxSplitterComponent implements AfterContentInit {
3✔
76
    public document = inject(DOCUMENT);
25✔
77
    private elementRef = inject(ElementRef);
25✔
78

79
    /**
80
     * Gets the list of splitter panes.
81
     *
82
     * @example
83
     * ```typescript
84
     * const panes = this.splitter.panes;
85
     * ```
86
     */
87
    @ContentChildren(IgxSplitterPaneComponent, { read: IgxSplitterPaneComponent })
88
    public panes!: QueryList<IgxSplitterPaneComponent>;
89

90
    /**
91
    * @hidden
92
    * @internal
93
    */
94
    @HostBinding('class.igx-splitter')
95
    public cssClass = 'igx-splitter';
25✔
96

97
    /**
98
     * @hidden @internal
99
     * Gets/Sets the `overflow` property of the current splitter.
100
     */
101
    @HostBinding('style.overflow')
102
    public overflow = 'hidden';
25✔
103

104
    /**
105
     * @hidden @internal
106
     * Sets/Gets the `display` property of the current splitter.
107
     */
108
    @HostBinding('style.display')
109
    public display = 'flex';
25✔
110

111
    /**
112
     * @hidden
113
     * @internal
114
     */
115
    @HostBinding('attr.aria-orientation')
116
    public get orientation() {
117
        return this.type === SplitterType.Horizontal ? 'horizontal' : 'vertical';
175✔
118
    }
119

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

133
    /**
134
     * Event fired when resizing of panes is in progress.
135
     *
136
     * @example
137
     * ```html
138
     * <igx-splitter (resizing)='resizing($event)'>
139
     *  <igx-splitter-pane>...</igx-splitter-pane>
140
     * </igx-splitter>
141
     * ```
142
     */
143
    @Output()
144
    public resizing = new EventEmitter<ISplitterBarResizeEventArgs>();
25✔
145

146

147
    /**
148
     * Event fired when resizing of panes ends.
149
     *
150
     * @example
151
     * ```html
152
     * <igx-splitter (resizeEnd)='resizeEnd($event)'>
153
     *  <igx-splitter-pane>...</igx-splitter-pane>
154
     * </igx-splitter>
155
     * ```
156
     */
157
    @Output()
158
    public resizeEnd = new EventEmitter<ISplitterBarResizeEventArgs>();
25✔
159

160
    private _type: SplitterType = SplitterType.Horizontal;
25✔
161

162
    /**
163
     * @hidden @internal
164
     * A field that holds the initial size of the main splitter pane in each pair of panes divided by a splitter bar.
165
     */
166
    private initialPaneSize!: number;
167

168
    /**
169
     * @hidden @internal
170
     * A field that holds the initial size of the sibling pane in each pair of panes divided by a gripper.
171
     * @memberof SplitterComponent
172
     */
173
    private initialSiblingSize!: number;
174

175
    /**
176
     * @hidden @internal
177
     * The main pane in each pair of panes divided by a gripper.
178
     */
179
    private pane!: IgxSplitterPaneComponent;
180

181
    /**
182
     * The sibling pane in each pair of panes divided by a splitter bar.
183
     */
184
    private sibling!: IgxSplitterPaneComponent;
185
    /**
186
     * Gets/Sets the splitter orientation.
187
     *
188
     * @example
189
     * ```html
190
     * <igx-splitter [type]="type">...</igx-splitter>
191
     * ```
192
     */
193
    @Input()
194
    public get type() {
195
        return this._type;
708✔
196
    }
197
    public set type(value) {
198
        this._type = value;
30✔
199
        this.resetPaneSizes();
30✔
200
        this.panes?.notifyOnChanges();
30✔
201
    }
202

203
    /**
204
     * Sets the visibility of the handle and expanders in the splitter bar.
205
     * False by default
206
     *
207
     * @example
208
     * ```html
209
     * <igx-splitter [nonCollapsible]='true'>
210
     * </igx-splitter>
211
     * ```
212
     */
213
    @Input({ transform: booleanAttribute })
214
    public nonCollapsible = false; // Input to toggle showing/hiding expanders
25✔
215

216
    /**
217
     * @hidden @internal
218
     * Gets the `flex-direction` property of the current `SplitterComponent`.
219
     */
220
    @HostBinding('style.flex-direction')
221
    public get direction(): string {
222
        return this.type === SplitterType.Horizontal ? 'row' : 'column';
175✔
223
    }
224

225
    /** @hidden @internal */
226
    public ngAfterContentInit(): void {
227
        this.initPanes();
25✔
228
        this.panes.changes.subscribe(() => {
25✔
229
            this.initPanes();
9✔
230
        });
231
    }
232

233
    /**
234
     * @hidden @internal
235
     * This method performs  initialization logic when the user starts dragging the splitter bar between each pair of panes.
236
     * @param pane - the main pane associated with the currently dragged bar.
237
     */
238
    public onMoveStart(pane: IgxSplitterPaneComponent) {
239
        const panes = this.panes.toArray();
18✔
240
        this.pane = pane;
18✔
241
        this.sibling = panes[panes.indexOf(this.pane) + 1];
18✔
242

243
        const paneRect = this.pane.element.getBoundingClientRect();
18✔
244
        this.initialPaneSize = this.type === SplitterType.Horizontal ? paneRect.width : paneRect.height;
18✔
245

246
        const siblingRect = this.sibling.element.getBoundingClientRect();
18✔
247
        this.initialSiblingSize = this.type === SplitterType.Horizontal ? siblingRect.width : siblingRect.height;
18✔
248
        const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
18✔
249
        this.resizeStart.emit(args);
18✔
250
    }
251

252
    /**
253
     * @hidden @internal
254
     * This method performs calculations concerning the sizes of each pair of panes when the bar between them is dragged.
255
     * @param delta - The difference along the X (or Y) axis between the initial and the current point when dragging the bar.
256
     */
257
    public onMoving(delta: number) {
258
        const [ paneSize, siblingSize ] = this.calcNewSizes(delta);
18✔
259

260
        this.pane.dragSize = paneSize + 'px';
18✔
261
        this.sibling.dragSize = siblingSize + 'px';
18✔
262

263
        const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
18✔
264
        this.resizing.emit(args);
18✔
265
    }
266

267
    public onMoveEnd(delta: number) {
268
        let [ paneSize, siblingSize ] = this.calcNewSizes(delta);
6✔
269

270
        if (paneSize + siblingSize > this.getTotalSize() && delta < 0) {
6✔
271
            siblingSize = this.getTotalSize() - paneSize;
1✔
272
        } else if (paneSize + siblingSize > this.getTotalSize() && delta > 0) {
5!
273
            paneSize = this.getTotalSize() - siblingSize;
×
274
        }
275

276
        if (this.pane.isPercentageSize) {
6✔
277
            // handle % resizes
278
            const totalSize = this.getTotalSize();
5✔
279
            const percentPaneSize = (paneSize / totalSize) * 100;
5✔
280
            this.pane.size = percentPaneSize + '%';
5✔
281
        } else {
282
            // px resize
283
            this.pane.size = paneSize + 'px';
1✔
284
        }
285

286
        if (this.sibling.isPercentageSize) {
6✔
287
            // handle % resizes
288
            const totalSize = this.getTotalSize();
4✔
289
            const percentSiblingPaneSize = (siblingSize / totalSize) * 100;
4✔
290
            this.sibling.size = percentSiblingPaneSize + '%';
4✔
291
        } else {
292
            // px resize
293
            this.sibling.size = siblingSize + 'px';
2✔
294
        }
295
        this.pane.dragSize = null;
6✔
296
        this.sibling.dragSize = null;
6✔
297

298
        const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
6✔
299
        this.resizeEnd.emit(args);
6✔
300
    }
301

302
    /** @hidden @internal */
303
    public getPaneSiblingsByOrder(order: number, barIndex: number): Array<IgxSplitterPaneComponent> {
304
        const panes = this.panes.toArray();
224✔
305
        const prevPane = panes[order - barIndex - 1];
224✔
306
        const nextPane = panes[order - barIndex];
224✔
307
        const siblings = [prevPane, nextPane];
224✔
308
        return siblings;
224✔
309
    }
310

311
    private getTotalSize() {
312
        const computed = this.document.defaultView.getComputedStyle(this.elementRef.nativeElement);
22✔
313
        const totalSize = this.type === SplitterType.Horizontal ? computed.getPropertyValue('width') : computed.getPropertyValue('height');
22✔
314
        return parseFloat(totalSize);
22✔
315
    }
316

317

318
    /**
319
     * @hidden @internal
320
     * This method inits panes with properties.
321
     */
322
    private initPanes() {
323
        this.panes.forEach(pane => {
34✔
324
            pane.owner = this;
76✔
325
            if (this.type === SplitterType.Horizontal) {
76✔
326
                pane.minWidth = pane.minSize ?? '0';
57✔
327
                pane.maxWidth = pane.maxSize ?? '100%';
57✔
328
            } else {
329
                pane.minHeight = pane.minSize ?? '0';
19✔
330
                pane.maxHeight = pane.maxSize ?? '100%';
19✔
331
            }
332
        });
333
        this.assignFlexOrder();
34✔
334
        if (this.panes.filter(x => x.collapsed).length > 0) {
76✔
335
            // if any panes are collapsed, reset sizes.
336
            this.resetPaneSizes();
2✔
337
        }
338
    }
339

340
    /**
341
     * @hidden @internal
342
     * This method reset pane sizes.
343
     */
344
    private resetPaneSizes() {
345
        if (this.panes) {
32✔
346
            // if type is changed runtime, should reset sizes.
347
            this.panes.forEach(x => {
9✔
348
                x.size = 'auto'
21✔
349
                x.minWidth = '0';
21✔
350
                x.maxWidth = '100%';
21✔
351
                x.minHeight = '0';
21✔
352
                x.maxHeight = '100%';
21✔
353
            });
354
        }
355
    }
356

357
    /**
358
     * @hidden @internal
359
     * This method assigns the order of each pane.
360
     */
361
    private assignFlexOrder() {
362
        let k = 0;
34✔
363
        this.panes.forEach((pane: IgxSplitterPaneComponent) => {
34✔
364
            pane.order = k;
76✔
365
            k += 2;
76✔
366
        });
367
    }
368

369
    /**
370
     * @hidden @internal
371
     * Calculates new sizes for the panes based on move delta and initial sizes
372
     */
373
    private calcNewSizes(delta: number): [number, number] {
374
        const min = parseInt(this.pane.minSize, 10) || 0;
24✔
375
        const minSibling = parseInt(this.sibling.minSize, 10) || 0;
24✔
376
        const max = parseInt(this.pane.maxSize, 10) || this.initialPaneSize + this.initialSiblingSize - minSibling;
24✔
377
        const maxSibling = parseInt(this.sibling.maxSize, 10) || this.initialPaneSize + this.initialSiblingSize - min;
24✔
378

379
        if (delta < 0) {
24✔
380
            const maxPossibleDelta = Math.min(
16✔
381
                max - this.initialPaneSize,
382
                this.initialSiblingSize - minSibling,
383
            )
384
            delta = Math.min(maxPossibleDelta, Math.abs(delta)) * -1;
16✔
385
        } else {
386
            const maxPossibleDelta = Math.min(
8✔
387
                this.initialPaneSize - min,
388
                maxSibling - this.initialSiblingSize
389
            )
390
            delta = Math.min(maxPossibleDelta, Math.abs(delta));
8✔
391
        }
392
        return [this.initialPaneSize - delta, this.initialSiblingSize + delta];
24✔
393
    }
394
}
395

396
/**
397
 * @hidden @internal
398
 * Represents the draggable bar that visually separates panes and allows for changing their sizes.
399
 */
400
@Component({
401
    selector: 'igx-splitter-bar',
402
    templateUrl: './splitter-bar.component.html',
403
    changeDetection: ChangeDetectionStrategy.Eager,
404
    imports: [IgxDragDirective, IgxDragIgnoreDirective]
405
})
406
export class IgxSplitBarComponent {
3✔
407
    /**
408
     * Set css class to the host element.
409
     */
410
    @HostBinding('class.igx-splitter-bar-host')
411
    public cssClass = 'igx-splitter-bar-host';
32✔
412

413
     /**
414
     * Sets the visibility of the handle and expanders in the splitter bar.
415
     */
416
    @Input({ transform: booleanAttribute })
417
    public nonCollapsible;
418

419
    /**
420
     * Gets/Sets the orientation.
421
     */
422
    @Input()
423
    public type: SplitterType = SplitterType.Horizontal;
32✔
424

425
    /**
426
     * Sets/gets the element order.
427
     */
428
    @HostBinding('style.order')
429
    @Input()
430
    public order!: number;
431

432
    /**
433
     * @hidden
434
     * @internal
435
     */
436
    @HostBinding('attr.tabindex')
437
    public get tabindex() {
438
        return this.resizeDisallowed ? null : 0;
224✔
439
    }
440

441
    /**
442
     * @hidden
443
     * @internal
444
     */
445
    @HostBinding('attr.aria-orientation')
446
    public get orientation() {
447
        return this.type === SplitterType.Horizontal ? 'horizontal' : 'vertical';
224✔
448
    }
449

450
    /**
451
     * @hidden
452
     * @internal
453
     */
454
    public get cursor() {
455
        if (this.resizeDisallowed) {
226✔
456
            return '';
39✔
457
        }
458
        return this.type === SplitterType.Horizontal ? 'col-resize' : 'row-resize';
187✔
459
    }
460

461
    /**
462
     * Sets/gets the `SplitPaneComponent` associated with the current `SplitBarComponent`.
463
     *
464
     * @memberof SplitBarComponent
465
     */
466
    @Input()
467
    public pane!: IgxSplitterPaneComponent;
468

469
    /**
470
     * Sets/Gets the `SplitPaneComponent` sibling components associated with the current `SplitBarComponent`.
471
     */
472
    @Input()
473
    public siblings!: Array<IgxSplitterPaneComponent>;
474

475
    /**
476
     * An event that is emitted whenever we start dragging the current `SplitBarComponent`.
477
     */
478
    @Output()
479
    public moveStart = new EventEmitter<IgxSplitterPaneComponent>();
32✔
480

481
    /**
482
     * An event that is emitted while we are dragging the current `SplitBarComponent`.
483
     */
484
    @Output()
485
    public moving = new EventEmitter<number>();
32✔
486

487
    @Output()
488
    public movingEnd = new EventEmitter<number>();
32✔
489

490
    /**
491
     * A temporary holder for the pointer coordinates.
492
     */
493
    private startPoint!: number;
494

495
    private interactionKeys = new Set('right down left up arrowright arrowdown arrowleft arrowup'.split(' '));
32✔
496

497
    /**
498
     * @hidden @internal
499
     */
500
    public get prevButtonHidden() {
501
        return this.siblings[0]?.collapsed && !this.siblings[1]?.collapsed;
224✔
502
    }
503

504
    /**
505
     * @hidden @internal
506
     */
507
    @HostListener('keydown', ['$event'])
508
    public keyEvent(event: KeyboardEvent) {
509
        const key = event.key.toLowerCase();
17✔
510
        const ctrl = event.ctrlKey;
17✔
511
        event.stopPropagation();
17✔
512
        if (this.interactionKeys.has(key)) {
17✔
513
            event.preventDefault();
17✔
514
        }
515
        switch (key) {
17!
516
            case 'arrowup':
517
            case 'up':
518
                if (this.type === SplitterType.Vertical) {
3✔
519
                    if (ctrl) {
3✔
520
                        this.onCollapsing(false);
2✔
521
                        break;
2✔
522
                    }
523
                    if (!this.resizeDisallowed) {
1✔
524
                        event.preventDefault();
1✔
525
                        this.moveStart.emit(this.pane);
1✔
526
                        this.moving.emit(10);
1✔
527
                    }
528
                }
529
                break;
1✔
530
            case 'arrowdown':
531
            case 'down':
532
                if (this.type === SplitterType.Vertical) {
5✔
533
                    if (ctrl) {
5✔
534
                        this.onCollapsing(true);
2✔
535
                        break;
2✔
536
                    }
537
                    if (!this.resizeDisallowed) {
3✔
538
                        event.preventDefault();
2✔
539
                        this.moveStart.emit(this.pane);
2✔
540
                        this.moving.emit(-10);
2✔
541
                    }
542
                }
543
                break;
3✔
544
            case 'arrowleft':
545
            case 'left':
546
                if (this.type === SplitterType.Horizontal) {
3✔
547
                    if (ctrl) {
3✔
548
                        this.onCollapsing(false);
2✔
549
                        break;
2✔
550
                    }
551
                    if (!this.resizeDisallowed) {
1✔
552
                        event.preventDefault();
1✔
553
                        this.moveStart.emit(this.pane);
1✔
554
                        this.moving.emit(10);
1✔
555
                    }
556
                }
557
                break;
1✔
558
            case 'arrowright':
559
            case 'right':
560
                if (this.type === SplitterType.Horizontal) {
6✔
561
                    if (ctrl) {
6✔
562
                        this.onCollapsing(true);
2✔
563
                        break;
2✔
564
                    }
565
                    if (!this.resizeDisallowed) {
4✔
566
                        event.preventDefault();
2✔
567
                        this.moveStart.emit(this.pane);
2✔
568
                        this.moving.emit(-10);
2✔
569
                    }
570
                }
571
                break;
4✔
572
            default:
573
                break;
×
574
        }
575
    }
576

577
    /**
578
     * @hidden @internal
579
     */
580
    public get dragDir() {
581
        return this.type === SplitterType.Horizontal ? DragDirection.VERTICAL : DragDirection.HORIZONTAL;
224✔
582
    }
583

584
    /**
585
     * @hidden @internal
586
     */
587
    public get nextButtonHidden() {
588
        return this.siblings[1]?.collapsed && !this.siblings[0]?.collapsed;
224✔
589
    }
590

591
    /**
592
     * @hidden @internal
593
     */
594
    public onDragStart(event: IDragStartEventArgs) {
595
        if (this.resizeDisallowed) {
1✔
596
            event.cancel = true;
1✔
597
            return;
1✔
598
        }
599
        this.startPoint = this.type === SplitterType.Horizontal ? event.startX : event.startY;
×
600
        this.moveStart.emit(this.pane);
×
601
    }
602

603
    /**
604
     * @hidden @internal
605
     */
606
    public onDragMove(event: IDragMoveEventArgs) {
607
        const isHorizontal = this.type === SplitterType.Horizontal;
×
608
        const curr = isHorizontal ? event.pageX : event.pageY;
×
609
        const delta = this.startPoint - curr;
×
610
        if (delta !== 0) {
×
611
            this.moving.emit(delta);
×
612
            event.cancel = true;
×
613
            event.owner.element.nativeElement.style.transform = '';
×
614
        }
615
    }
616

617
    public onDragEnd(event: any) {
618
        const isHorizontal = this.type === SplitterType.Horizontal;
×
619
        const curr = isHorizontal ? event.pageX : event.pageY;
×
620
        const delta = this.startPoint - curr;
×
621
        if (delta !== 0) {
×
622
            this.movingEnd.emit(delta);
×
623
        }
624
    }
625

626
    protected get resizeDisallowed() {
627
        const relatedTabs = this.siblings;
460✔
628
        return !!relatedTabs.find(x => x?.resizable === false || x?.collapsed === true);
872✔
629
    }
630

631
    /**
632
     * @hidden @internal
633
     */
634
    public onCollapsing(next: boolean) {
635
        const prevSibling = this.siblings[0];
15✔
636
        const nextSibling = this.siblings[1];
15✔
637
        let target;
638
        if (next) {
15✔
639
            // if next is clicked when prev pane is hidden, show prev pane, else hide next pane.
640
            target = prevSibling.collapsed ? prevSibling : nextSibling;
7✔
641
        } else {
642
            // if prev is clicked when next pane is hidden, show next pane, else hide prev pane.
643
            target = nextSibling.collapsed ? nextSibling : prevSibling;
8✔
644
        }
645
        target.toggle();
15✔
646
    }
647
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc