ninja-splitter
Version:
Convenient and easy to use splitter component for Angular 9+
424 lines (414 loc) • 16 kB
JavaScript
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