igniteui-angular-sovn
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
589 lines (530 loc) • 18.2 kB
text/typescript
import { DOCUMENT, NgFor, NgIf } from '@angular/common';
import { AfterContentInit, Component, ContentChildren, ElementRef, EventEmitter, HostBinding, HostListener, Inject, Input, Output, QueryList, forwardRef } from '@angular/core';
import { DragDirection, IDragMoveEventArgs, IDragStartEventArgs, IgxDragDirective, IgxDragIgnoreDirective } from '../directives/drag-drop/drag-drop.directive';
import { IgxSplitterPaneComponent } from './splitter-pane/splitter-pane.component';
/**
* An enumeration that defines the `SplitterComponent` panes orientation.
*/
export enum SplitterType {
Horizontal,
Vertical
}
export declare interface ISplitterBarResizeEventArgs {
pane: IgxSplitterPaneComponent;
sibling: IgxSplitterPaneComponent;
}
/**
* Provides a framework for a simple layout, splitting the view horizontally or vertically
* into multiple smaller resizable and collapsible areas.
*
* @igxModule IgxSplitterModule
*
* @igxParent Layouts
*
* @igxTheme igx-splitter-theme
*
* @igxKeywords splitter panes layout
*
* @igxGroup presentation
*
* @example
* ```html
* <igx-splitter>
* <igx-splitter-pane>
* ...
* </igx-splitter-pane>
* <igx-splitter-pane>
* ...
* </igx-splitter-pane>
* </igx-splitter>
* ```
*/
export class IgxSplitterComponent implements AfterContentInit {
/**
* Gets the list of splitter panes.
*
* @example
* ```typescript
* const panes = this.splitter.panes;
* ```
*/
public panes!: QueryList<IgxSplitterPaneComponent>;
/**
* @hidden
* @internal
*/
public cssClass = 'igx-splitter';
/**
* @hidden @internal
* Gets/Sets the `overflow` property of the current splitter.
*/
public overflow = 'hidden';
/**
* @hidden @internal
* Sets/Gets the `display` property of the current splitter.
*/
public display = 'flex';
/**
* @hidden
* @internal
*/
public get orientation() {
return this.type === SplitterType.Horizontal ? 'horizontal' : 'vertical';
}
/**
* Event fired when resizing of panes starts.
*
* @example
* ```html
* <igx-splitter (resizeStart)='resizeStart($event)'>
* <igx-splitter-pane>...</igx-splitter-pane>
* </igx-splitter>
* ```
*/
public resizeStart = new EventEmitter<ISplitterBarResizeEventArgs>();
/**
* Event fired when resizing of panes is in progress.
*
* @example
* ```html
* <igx-splitter (resizing)='resizing($event)'>
* <igx-splitter-pane>...</igx-splitter-pane>
* </igx-splitter>
* ```
*/
public resizing = new EventEmitter<ISplitterBarResizeEventArgs>();
/**
* Event fired when resizing of panes ends.
*
* @example
* ```html
* <igx-splitter (resizeEnd)='resizeEnd($event)'>
* <igx-splitter-pane>...</igx-splitter-pane>
* </igx-splitter>
* ```
*/
public resizeEnd = new EventEmitter<ISplitterBarResizeEventArgs>();
private _type: SplitterType = SplitterType.Horizontal;
/**
* @hidden @internal
* A field that holds the initial size of the main `IgxSplitterPaneComponent` in each pair of panes divided by a splitter bar.
*/
private initialPaneSize!: number;
/**
* @hidden @internal
* A field that holds the initial size of the sibling pane in each pair of panes divided by a gripper.
* @memberof SplitterComponent
*/
private initialSiblingSize!: number;
/**
* @hidden @internal
* The main pane in each pair of panes divided by a gripper.
*/
private pane!: IgxSplitterPaneComponent;
/**
* The sibling pane in each pair of panes divided by a splitter bar.
*/
private sibling!: IgxSplitterPaneComponent;
constructor( public document, private elementRef: ElementRef) { }
/**
* Gets/Sets the splitter orientation.
*
* @example
* ```html
* <igx-splitter [type]="type">...</igx-splitter>
* ```
*/
public get type() {
return this._type;
}
public set type(value) {
this._type = value;
this.resetPaneSizes();
this.panes?.notifyOnChanges();
}
/**
* @hidden @internal
* Gets the `flex-direction` property of the current `SplitterComponent`.
*/
public get direction(): string {
return this.type === SplitterType.Horizontal ? 'row' : 'column';
}
/** @hidden @internal */
public ngAfterContentInit(): void {
this.initPanes();
this.panes.changes.subscribe(() => {
this.initPanes();
});
}
/**
* @hidden @internal
* This method performs initialization logic when the user starts dragging the splitter bar between each pair of panes.
* @param pane - the main pane associated with the currently dragged bar.
*/
public onMoveStart(pane: IgxSplitterPaneComponent) {
const panes = this.panes.toArray();
this.pane = pane;
this.sibling = panes[panes.indexOf(this.pane) + 1];
const paneRect = this.pane.element.getBoundingClientRect();
this.initialPaneSize = this.type === SplitterType.Horizontal ? paneRect.width : paneRect.height;
const siblingRect = this.sibling.element.getBoundingClientRect();
this.initialSiblingSize = this.type === SplitterType.Horizontal ? siblingRect.width : siblingRect.height;
const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
this.resizeStart.emit(args);
}
/**
* @hidden @internal
* This method performs calculations concerning the sizes of each pair of panes when the bar between them is dragged.
* @param delta - The difference along the X (or Y) axis between the initial and the current point when dragging the bar.
*/
public onMoving(delta: number) {
const min = parseInt(this.pane.minSize, 10) || 0;
const max = parseInt(this.pane.maxSize, 10) || this.initialPaneSize + this.initialSiblingSize;
const minSibling = parseInt(this.sibling.minSize, 10) || 0;
const maxSibling = parseInt(this.sibling.maxSize, 10) || this.initialPaneSize + this.initialSiblingSize;
const paneSize = this.initialPaneSize - delta;
const siblingSize = this.initialSiblingSize + delta;
if (paneSize < min || paneSize > max || siblingSize < minSibling || siblingSize > maxSibling) {
return;
}
this.pane.dragSize = paneSize + 'px';
this.sibling.dragSize = siblingSize + 'px';
const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
this.resizing.emit(args);
}
public onMoveEnd(delta: number) {
const min = parseInt(this.pane.minSize, 10) || 0;
const max = parseInt(this.pane.maxSize, 10) || this.initialPaneSize + this.initialSiblingSize;
const minSibling = parseInt(this.sibling.minSize, 10) || 0;
const maxSibling = parseInt(this.sibling.maxSize, 10) || this.initialPaneSize + this.initialSiblingSize;
const paneSize = this.initialPaneSize - delta;
const siblingSize = this.initialSiblingSize + delta;
if (paneSize < min || paneSize > max || siblingSize < minSibling || siblingSize > maxSibling) {
return;
}
if (this.pane.isPercentageSize) {
// handle % resizes
const totalSize = this.getTotalSize();
const percentPaneSize = (paneSize / totalSize) * 100;
this.pane.size = percentPaneSize + '%';
} else {
// px resize
this.pane.size = paneSize + 'px';
}
if (this.sibling.isPercentageSize) {
// handle % resizes
const totalSize = this.getTotalSize();
const percentSiblingPaneSize = (siblingSize / totalSize) * 100;
this.sibling.size = percentSiblingPaneSize + '%';
} else {
// px resize
this.sibling.size = siblingSize + 'px';
}
this.pane.dragSize = null;
this.sibling.dragSize = null;
const args: ISplitterBarResizeEventArgs = { pane: this.pane, sibling: this.sibling };
this.resizeEnd.emit(args);
}
/** @hidden @internal */
public getPaneSiblingsByOrder(order: number, barIndex: number): Array<IgxSplitterPaneComponent> {
const panes = this.panes.toArray();
const prevPane = panes[order - barIndex - 1];
const nextPane = panes[order - barIndex];
const siblings = [prevPane, nextPane];
return siblings;
}
private getTotalSize() {
const computed = this.document.defaultView.getComputedStyle(this.elementRef.nativeElement);
const totalSize = this.type === SplitterType.Horizontal ? computed.getPropertyValue('width') : computed.getPropertyValue('height');
return parseFloat(totalSize);
}
/**
* @hidden @internal
* This method inits panes with properties.
*/
private initPanes() {
this.panes.forEach(pane => {
pane.owner = this;
if (this.type === SplitterType.Horizontal) {
pane.minWidth = pane.minSize ?? '0';
pane.maxWidth = pane.maxSize ?? '100%';
} else {
pane.minHeight = pane.minSize ?? '0';
pane.maxHeight = pane.maxSize ?? '100%';
}
});
this.assignFlexOrder();
if (this.panes.filter(x => x.collapsed).length > 0) {
// if any panes are collapsed, reset sizes.
this.resetPaneSizes();
}
}
/**
* @hidden @internal
* This method reset pane sizes.
*/
private resetPaneSizes() {
if (this.panes) {
// if type is changed runtime, should reset sizes.
this.panes.forEach(x => {
x.size = 'auto'
x.minWidth = '0';
x.maxWidth = '100%';
x.minHeight = '0';
x.maxHeight = '100%';
});
}
}
/**
* @hidden @internal
* This method assigns the order of each pane.
*/
private assignFlexOrder() {
let k = 0;
this.panes.forEach((pane: IgxSplitterPaneComponent) => {
pane.order = k;
k += 2;
});
}
}
export const SPLITTER_INTERACTION_KEYS = new Set('right down left up arrowright arrowdown arrowleft arrowup'.split(' '));
/**
* @hidden @internal
* Represents the draggable bar that visually separates panes and allows for changing their sizes.
*/
export class IgxSplitBarComponent {
/**
* Set css class to the host element.
*/
public cssClass = 'igx-splitter-bar-host';
/**
* Gets/Sets the orientation.
*/
public type: SplitterType = SplitterType.Horizontal;
/**
* Sets/gets the element order.
*/
public order!: number;
/**
* @hidden
* @internal
*/
public get tabindex() {
return this.resizeDisallowed ? null : 0;
}
/**
* @hidden
* @internal
*/
public get orientation() {
return this.type === SplitterType.Horizontal ? 'horizontal' : 'vertical';
}
/**
* @hidden
* @internal
*/
public get cursor() {
if (this.resizeDisallowed) {
return '';
}
return this.type === SplitterType.Horizontal ? 'col-resize' : 'row-resize';
}
/**
* Sets/gets the `SplitPaneComponent` associated with the current `SplitBarComponent`.
*
* @memberof SplitBarComponent
*/
public pane!: IgxSplitterPaneComponent;
/**
* Sets/Gets the `SplitPaneComponent` sibling components associated with the current `SplitBarComponent`.
*/
public siblings!: Array<IgxSplitterPaneComponent>;
/**
* An event that is emitted whenever we start dragging the current `SplitBarComponent`.
*/
public moveStart = new EventEmitter<IgxSplitterPaneComponent>();
/**
* An event that is emitted while we are dragging the current `SplitBarComponent`.
*/
public moving = new EventEmitter<number>();
public movingEnd = new EventEmitter<number>();
/**
* A temporary holder for the pointer coordinates.
*/
private startPoint!: number;
/**
* @hidden @internal
*/
public get prevButtonHidden() {
return this.siblings[0].collapsed && !this.siblings[1].collapsed;
}
/**
* @hidden @internal
*/
public keyEvent(event: KeyboardEvent) {
const key = event.key.toLowerCase();
const ctrl = event.ctrlKey;
event.stopPropagation();
if (SPLITTER_INTERACTION_KEYS.has(key)) {
event.preventDefault();
}
switch (key) {
case 'arrowup':
case 'up':
if (this.type === SplitterType.Vertical) {
if (ctrl) {
this.onCollapsing(false);
break;
}
if (!this.resizeDisallowed) {
event.preventDefault();
this.moveStart.emit(this.pane);
this.moving.emit(10);
}
}
break;
case 'arrowdown':
case 'down':
if (this.type === SplitterType.Vertical) {
if (ctrl) {
this.onCollapsing(true);
break;
}
if (!this.resizeDisallowed) {
event.preventDefault();
this.moveStart.emit(this.pane);
this.moving.emit(-10);
}
}
break;
case 'arrowleft':
case 'left':
if (this.type === SplitterType.Horizontal) {
if (ctrl) {
this.onCollapsing(false);
break;
}
if (!this.resizeDisallowed) {
event.preventDefault();
this.moveStart.emit(this.pane);
this.moving.emit(10);
}
}
break;
case 'arrowright':
case 'right':
if (this.type === SplitterType.Horizontal) {
if (ctrl) {
this.onCollapsing(true);
break;
}
if (!this.resizeDisallowed) {
event.preventDefault();
this.moveStart.emit(this.pane);
this.moving.emit(-10);
}
}
break;
default:
break;
}
}
/**
* @hidden @internal
*/
public get dragDir() {
return this.type === SplitterType.Horizontal ? DragDirection.VERTICAL : DragDirection.HORIZONTAL;
}
/**
* @hidden @internal
*/
public get nextButtonHidden() {
return this.siblings[1].collapsed && !this.siblings[0].collapsed;
}
/**
* @hidden @internal
*/
public onDragStart(event: IDragStartEventArgs) {
if (this.resizeDisallowed) {
event.cancel = true;
return;
}
this.startPoint = this.type === SplitterType.Horizontal ? event.startX : event.startY;
this.moveStart.emit(this.pane);
}
/**
* @hidden @internal
*/
public onDragMove(event: IDragMoveEventArgs) {
const isHorizontal = this.type === SplitterType.Horizontal;
const curr = isHorizontal ? event.pageX : event.pageY;
const delta = this.startPoint - curr;
if (delta !== 0) {
this.moving.emit(delta);
event.cancel = true;
event.owner.element.nativeElement.style.transform = '';
}
}
public onDragEnd(event: any) {
const isHorizontal = this.type === SplitterType.Horizontal;
const curr = isHorizontal ? event.pageX : event.pageY;
const delta = this.startPoint - curr;
if (delta !== 0) {
this.movingEnd.emit(delta);
}
}
protected get resizeDisallowed() {
const relatedTabs = this.siblings;
return !!relatedTabs.find(x => x.resizable === false || x.collapsed === true);
}
/**
* @hidden @internal
*/
public onCollapsing(next: boolean) {
const prevSibling = this.siblings[0];
const nextSibling = this.siblings[1];
let target;
if (next) {
// if next is clicked when prev pane is hidden, show prev pane, else hide next pane.
target = prevSibling.collapsed ? prevSibling : nextSibling;
} else {
// if prev is clicked when next pane is hidden, show next pane, else hide prev pane.
target = nextSibling.collapsed ? nextSibling : prevSibling;
}
target.toggle();
}
}