Skip to content

Instantly share code, notes, and snippets.

@bojidaryovchev
Last active March 8, 2023 14:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bojidaryovchev/78fe87dc59e2ef3e913c80f90c2fa747 to your computer and use it in GitHub Desktop.
Save bojidaryovchev/78fe87dc59e2ef3e913c80f90c2fa747 to your computer and use it in GitHub Desktop.
Inspired by angular-split, made able to handle flex-based areas
import {
AfterViewInit,
Component,
ContentChildren,
ElementRef,
HostBinding,
HostListener,
Input,
QueryList,
} from '@angular/core';
type SplitAreaSizeType = 'pixel' | 'percent' | 'flex';
@Component({
selector: 'app-split-area',
template: `
<section [style.padding]="padding">
<ng-content></ng-content>
</section>
`,
styles: [
`
:host {
overflow: hidden;
}
section {
height: 100%;
}
`
],
})
export class SplitAreaComponent implements AfterViewInit {
@Input() sizeType?: SplitAreaSizeType;
@Input() size?: string;
@Input() padding?: string;
constructor(private _elementRef: ElementRef<HTMLElement>) {}
ngAfterViewInit(): void {
switch (this.sizeType) {
case 'pixel':
this.setFlex({
flexBasis: `${this.size}px`,
});
break;
case 'percent':
this.setFlex({
flexBasis: `${this.size}%`,
});
break;
case 'flex':
this.setFlex({
flexGrow: this.size,
});
break;
default:
this.setFlex({
flexBasis: `${this.width}px`,
});
break;
}
}
get width(): number {
return this.boundingClientRect.width;
}
private get boundingClientRect(): DOMRect {
return this._elementRef.nativeElement.getBoundingClientRect();
}
setFlex({
flexBasis = '0',
flexGrow = '0',
flexShrink = '1',
}: {
flexBasis?: string;
flexGrow?: string;
flexShrink?: string;
}): void {
this._elementRef.nativeElement.style.setProperty('flex-basis', flexBasis || '');
this._elementRef.nativeElement.style.setProperty('flex-grow', flexGrow || '');
this._elementRef.nativeElement.style.setProperty('flex-shrink', flexShrink || '');
}
}
@Component({
selector: 'app-split-gutter',
template: `
<div class="gutter-icon"></div>
`,
styles: [
`
:host {
display: block;
flex-basis: 12px;
flex-grow: 0;
flex-shrink: 0;
cursor: col-resize;
border-left: 1px solid lightgray;
border-right: 1px solid lightgray;
}
:host.active {
border-left: 1px solid lightblue;
border-right: 1px solid lightblue;
}
.gutter-icon {
width: 100%;
height: 100%;
background-position: center center;
background-repeat: no-repeat;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==');
}
`
]
})
export class SplitGutterComponent implements AfterViewInit {
private leftArea?: SplitAreaComponent;
private rightArea?: SplitAreaComponent;
private eventX?: number;
private leftAreaWidth?: number;
private rightAreaWidth?: number;
@HostBinding('class.active')
active?: boolean;
constructor(private splitComponent: SplitComponent) {}
ngAfterViewInit(): void {
if (!this.splitComponent.areas || !this.splitComponent.gutters) {
return;
}
const gutters = this.splitComponent.gutters.toArray();
const areas = this.splitComponent.areas.toArray();
const index = gutters.indexOf(this);
this.leftArea = areas[index];
this.rightArea = areas[index + 1];
}
setAreaWidths(): void {
if (!this.leftArea || !this.rightArea) {
return;
}
this.leftAreaWidth = this.leftArea.width;
this.rightAreaWidth = this.rightArea.width;
}
setFlex(): void {
this.setLeftAreaFlex(this.leftAreaWidth!);
this.setRightAreaFlex(this.rightAreaWidth!);
}
@HostListener('mousedown', ['$event'])
onMouseDown(event: MouseEvent): void {
event.preventDefault();
this.active = true;
this.eventX = event.clientX;
if (!this.splitComponent.gutters) {
return;
}
this.splitComponent.gutters.forEach((gutter) => {
gutter.setAreaWidths();
});
this.splitComponent.gutters.forEach((gutter) => {
gutter.setFlex();
});
}
@HostListener('window:mousemove', ['$event'])
onMouseMove(event: MouseEvent): void {
if (
this.eventX === undefined ||
!this.leftArea ||
!this.rightArea ||
this.leftAreaWidth === undefined ||
this.rightAreaWidth === undefined
) {
return;
}
let delta = this.eventX - event.clientX;
if (delta < -this.rightAreaWidth) {
delta = -this.rightAreaWidth;
}
if (delta > this.leftAreaWidth) {
delta = this.leftAreaWidth;
}
const leftAreaWidth = this.leftAreaWidth - delta;
const rightAreaWidth = this.rightAreaWidth + delta;
this.setLeftAreaFlex(leftAreaWidth);
this.setRightAreaFlex(rightAreaWidth);
}
@HostListener('window:mouseup')
onMouseUp(): void {
delete this.active;
delete this.eventX;
delete this.leftAreaWidth;
delete this.rightAreaWidth;
}
private setLeftAreaFlex(leftAreaWidth: number): void {
if (!this.leftArea) {
return;
}
this.leftArea.setFlex({
flexBasis: `${leftAreaWidth}px`,
...(this.leftArea.sizeType === 'flex' ? { flexGrow: this.leftArea.size } : {}),
});
}
private setRightAreaFlex(rightAreaWidth: number): void {
if (!this.rightArea) {
return;
}
this.rightArea.setFlex({
flexBasis: `${rightAreaWidth}px`,
...(this.rightArea.sizeType === 'flex' ? { flexGrow: this.rightArea.size } : {}),
});
}
}
@Component({
selector: 'app-split',
template: `
<ng-content></ng-content>
`,
styles: [
`
:host {
flex: 1;
display: flex;
}
`
],
})
export class SplitComponent {
@ContentChildren(SplitAreaComponent) areas?: QueryList<SplitAreaComponent>;
@ContentChildren(SplitGutterComponent) gutters?: QueryList<SplitGutterComponent>;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment