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

IgniteUI / igniteui-angular / 13948959277

19 Mar 2025 02:24PM UTC coverage: 91.581%. First build
13948959277

Pull #15422

github

web-flow
Merge e513a79a9 into e9e83f6b5
Pull Request #15422: Sizing panes corectly with minSize and the browser is shrinked - 18.2.x

12996 of 15234 branches covered (85.31%)

3 of 7 new or added lines in 1 file covered. (42.86%)

26324 of 28744 relevant lines covered (91.58%)

34017.89 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 { 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',
48
    standalone: true,
49
    imports: [NgFor, NgIf, forwardRef(() => IgxSplitBarComponent)]
58✔
50
})
51
export class IgxSplitterComponent implements AfterContentInit {
2✔
52
    /**
53
     * Gets the list of splitter panes.
54
     *
55
     * @example
56
     * ```typescript
57
     * const panes = this.splitter.panes;
58
     * ```
59
     */
60
    @ContentChildren(IgxSplitterPaneComponent, { read: IgxSplitterPaneComponent })
61
    public panes!: QueryList<IgxSplitterPaneComponent>;
62

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

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

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

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

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

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

119

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

133
    private _type: SplitterType = SplitterType.Horizontal;
22✔
134

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

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

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

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

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

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

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

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

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

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

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

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

235
        this.pane.dragSize = paneSize + 'px';
18✔
236
        this.sibling.dragSize = siblingSize + 'px';
18✔
237

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

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

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

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

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

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

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

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

294

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

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

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

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

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

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

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

396
    /**
397
     * Gets/Sets the orientation.
398
     */
399
    @Input()
400
    public type: SplitterType = SplitterType.Horizontal;
28✔
401

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

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

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

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

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

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

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

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

464
    @Output()
465
    public movingEnd = new EventEmitter<number>();
28✔
466

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

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

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

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

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

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

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

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

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

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

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