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

IgniteUI / igniteui-angular / 15849983933

24 Jun 2025 12:07PM UTC coverage: 91.421% (-0.02%) from 91.437%
15849983933

Pull #15925

github

web-flow
fix(igxGrid): Use default scheduler when throttling. (#15884)
Pull Request #15925: Mass Merge 20.0.x to master

13388 of 15716 branches covered (85.19%)

59 of 84 new or added lines in 13 files covered. (70.24%)

40 existing lines in 4 files now uncovered.

27068 of 29608 relevant lines covered (91.42%)

36679.68 hits per line

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

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

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

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

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

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

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

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

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

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

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

117

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

131
    private _type: SplitterType = SplitterType.Horizontal;
22✔
132

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

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

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

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

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

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

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

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

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

216
        const paneRect = this.pane.element.getBoundingClientRect();
18✔
217
        this.initialPaneSize = this.type === SplitterType.Horizontal ? paneRect.width : paneRect.height;
18✔
218

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

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

233
        this.pane.dragSize = paneSize + 'px';
18✔
234
        this.sibling.dragSize = siblingSize + 'px';
18✔
235

236
        const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
18✔
237
        this.resizing.emit(args);
18✔
238
    }
239

240
    public onMoveEnd(delta: number) {
241
        let [ paneSize, siblingSize ] = this.calcNewSizes(delta);
6✔
242

243
        if (paneSize + siblingSize > this.getTotalSize() && delta < 0) {
6!
UNCOV
244
            paneSize = this.getTotalSize();
×
UNCOV
245
            siblingSize = 0;
×
246
        } else if(paneSize + siblingSize > this.getTotalSize() && delta > 0) {
6!
247
            paneSize = 0;
×
248
            siblingSize = this.getTotalSize();
×
249
        }
250

251
        if (this.pane.isPercentageSize) {
6✔
252
            // handle % resizes
253
            const totalSize = this.getTotalSize();
3✔
254
            const percentPaneSize = (paneSize / totalSize) * 100;
3✔
255
            this.pane.size = percentPaneSize + '%';
3✔
256
        } else {
257
            // px resize
258
            this.pane.size = paneSize + 'px';
3✔
259
        }
260

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

273
        const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
6✔
274
        this.resizeEnd.emit(args);
6✔
275
    }
276

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

286
    private getTotalSize() {
287
        const computed = this.document.defaultView.getComputedStyle(this.elementRef.nativeElement);
23✔
288
        const totalSize = this.type === SplitterType.Horizontal ? computed.getPropertyValue('width') : computed.getPropertyValue('height');
23✔
289
        return parseFloat(totalSize);
23✔
290
    }
291

292

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

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

332
    /**
333
     * @hidden @internal
334
     * This method assigns the order of each pane.
335
     */
336
    private assignFlexOrder() {
337
        let k = 0;
31✔
338
        this.panes.forEach((pane: IgxSplitterPaneComponent) => {
31✔
339
            pane.order = k;
69✔
340
            k += 2;
69✔
341
        });
342
    }
343

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

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

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

387
     /**
388
     * Sets the visibility of the handle and expanders in the splitter bar.
389
     */
390
    @Input({ transform: booleanAttribute })
391
    public nonCollapsible;
392

393
    /**
394
     * Gets/Sets the orientation.
395
     */
396
    @Input()
397
    public type: SplitterType = SplitterType.Horizontal;
28✔
398

399
    /**
400
     * Sets/gets the element order.
401
     */
402
    @HostBinding('style.order')
403
    @Input()
404
    public order!: number;
405

406
    /**
407
     * @hidden
408
     * @internal
409
     */
410
    @HostBinding('attr.tabindex')
411
    public get tabindex() {
412
        return this.resizeDisallowed ? null : 0;
198✔
413
    }
414

415
    /**
416
     * @hidden
417
     * @internal
418
     */
419
    @HostBinding('attr.aria-orientation')
420
    public get orientation() {
421
        return this.type === SplitterType.Horizontal ? 'horizontal' : 'vertical';
198✔
422
    }
423

424
    /**
425
     * @hidden
426
     * @internal
427
     */
428
    public get cursor() {
429
        if (this.resizeDisallowed) {
200✔
430
            return '';
39✔
431
        }
432
        return this.type === SplitterType.Horizontal ? 'col-resize' : 'row-resize';
161✔
433
    }
434

435
    /**
436
     * Sets/gets the `SplitPaneComponent` associated with the current `SplitBarComponent`.
437
     *
438
     * @memberof SplitBarComponent
439
     */
440
    @Input()
441
    public pane!: IgxSplitterPaneComponent;
442

443
    /**
444
     * Sets/Gets the `SplitPaneComponent` sibling components associated with the current `SplitBarComponent`.
445
     */
446
    @Input()
447
    public siblings!: Array<IgxSplitterPaneComponent>;
448

449
    /**
450
     * An event that is emitted whenever we start dragging the current `SplitBarComponent`.
451
     */
452
    @Output()
453
    public moveStart = new EventEmitter<IgxSplitterPaneComponent>();
28✔
454

455
    /**
456
     * An event that is emitted while we are dragging the current `SplitBarComponent`.
457
     */
458
    @Output()
459
    public moving = new EventEmitter<number>();
28✔
460

461
    @Output()
462
    public movingEnd = new EventEmitter<number>();
28✔
463

464
    /**
465
     * A temporary holder for the pointer coordinates.
466
     */
467
    private startPoint!: number;
468

469
    private interactionKeys = new Set('right down left up arrowright arrowdown arrowleft arrowup'.split(' '));
28✔
470

471
    /**
472
     * @hidden @internal
473
     */
474
    public get prevButtonHidden() {
475
        return this.siblings[0].collapsed && !this.siblings[1].collapsed;
198✔
476
    }
477

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

551
    /**
552
     * @hidden @internal
553
     */
554
    public get dragDir() {
555
        return this.type === SplitterType.Horizontal ? DragDirection.VERTICAL : DragDirection.HORIZONTAL;
198✔
556
    }
557

558
    /**
559
     * @hidden @internal
560
     */
561
    public get nextButtonHidden() {
562
        return this.siblings[1].collapsed && !this.siblings[0].collapsed;
198✔
563
    }
564

565
    /**
566
     * @hidden @internal
567
     */
568
    public onDragStart(event: IDragStartEventArgs) {
569
        if (this.resizeDisallowed) {
1✔
570
            event.cancel = true;
1✔
571
            return;
1✔
572
        }
UNCOV
573
        this.startPoint = this.type === SplitterType.Horizontal ? event.startX : event.startY;
×
UNCOV
574
        this.moveStart.emit(this.pane);
×
575
    }
576

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

591
    public onDragEnd(event: any) {
UNCOV
592
        const isHorizontal = this.type === SplitterType.Horizontal;
×
UNCOV
593
        const curr = isHorizontal ? event.pageX : event.pageY;
×
UNCOV
594
        const delta = this.startPoint - curr;
×
595
        if (delta !== 0) {
×
596
            this.movingEnd.emit(delta);
×
597
        }
598
    }
599

600
    protected get resizeDisallowed() {
601
        const relatedTabs = this.siblings;
408✔
602
        return !!relatedTabs.find(x => x.resizable === false || x.collapsed === true);
768✔
603
    }
604

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