UNPKG

ninja-splitter

Version:

Convenient and easy to use splitter component for Angular 9+

424 lines (414 loc) 16 kB
import { EventEmitter, Component, ElementRef, Self, ViewChild, Input, Output, HostListener, HostBinding, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; class PositionService { static position(element) { const nativeEl = element.nativeElement; const elBCR = this.offset(nativeEl); let offsetParentBCR = { top: 0, left: 0 }; const offsetParentEl = this.parentOffsetEl(nativeEl); if (offsetParentEl !== this.document) { offsetParentBCR = this.offset(offsetParentEl); offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; } const boundingClientRect = nativeEl.getBoundingClientRect(); return { width: boundingClientRect.width || nativeEl.offsetWidth, height: boundingClientRect.height || nativeEl.offsetHeight, top: elBCR.top - offsetParentBCR.top, left: elBCR.left - offsetParentBCR.left, }; } static offset(element) { const nativeEl = element.nativeElement; const boundingClientRect = nativeEl.getBoundingClientRect(); return { width: boundingClientRect.width || nativeEl.offsetWidth, height: boundingClientRect.height || nativeEl.offsetHeight, top: boundingClientRect.top + (this.window.pageYOffset || this.document.documentElement.scrollTop), left: boundingClientRect.left + (this.window.pageXOffset || this.document.documentElement.scrollLeft), }; } static get window() { return window; } static get document() { return window.document; } static getStyle(nativeEl, cssProp) { if (nativeEl.currentStyle) { return nativeEl.currentStyle[cssProp]; } if (this.window.getComputedStyle) { return this.window.getComputedStyle(nativeEl)[cssProp]; } return nativeEl.style[cssProp]; } static isStaticPositioned(nativeEl) { return (this.getStyle(nativeEl, 'position') || 'static') === 'static'; } static parentOffsetEl(nativeEl) { let offsetParent = nativeEl.offsetParent || this.document; while (offsetParent && offsetParent !== this.document && this.isStaticPositioned(offsetParent)) { offsetParent = offsetParent.offsetParent; } return offsetParent || this.document; } } class NinjaSplitterComponent { constructor(self) { this.self = self; this.initialRatio = 0.5; this.primaryMinSize = 0; this.secondaryMinSize = 0; this.separatorThickness = 5; this.primaryToggledOff = false; this.secondaryToggledOff = false; this.localStorageKey = null; this.notifySizeDidChange = new EventEmitter(); this.notifyBeginResizing = new EventEmitter(); this.notifyEndedResizing = new EventEmitter(); this.dividerSize = 8.0; this.isResizing = false; } get isHorizontal() { return this.self.nativeElement.nodeName === 'HORIZONTAL-NINJA'; } get sizePropertyName() { if (this.self.nativeElement.nodeName.toUpperCase() === 'HORIZONTAL-NINJA') { return 'offsetHeight'; } else { return 'offsetWidth'; } } ngAfterViewInit() { this.checkBothToggledOff(); if (!this.primaryToggledOff && !this.secondaryToggledOff) { let ratio = this.initialRatio; if (this.localStorageKey != null) { const ratioStr = localStorage.getItem(this.localStorageKey); if (ratioStr != null) { ratio = +ratioStr; } } const size = ratio * this.self.nativeElement[this.sizePropertyName]; this.applySizeChange(size); } } ngOnChanges(changes) { this.checkBothToggledOff(); if (changes.primaryToggledOff) { if (changes.primaryToggledOff.currentValue === true) { this.primarySizeBeforeTogglingOff = this.getPrimarySize(); this.applySizeChange(0); } else { this.applySizeChange(this.primarySizeBeforeTogglingOff); } } else if (changes.secondaryToggledOff) { if (changes.secondaryToggledOff.currentValue === true) { this.primarySizeBeforeTogglingOff = this.getPrimarySize(); this.applySizeChange(this.self.nativeElement[this.sizePropertyName]); } else { this.applySizeChange(this.primarySizeBeforeTogglingOff); } } } getPrimarySize() { throw new Error("NinjaSplitterComponent shouldn't be instantiated. Override this method."); } getSecondarySize() { throw new Error("NinjaSplitterComponent shouldn't be instantiated. Override this method."); } dividerPosition(size) { throw new Error("NinjaSplitterComponent shouldn't be instantiated. Override this method."); } getAvailableSize() { return this.self.nativeElement[this.sizePropertyName] - this.dividerSize; } applySizeChange(size) { let primarySize = this.checkValidBounds(size, this.primaryMinSize, this.getAvailableSize() - this.secondaryMinSize); if (this.primaryToggledOff) { primarySize = 0; } else if (this.secondaryToggledOff) { primarySize = this.self.nativeElement[this.sizePropertyName]; } this.dividerPosition(primarySize); this.notifySizeDidChange.emit({ primary: this.getPrimarySize(), secondary: this.getSecondarySize(), }); } notifyWillChangeSize(resizing) { this.isResizing = resizing; this.notifyBeginResizing.emit(); } checkValidBounds(newSize, minSize, maxSize) { if (newSize >= minSize && newSize <= maxSize) { return newSize; } else if (maxSize - newSize > 0) { return minSize; } else { return maxSize; } } checkBothToggledOff() { if (this.primaryToggledOff && this.secondaryToggledOff) { throw new Error('You cannot toggle off both the primary and secondary component'); } } stopResizing() { this.isResizing = false; this.primaryComponent.nativeElement.style.cursor = 'auto'; this.secondaryComponent.nativeElement.style.cursor = 'auto'; if (this.localStorageKey != null) { const ratio = this.getPrimarySize() / this.self.nativeElement[this.sizePropertyName]; localStorage.setItem(this.localStorageKey, JSON.stringify(ratio)); } this.notifyEndedResizing.emit(); } onMouseup() { if (this.isResizing) { this.stopResizing(); } } onDocumentLeave(event) { if (this.isResizing) { event = event ? event : window.event; const from = event.relatedTarget || event.toElement; if (!from || from.nodeName === 'HTML') { this.stopResizing(); } } } } NinjaSplitterComponent.decorators = [ { type: Component, args: [{ selector: 'ninja-splitter', template: '', styles: [` :host { height: 100%; } `] },] } ]; NinjaSplitterComponent.ctorParameters = () => [ { type: ElementRef, decorators: [{ type: Self }] } ]; NinjaSplitterComponent.propDecorators = { primaryComponent: [{ type: ViewChild, args: ['primaryComponent', { static: true },] }], secondaryComponent: [{ type: ViewChild, args: ['secondaryComponent', { static: true },] }], initialRatio: [{ type: Input, args: ['primary-component-initialratio',] }], primaryMinSize: [{ type: Input, args: ['primary-minsize',] }], secondaryMinSize: [{ type: Input, args: ['secondary-minsize',] }], separatorThickness: [{ type: Input, args: ['separator-width-px',] }], primaryToggledOff: [{ type: Input, args: ['primary-component-toggle',] }], secondaryToggledOff: [{ type: Input, args: ['secondary-component-toggle',] }], localStorageKey: [{ type: Input, args: ['local-storage-key',] }], notifySizeDidChange: [{ type: Output, args: ['on-change',] }], notifyBeginResizing: [{ type: Output, args: ['on-begin-resizing',] }], notifyEndedResizing: [{ type: Output, args: ['on-ended-resizing',] }], onMouseup: [{ type: HostListener, args: ['mouseup',] }, { type: HostListener, args: ['touchend',] }], onDocumentLeave: [{ type: HostListener, args: ['document:mouseout', ['$event'],] }] }; class HorizontalNinjaSplitterComponent extends NinjaSplitterComponent { getPrimarySize() { return this.primaryComponent.nativeElement.offsetHeight; } getSecondarySize() { return this.secondaryComponent.nativeElement.offsetHeight; } dividerPosition(size) { const sizePct = (size / this.self.nativeElement[this.sizePropertyName]) * 100.0; this.primaryComponent.nativeElement.style.height = sizePct + '%'; this.secondaryComponent.nativeElement.style.height = `calc(${100 - sizePct}% - ${+!(this.primaryToggledOff || this.secondaryToggledOff) * this.separatorThickness}px)`; } onMousemove(event) { if (this.isResizing) { const coords = PositionService.offset(this.primaryComponent); this.applySizeChange(event.pageY - coords.top); return false; } } } HorizontalNinjaSplitterComponent.decorators = [ { type: Component, args: [{ selector: 'horizontal-ninja', template: ` <div #primaryComponent [hidden]="primaryToggledOff" class="upper ninja-com"> <ng-content select=".ninja-content-primary"></ng-content> </div> <ninja-separator #separator [hidden]="primaryToggledOff || secondaryToggledOff" [thickness]="separatorThickness" (notifyWillChangeSize)="notifyWillChangeSize($event)"> </ninja-separator> <div #secondaryComponent [hidden]="secondaryToggledOff" class="lower ninja-com"> <ng-content select=".ninja-content-secondary"></ng-content> </div> `, styles: [`:host { height: 100%; width: 100%; display: flex; flex-flow: column; } .ninja-com { height: calc(50% - 4px); }`] },] } ]; HorizontalNinjaSplitterComponent.propDecorators = { onMousemove: [{ type: HostListener, args: ['mousemove', ['$event'],] }] }; class NinjaSeparatorComponent { constructor() { this.horizontal = true; this.notifyWillChangeSize = new EventEmitter(); } get vertical() { return !this.horizontal; } get width() { if (this.horizontal) { return `inherit`; } else { return `${this.thickness}px`; } } get height() { if (this.vertical) { return `inherit`; } else { return `${this.thickness}px`; } } onMousedown() { this.notifyWillChangeSize.emit(true); } } NinjaSeparatorComponent.decorators = [ { type: Component, args: [{ selector: 'ninja-separator', template: ` <div class="handle"></div> `, styles: [":host{background-color:var(--ninja-separator-background-color, #fff);position:relative}:host.vertical{border-left:1px solid #ddd;cursor:var(--ninja-separator-vcursor, ew-resize)}:host.horizontal{border-top:1px solid #ddd;cursor:var(--ninja-separator-hcursor, ns-resize)}:host:hover{background-color:var(--ninja-separator-hover-background-color, #fafafa)}.handle{background-color:#eee}.handle.horizontal{margin:auto;width:35px;height:100%}.handle.vertical{position:absolute;top:calc(50% - 17px);width:100%;height:35px}\n"] },] } ]; NinjaSeparatorComponent.ctorParameters = () => []; NinjaSeparatorComponent.propDecorators = { thickness: [{ type: Input }], horizontal: [{ type: Input }, { type: HostBinding, args: ['class.horizontal',] }], notifyWillChangeSize: [{ type: Output }], vertical: [{ type: HostBinding, args: ['class.vertical',] }], width: [{ type: HostBinding, args: ['style.width',] }], height: [{ type: HostBinding, args: ['style.height',] }], onMousedown: [{ type: HostListener, args: ['mousedown',] }] }; class VerticalNinjaSplitterComponent extends NinjaSplitterComponent { getPrimarySize() { return this.primaryComponent.nativeElement.offsetWidth; } getSecondarySize() { return this.secondaryComponent.nativeElement.offsetWidth; } dividerPosition(size) { const sizePct = (size / this.self.nativeElement[this.sizePropertyName]) * 100; this.primaryComponent.nativeElement.style.width = sizePct + '%'; this.secondaryComponent.nativeElement.style.width = 'calc(' + (100 - sizePct) + '% - ' + (this.primaryToggledOff || this.secondaryToggledOff ? 0 : this.separatorThickness) + 'px)'; } onMousemove(event) { if (this.isResizing) { const coords = PositionService.offset(this.primaryComponent); this.applySizeChange(event.pageX - coords.left); } } } VerticalNinjaSplitterComponent.decorators = [ { type: Component, args: [{ selector: 'vertical-ninja', template: ` <div #primaryComponent [hidden]="primaryToggledOff" class="left ninja-com"> <ng-content select=".ninja-content-primary"></ng-content> </div> <ninja-separator [horizontal]="false" #separator [hidden]="primaryToggledOff || secondaryToggledOff" [thickness]="separatorThickness" (notifyWillChangeSize)="notifyWillChangeSize($event)"> </ninja-separator> <div #secondaryComponent [hidden]="secondaryToggledOff" class="right ninja-com"> <ng-content select=".ninja-content-secondary"></ng-content> </div> `, styles: [`:host { height: 100%; width: 100%; display: flex; } .ninja-com { width: calc(50% - 4px); }`] },] } ]; VerticalNinjaSplitterComponent.propDecorators = { outerContainer: [{ type: ViewChild, args: ['outer', { static: true },] }], onMousemove: [{ type: HostListener, args: ['mousemove', ['$event'],] }] }; function delayedInit() { return { ngModule: NinjaSplitterModule, providers: [] }; } class NinjaSplitterModule { } NinjaSplitterModule.forRoot = delayedInit(); NinjaSplitterModule.decorators = [ { type: NgModule, args: [{ imports: [CommonModule], declarations: [ HorizontalNinjaSplitterComponent, VerticalNinjaSplitterComponent, NinjaSeparatorComponent, NinjaSplitterComponent ], exports: [HorizontalNinjaSplitterComponent, VerticalNinjaSplitterComponent] },] } ]; export { HorizontalNinjaSplitterComponent, NinjaSeparatorComponent, NinjaSplitterComponent, NinjaSplitterModule, PositionService, VerticalNinjaSplitterComponent, delayedInit }; //# sourceMappingURL=ninja-splitter.js.map