ng-split-areas
Version:
Splitting views in Angular horizontally / vertically with configurable size-restrictions
1,205 lines (1,202 loc) • 72.1 kB
JavaScript
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Directive, ElementRef, EventEmitter, HostBinding, HostListener, Input, NgModule, NgZone, Output, Renderer2, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
/**
* Calculation logic for area sizes
*/
class AreaSizeCalculation {
/**
* Modifies sizes of areas in the list by totalSize
*
* @param areas list of areas to be modified
* @param sourceAreaIndex index of area that triggered modification (will not be modified [a second time])
* @param totalSize size to be added / subtracted to / from areas within the list
* @param containerSizePixel size of the container in pixel
* @param gutterSizePxPerVisibleComponent size of the gutter in pixel per visible component
* @param direction direction of modification starting from sourceAreaIndex
* @returns size left, that couldnt be added / subtracted to / from the areas within the list
*/
static modifyAreaSizes(areas, sourceAreaIndex, totalSize, containerSizePixel, gutterSizePxPerVisibleComponent, direction) {
let result = totalSize;
// if there is nothing to modify (-> totalSize is zero)
if (result === 0) {
// there's nothing to do
return result;
}
// if the area-list is empty
if (!areas || areas.length === 0) {
// there's nothing to do
return result;
}
// if there is only 1 area in the area list
if (areas.length <= 1) {
// there's nothing to do, because the list contains the source area only
return result;
}
// modifying direction = to the right
if (direction === 'right') {
// sourceArea is rightmost area
if (sourceAreaIndex + 1 === areas.length) {
// there's nothing to do
return result;
}
// iterate the area-list to the right (starting with the component next to sourceAreaIndex)
for (let i = sourceAreaIndex + 1; i < areas.length; i++) {
// area at current index
const currentArea = areas[i];
// left space to be subtracted
result = AreaSizeCalculation.modifyAreaSize(currentArea, result, containerSizePixel, gutterSizePxPerVisibleComponent);
// if there is no space left to modify
if (result === 0) {
// stop iterating
break;
}
}
}
else if (direction === 'left') { // modifying direction = to the left
// sourceArea is leftmost area
if (sourceAreaIndex === 0) {
// there's nothing to do
return result;
}
// iterate the area-list to the left (starting with the component next to sourceAreaIndex)
for (let i = sourceAreaIndex - 1; i >= 0; i--) {
// area at current index
const currentArea = areas[i];
// left space to be subtracted
result = AreaSizeCalculation.modifyAreaSize(currentArea, result, containerSizePixel, gutterSizePxPerVisibleComponent);
// if there is no space left to modify
if (result === 0) {
// stop iterating
break;
}
}
}
return result;
}
/**
* Modifies size of the area by given size
*
* @param area Area to be modified
* @param sizeToBeModified Size to be added / subtracted to / from the area
* @param containerSize Size of the container
* @param gutterSizePxPerVisibleComponent gutter size in px per visible component
*
* @returns left size that couldn't be added / subtracted to / from
* the area (through min / max restrictions)
*/
static modifyAreaSize(area, sizeToBeModified, containerSize, gutterSizePxPerVisibleComponent) {
// size left to be modified
let result = sizeToBeModified;
// is current action a subtraction
const isSubtract = sizeToBeModified < 0;
// there's size to be taken away (subtraction
if (isSubtract) {
// adding size taken away from the area
result += this.subtractSizeFromArea(area, Math.abs(sizeToBeModified), containerSize, gutterSizePxPerVisibleComponent);
}
else {
// subtracting size taken away from the area
result -= this.addSizeToArea(area, sizeToBeModified, containerSize, gutterSizePxPerVisibleComponent);
}
return result;
}
/**
* Subtracts size of the area by given size
*
* @param area Area to be subtracted it's size
* @param sizeToBeSubtracted Size to be subtracted from the area
* @param containerSize Size of the container
* @param gutterSizePxPerVisibleComponent size of the gutter in px per visible component
* @returns size that was subtracted
*/
static subtractSizeFromArea(area, sizeToBeSubtracted, containerSize, gutterSizePxPerVisibleComponent) {
// size that was subtracted
let result = 0;
// if the areas size is already 0
if (area.size === 0) {
// there's nothing to do
return result;
}
// maximum size to be taken away from the area
let maxSizeToBeTakenAway = area.size;
// area has a minimum pixel restriction
if (area.minSizePx) {
// minimum size of area (in pcnt of container size)
const minSizeInPcntOfCurrentContainer = Math.ceil(area.minSizePx + gutterSizePxPerVisibleComponent) / containerSize;
// maximum size to be taken away from area = current-size - min-size
maxSizeToBeTakenAway = area.size - minSizeInPcntOfCurrentContainer;
}
// if there is no size available to be taken away
if (maxSizeToBeTakenAway <= 0) {
// there's nothing to do
return result;
}
// size to be actually taken away from the area
let sizeToBeTakenAway = sizeToBeSubtracted;
// if the size to be taken away is > than the max size that can be taken away
if (sizeToBeTakenAway > maxSizeToBeTakenAway) {
// size to be taken away = max
sizeToBeTakenAway = maxSizeToBeTakenAway;
}
// subtract size from current area's size
area.size -= sizeToBeTakenAway;
// set the result to the size taken away;
result = sizeToBeTakenAway;
// return what was actually taken away from the area
return result;
}
/**
* Adds size to the area by given size
*
* @param area Area to be added something to it's size
* @param sizeToBeAdded Size to be added to the area
* @param containerSize Size of the container
* @param gutterSizePxPerVisibleComponent size of the gutter in px per visible component
* @returns size that was subtracted
*/
static addSizeToArea(area, sizeToBeAdded, containerSize, gutterSizePxPerVisibleComponent) {
// size that was added
let result = 0;
// if the areas size is 0
if (area.size === 0) {
// there's nothing to do
return result;
}
//maximum size to be added (default: Number.MAX_VALUE)
let maxSizeToBeAdded = Number.MAX_VALUE;
// area has a maximum pixel restriction
if (area.maxSizePx) {
// maximum size of area (in pcnt of container size)
const maxSizePcnt = Math.ceil(area.maxSizePx + gutterSizePxPerVisibleComponent) / containerSize;
// if area size is already greater or equal max-size
if (area.size >= maxSizePcnt) {
// we cannot add any space to this area
maxSizeToBeAdded = 0;
}
else {
// we can add the amount until the area reaches max size
maxSizeToBeAdded = maxSizePcnt - area.size;
}
}
// if there is no size available to be added
if (maxSizeToBeAdded === 0) {
// there's nothing to do
return result;
}
// size to be actually added to the area
let sizeToBeActuallyAdded = sizeToBeAdded;
// if the size to be added is > than the max size that can be added
if (sizeToBeActuallyAdded > maxSizeToBeAdded) {
// size to be added = max
sizeToBeAdded = maxSizeToBeAdded;
}
// add size to current area's size
area.size += sizeToBeAdded;
// set the result to the size added
result = sizeToBeAdded;
// return what was actually added to the area
return result;
}
/**
* Calculates area sizes according to given IAreaSizeCalculationOptions
*/
calculate(opts) {
// if calculate was triggered by a drag-and-drop action
if (opts.calculationSource
&& opts.calculationSource.isDragAndDrop) {
// handle dragging
this.handleDragAndDrop(opts);
}
else if (opts.calculationSource
&& opts.calculationSource.isWindowResize) {
// handle window-resize
this.handleWindowResize(opts);
}
else {
// handle normal layout calculations
this.handleNormalLayout(opts);
}
}
/**
* Calculates area sizes when gutters are dragged
* (according to given AreaDragAndDrop inside IAreaSizeCalculationOptions)
*/
handleDragAndDrop(opts) {
// area-drag-and-drop object inside calculationSource
const areaDragAndDrop = opts.calculationSource;
// put dragged areas from drag-and-drop into a 2 element list
const draggedAreas = [areaDragAndDrop.areaA, areaDragAndDrop.areaB];
// calculate complete size in px of both dragged areas
const draggedAreasSizePx = (areaDragAndDrop.areaA.size + areaDragAndDrop.areaB.size)
* (opts.containerSizePx ? opts.containerSizePx : 0);
// both areas are visible after drag (size > 0) and the gutter was moved from it's original position
if (areaDragAndDrop.areaA.size > 0 && areaDragAndDrop.areaB.size > 0 && areaDragAndDrop.offsetPixel) {
// both areas (left side and right side) have min size px configured
if (areaDragAndDrop.areaA.minSizePx && areaDragAndDrop.areaB.minSizePx) {
// sum of min-size of both areas
const totalMinSizePx = areaDragAndDrop.areaA.minSizePx + areaDragAndDrop.areaB.minSizePx;
// size in px that can be used by the components (=> without gutter size)
const draggedAreasSizePxWithoutGutter = draggedAreasSizePx - (opts.gutterSizePx ? opts.gutterSizePx : 0);
// both components won't fit into given size (because the sum of their min-sizes is > total available space)
if (totalMinSizePx > draggedAreasSizePxWithoutGutter) {
// gutter was moved left (-> offsetPixel = start - end = a positive number)
if (areaDragAndDrop.offsetPixel > 0) {
//left side wins
areaDragAndDrop.areaA.size += areaDragAndDrop.areaB.size;
//right side is reset to 0
areaDragAndDrop.areaB.size = 0;
}
else if (areaDragAndDrop.offsetPixel < 0) { // gutter was moved right (-> offsetPixel = start - end = a negative number)
//right side wins
areaDragAndDrop.areaB.size += areaDragAndDrop.areaA.size;
//left side is reset to 0
areaDragAndDrop.areaA.size = 0;
}
}
}
}
// check & fix min sizes of dragged areas
this.checkAndFixSizePxRestrictions({
// use complete size in px of both dragged areas as containerSizePx
containerSizePx: draggedAreasSizePx,
// use dragged areas as list of displayed areas
displayedAreas: draggedAreas,
// copy gutter size of original options object, because it's the same for dragging areas
gutterSizePx: opts.gutterSizePx
});
}
/**
* Calculates area sizes when the window:resize-event was triggered
*/
handleWindowResize(opts) {
this.checkAndFixSizePxRestrictions(opts);
}
/**
* Calculates area sizes for normal layouting (first display, area-list changes, ...)
*/
handleNormalLayout(opts) {
this.checkAndFixSizePxRestrictions(opts);
}
checkAndFixSizePxRestrictions(opts) {
//check and fix min-sizes
this.checkAndFixSizePxRestrictedAreas(opts, 'min');
//check and fix max-sizes
this.checkAndFixSizePxRestrictedAreas(opts, 'max');
}
/**
* Checks and fixes <split-area>-Components that have minSizePx / maxSizePx configured
*
* @throws E-00001 - area sizes could not be calculated fixing minSizePx / maxSizePx
*/
checkAndFixSizePxRestrictedAreas(opts, restrictionProperty) {
// if we have one or zero displayed area(s)
if (opts.displayedAreas.length <= 1) {
// there is nothing to check and fix
return;
}
// size of the container in pixel (= available space in px)
const containerSizePixel = opts.containerSizePx ? opts.containerSizePx : 0;
// areas with a size > 0
const displayedAreasWithSizeGreaterZero = opts.displayedAreas.filter((a) => a.size !== 0);
// total size of all gutters in px
const totalGutterSizePx = (opts.displayedAreas.length - 1) * (opts.gutterSizePx ? opts.gutterSizePx : 0);
// size of the gutter in px per component
const gutterSizePxPerVisibleComponent = displayedAreasWithSizeGreaterZero.length > 1
? totalGutterSizePx / displayedAreasWithSizeGreaterZero.length
: totalGutterSizePx;
// iterate all displayed areas (with size > 0)
displayedAreasWithSizeGreaterZero.forEach((area, index) => {
// new size of the current area (default: current size)
let newAreaSize = area.size;
//switch min-width / max-width calculation by parameter (restrictionProperty)
switch (restrictionProperty) {
case 'min':
// if the (calculated) area size in pixels is smaller than its configured minSize
if (area.size * containerSizePixel <
Math.ceil(area.minSizePx + gutterSizePxPerVisibleComponent)) {
// new size of the current area (-> min size in percent) =
// it's configured min size in pixels / container size in pixels
newAreaSize =
Math.ceil(area.minSizePx + gutterSizePxPerVisibleComponent) / containerSizePixel;
}
break;
case 'max':
// if the (calculated) area size in pixels is greater than its configured maxSize
if (area.size * containerSizePixel >
Math.ceil(area.maxSizePx + gutterSizePxPerVisibleComponent)) {
// new size of the current area (-> max size in percent) =
// it's configured max size in pixels / container size in pixels
newAreaSize =
Math.ceil(area.maxSizePx + gutterSizePxPerVisibleComponent) / containerSizePixel;
}
break;
}
// if new size of the area differs from current size
if (newAreaSize !== area.size) {
// space that needs to be taken away / added from/to other areas
const sizeDiffToBeModifiedOnOtherAreas = area.size - newAreaSize;
// current area size = size respecting the minimum pixel size
area.size = newAreaSize;
if (sizeDiffToBeModifiedOnOtherAreas !== 0) {
// remove/add space-diff from to/all available areas
const diffAfterRemovingSpaceFromAreas = this.addAvailableSpaceToAreas(displayedAreasWithSizeGreaterZero, index, sizeDiffToBeModifiedOnOtherAreas, containerSizePixel, gutterSizePxPerVisibleComponent);
// if there is still space left after checking all areas
if (diffAfterRemovingSpaceFromAreas !== 0) {
// we add the diff to our current area to keep our layout consistent
area.size += sizeDiffToBeModifiedOnOtherAreas;
}
}
}
});
}
/**
* Modifies areas adding available space
*
* @param areas list of areas to be modified
* @param sourceAreaIndex index of area that triggered modification (will not be modified [a second time])
* @param availableSpace size to be added / subtracted to / from areas within the list
* @param containerSizePixel size of the container in pixel
* @param gutterSizePxPerVisibleComponent size of the gutter in pixel per visible component
*
* @returns space left after adding/removing from/to all available areas
*/
addAvailableSpaceToAreas(areas, sourceAreaIndex, availableSpace, containerSizePixel, gutterSizePxPerVisibleComponent) {
// add/remove space from/to areas on the right side of source-area(-index)
let result = AreaSizeCalculation.modifyAreaSizes(areas, sourceAreaIndex, availableSpace, containerSizePixel, gutterSizePxPerVisibleComponent, 'right');
// if there is still some space to be added/removed left after checking right side areas
if (result !== 0) {
// add/remove space from/to areas on the left side of source-area(-index)
result =
AreaSizeCalculation.modifyAreaSizes(areas, sourceAreaIndex, result, containerSizePixel, gutterSizePxPerVisibleComponent, 'left');
}
return result;
}
}
/**
* Handles and notifies size changes
*/
class Area {
constructor(area) {
this.sizeChanged = new EventEmitter();
if (!area) {
return;
}
this._size = area.size;
this.comp = area.comp;
this.maxSizePx = area.maxSizePx;
this.minSizePx = area.minSizePx;
this.order = area.order;
}
get size() {
return this._size;
}
set size(value) {
let previousValue = this._size;
this._size = value;
if (previousValue !== value) {
this.sizeChanged.next(value);
}
}
}
var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (undefined && undefined.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
/**
* angular-split
*
* Areas size are set in percentage of the split container.
* Gutters size are set in pixels.
*
* So we set css 'flex-basis' property like this (where 0 <= area.size <= 1):
* calc( { area.size * 100 }% - { area.size * nbGutter * gutterSize }px );
*
* Examples with 3 visible areas and 2 gutters:
*
* | 10px 10px |
* |---------------------[]---------------------[]------------------------------------|
* | calc(20% - 4px) calc(20% - 4px) calc(60% - 12px) |
*
*
* | 10px 10px |
* |--------------------------[]--------------------------[]--------------------------|
* | calc(33.33% - 6.667px) calc(33.33% - 6.667px) calc(33.33% - 6.667px) |
*
*
* |10px 10px |
* |[]----------------------------------------------------[]--------------------------|
* |0 calc(66.66% - 13.333px) calc(33%% - 6.667px) |
*
*
* 10px 10px |
* |[][]------------------------------------------------------------------------------|
* |0 0 calc(100% - 20px) |
*
*/
let SplitComponent = class SplitComponent {
constructor(ngZone, elRef, cdRef, renderer) {
this.ngZone = ngZone;
this.elRef = elRef;
this.cdRef = cdRef;
this.renderer = renderer;
this._direction = 'horizontal';
////
this._useTransition = false;
////
this._disabled = false;
////
this._width = null;
////
this._height = null;
////
this._gutterSize = 11;
////
this._gutterColor = '';
////
this._gutterImageH = '';
////
this._gutterImageV = '';
////
this._dir = 'ltr';
////
this.dragStart = new EventEmitter(false);
this.dragProgress = new EventEmitter(false);
this.dragEnd = new EventEmitter(false);
this.gutterClick = new EventEmitter(false);
/**
* notifies size changes on an area
*/
this.currentSizeChange = new EventEmitter();
this.transitionEndInternal = new Subject();
this.transitionEnd = this.transitionEndInternal.asObservable().pipe(debounceTime(20));
this.defaultAreaSizeCalculation = new AreaSizeCalculation();
this.isViewInitialized = false;
this.isDragging = false;
this.draggingWithoutMove = false;
this.currentGutterNum = 0;
this.displayedAreas = [];
this.hidedAreas = [];
this.dragListeners = [];
this.dragStartValues = {
sizePixelContainer: 0,
sizePixelA: 0,
sizePixelB: 0,
sizePercentA: 0,
sizePercentB: 0,
};
this.setMinSizeOnContainer = false;
}
set direction(v) {
v = (v === 'vertical') ? 'vertical' : 'horizontal';
this._direction = v;
[...this.displayedAreas, ...this.hidedAreas].forEach(area => {
area.comp.setStyleVisibleAndDir(area.comp.visible, this.isDragging, this.direction);
});
this.build(false, false);
}
get direction() {
return this._direction;
}
set useTransition(v) {
v = (typeof (v) === 'boolean') ? v : (v === 'false' ? false : true);
this._useTransition = v;
}
get useTransition() {
return this._useTransition;
}
set disabled(v) {
v = (typeof (v) === 'boolean') ? v : (v === 'false' ? false : true);
this._disabled = v;
// Force repaint if modified from TS class (instead of the template)
this.cdRef.markForCheck();
}
get disabled() {
return this._disabled;
}
set width(v) {
v = Number(v);
this._width = (!isNaN(v) && v > 0) ? v : null;
this.build(false, false);
}
get width() {
return this._width;
}
set height(v) {
v = Number(v);
this._height = (!isNaN(v) && v > 0) ? v : null;
this.build(false, false);
}
get height() {
return this._height;
}
set gutterSize(v) {
v = Number(v);
this._gutterSize = (!isNaN(v) && v > 0) ? v : 11;
this.build(false, false);
}
get gutterSize() {
return this._gutterSize;
}
set gutterColor(v) {
this._gutterColor = (typeof v === 'string' && v !== '') ? v : '';
// Force repaint if modified from TS class (instead of the template)
this.cdRef.markForCheck();
}
get gutterColor() {
return this._gutterColor;
}
set gutterImageH(v) {
this._gutterImageH = (typeof v === 'string' && v !== '') ? v : '';
// Force repaint if modified from TS class (instead of the template)
this.cdRef.markForCheck();
}
get gutterImageH() {
return this._gutterImageH;
}
set gutterImageV(v) {
this._gutterImageV = (typeof v === 'string' && v !== '') ? v : '';
// Force repaint if modified from TS class (instead of the template)
this.cdRef.markForCheck();
}
get gutterImageV() {
return this._gutterImageV;
}
set dir(v) {
v = (v === 'rtl') ? 'rtl' : 'ltr';
this._dir = v;
}
get dir() {
return this._dir;
}
get cssFlexdirection() {
return (this.direction === 'horizontal') ? 'row' : 'column';
}
get cssWidth() {
return this.width ? `${this.width}px` : '100%';
}
get cssHeight() {
return this.height ? `${this.height}px` : '100%';
}
get cssMinwidth() {
return (this.direction === 'horizontal') ? `${this.getNbGutters() * this.gutterSize}px` : null;
}
get cssMinheight() {
return (this.direction === 'vertical') ? `${this.getNbGutters() * this.gutterSize}px` : null;
}
set areaSizeCalculation(v) {
this._areaSizeCalculation = v;
}
get areaSizeCalculation() {
return this._areaSizeCalculation;
}
get areaSizeCalculationToBeUsed() {
return this.areaSizeCalculation ? this.areaSizeCalculation : this.defaultAreaSizeCalculation;
}
addArea(comp) {
const newArea = new Area({
comp,
minSizePx: comp.minSizePx,
maxSizePx: comp.maxSizePx,
order: 0,
size: 0
});
newArea.sizeChanged.subscribe((newSize) => this.fireSizeChanged(newArea, newSize * 100));
if (comp.visible === true) {
this.displayedAreas.push(newArea);
}
else {
this.hidedAreas.push(newArea);
}
comp.setStyleVisibleAndDir(comp.visible, this.isDragging, this.direction);
this.build(true, true);
}
removeArea(comp) {
if (this.displayedAreas.some((a) => a.comp === comp)) {
const area = this.displayedAreas.find((a) => a.comp === comp);
this.displayedAreas.splice(this.displayedAreas.indexOf(area), 1);
this.build(true, true);
}
else if (this.hidedAreas.some((a) => a.comp === comp)) {
const area = this.hidedAreas.find((a) => a.comp === comp);
this.hidedAreas.splice(this.hidedAreas.indexOf(area), 1);
}
}
updateArea(comp, resetOrders, resetSizes) {
// Only refresh if area is displayed (No need to check inside 'hidedAreas')
const item = this.displayedAreas.find((a) => a.comp === comp);
if (item) {
// use minSizePx of the component
item.minSizePx = comp.minSizePx;
// use maxSizePx of the component
item.maxSizePx = comp.maxSizePx;
this.build(resetOrders, resetSizes);
}
}
showArea(comp) {
const area = this.hidedAreas.find(a => a.comp === comp);
if (area) {
comp.setStyleVisibleAndDir(comp.visible, this.isDragging, this.direction);
const areas = this.hidedAreas.splice(this.hidedAreas.indexOf(area), 1);
this.displayedAreas.push(...areas);
this.build(true, true);
}
}
hideArea(comp) {
const area = this.displayedAreas.find(a => a.comp === comp);
if (area) {
comp.setStyleVisibleAndDir(comp.visible, this.isDragging, this.direction);
const areas = this.displayedAreas.splice(this.displayedAreas.indexOf(area), 1);
areas.forEach((currentArea) => {
currentArea.order = 0;
currentArea.size = 0;
});
this.hidedAreas.push(...areas);
this.build(true, true);
}
}
/**
* Min-Width of the container
*/
get containerMinWidth() {
if (!this.setMinSizeOnContainer)
return undefined;
// if split direction is NOT vertical
if (this.direction !== "horizontal") {
// min-width is 0 (unset)
return 0;
}
// return the sum of all area minSizes + gutter sizes
return this.getMinSizeOfAllDisplayedComponentsPlusGutterSize();
}
/**
* Min-Height of the container
*/
get containerMinHeight() {
if (!this.setMinSizeOnContainer)
return undefined;
// if split direction is NOT vertical
if (this.direction !== "vertical") {
// min-height is 0 (unset)
return 0;
}
// return the sum of all area minSizes + gutter sizes
return this.getMinSizeOfAllDisplayedComponentsPlusGutterSize();
}
ngAfterViewInit() {
this.isViewInitialized = true;
}
/**
* Sum of all area minSizes + gutter sizes
*/
getMinSizeOfAllDisplayedComponentsPlusGutterSize() {
if (!this.displayedAreas || this.displayedAreas.length === 0) {
return 0;
}
return this.getMinSizeOfAllDisplayedComponents() + (this.gutterSize * (this.displayedAreas.length - 1));
}
/**
* Sum of all area minSizes
*/
getMinSizeOfAllDisplayedComponents() {
if (!this.displayedAreas || this.displayedAreas.length === 0) {
return 0;
}
let result = 0;
this.displayedAreas.forEach((area) => {
if (area.minSizePx) {
result += area.minSizePx;
}
});
return result;
}
getNbGutters() {
return this.displayedAreas.length - 1;
}
build(resetOrders, resetSizes) {
this.stopDragging();
// ¤ AREAS ORDER
if (resetOrders === true) {
// If user provided 'order' for each area, use it to sort them.
if (this.displayedAreas.every((a) => a.comp.order !== null)) {
this.displayedAreas.sort((a, b) => a.comp.order - b.comp.order);
}
// Then set real order with multiples of 2, numbers between will be used by gutters.
this.displayedAreas.forEach((area, i) => {
area.order = i * 2;
area.comp.setStyleOrder(area.order);
});
}
// ¤ AREAS SIZE PERCENT
if (resetSizes === true) {
const totalUserSize = this.displayedAreas.reduce((total, s) => s.comp.size ? total + s.comp.size : total, 0);
// If user provided 'size' for each area and total == 1, use it.
if (this.displayedAreas.every((a) => a.comp.size !== null)
&& totalUserSize > .999
&& totalUserSize < 1.001) {
this.displayedAreas.forEach((area) => {
area.size = area.comp.size;
});
}
// Else set equal sizes for all areas.
else {
const size = 1 / this.displayedAreas.length;
this.displayedAreas.forEach(area => {
area.size = size;
});
}
}
// ¤
// If some real area sizes are less than gutterSize,
// set them to zero and dispatch size to others.
let percentToDispatch = 0;
// Get container pixel size
const containerSizePixel = this.containerSizePx;
this.displayedAreas.forEach(area => {
if (area.size * containerSizePixel < this.gutterSize) {
percentToDispatch += area.size;
area.size = 0;
}
});
if (percentToDispatch > 0 && this.displayedAreas.length > 0) {
const nbAreasNotZero = this.displayedAreas.filter(a => a.size !== 0).length;
if (nbAreasNotZero > 0) {
const percentToAdd = percentToDispatch / nbAreasNotZero;
this.displayedAreas.filter(a => a.size !== 0).forEach(area => {
area.size += percentToAdd;
});
}
// All area sizes (container percentage) are less than guterSize,
// It means containerSize < ngGutters * gutterSize
else {
this.displayedAreas[this.displayedAreas.length - 1].size = 1;
}
}
// do the extra calculation
this.areaSizeCalculationToBeUsed.calculate(this.createAreaSizeCalculationOptions());
this.refreshStyleSizes();
this.cdRef.markForCheck();
}
/**
* Creates an AreaSizeCalculation-Object of currently set instance parameters
*/
createAreaSizeCalculationOptions(calculationSource) {
return {
calculationSource,
containerSizePx: this.containerSizePx,
displayedAreas: this.displayedAreas,
gutterSizePx: this.gutterSize
};
}
/**
* Size of the container in pixels (corresponding to direction: height or width)
*/
get containerSizePx() {
// Get container pixel size
let result = this.getNbGutters() * this.gutterSize;
if (this.direction === 'horizontal') {
result = this.width ? this.width : this.elRef.nativeElement.offsetWidth;
}
else {
result = this.height ? this.height : this.elRef.nativeElement.offsetHeight;
}
return result;
}
refreshStyleSizes() {
const sumGutterSize = this.getNbGutters() * this.gutterSize;
this.displayedAreas.forEach((area) => {
area.comp.setStyleFlexbasis(`calc( ${area.size * 100}% - ${area.size * sumGutterSize}px )`, this.isDragging);
});
}
startDragging(startEvent, gutterOrder, gutterNum) {
startEvent.preventDefault();
// Place code here to allow '(gutterClick)' event even if '[disabled]="true"'.
this.currentGutterNum = gutterNum;
this.draggingWithoutMove = true;
this.ngZone.runOutsideAngular(() => {
this.dragListeners.push(this.renderer.listen('document', 'mouseup', (e) => this.stopDragging()));
this.dragListeners.push(this.renderer.listen('document', 'touchend', (e) => this.stopDragging()));
this.dragListeners.push(this.renderer.listen('document', 'touchcancel', (e) => this.stopDragging()));
});
if (this.disabled) {
return;
}
const areaA = this.displayedAreas.find(a => a.order === gutterOrder - 1);
const areaB = this.displayedAreas.find(a => a.order === gutterOrder + 1);
if (!areaA || !areaB) {
return;
}
const prop = (this.direction === 'horizontal') ? 'offsetWidth' : 'offsetHeight';
this.dragStartValues.sizePixelContainer = this.elRef.nativeElement[prop];
this.dragStartValues.sizePixelA = areaA.comp.getSizePixel(prop);
this.dragStartValues.sizePixelB = areaB.comp.getSizePixel(prop);
this.dragStartValues.sizePercentA = areaA.size;
this.dragStartValues.sizePercentB = areaB.size;
let start;
if (startEvent instanceof MouseEvent) {
start = {
x: startEvent.screenX,
y: startEvent.screenY,
};
}
else if (startEvent instanceof TouchEvent) {
start = {
x: startEvent.touches[0].screenX,
y: startEvent.touches[0].screenY,
};
}
else {
return;
}
this.ngZone.runOutsideAngular(() => {
this.dragListeners.push(this.renderer.listen('document', 'mousemove', (e) => this.dragEvent(e, start, areaA, areaB)));
this.dragListeners.push(this.renderer.listen('document', 'touchmove', (e) => this.dragEvent(e, start, areaA, areaB)));
});
areaA.comp.lockEvents();
areaB.comp.lockEvents();
this.isDragging = true;
this.notify('start');
}
dragEvent(event, start, areaA, areaB) {
if (!this.isDragging) {
return;
}
let end;
if (event instanceof MouseEvent) {
end = {
x: event.screenX,
y: event.screenY,
};
}
else if (event instanceof TouchEvent) {
end = {
x: event.touches[0].screenX,
y: event.touches[0].screenY,
};
}
else {
return;
}
this.draggingWithoutMove = false;
this.drag(start, end, areaA, areaB);
}
drag(start, end, areaA, areaB) {
// ¤ AREAS SIZE PIXEL
let offsetPixel = (this.direction === 'horizontal') ? (start.x - end.x) : (start.y - end.y);
/* const devicePixelRatio = window.devicePixelRatio || 1; //doesn't work
offsetPixel = offsetPixel / devicePixelRatio; //doesn't work */
if (this.dir === 'rtl') {
offsetPixel = -offsetPixel;
}
let newSizePixelA = this.dragStartValues.sizePixelA - offsetPixel;
let newSizePixelB = this.dragStartValues.sizePixelB + offsetPixel;
if (newSizePixelA < this.gutterSize && newSizePixelB < this.gutterSize) {
// WTF.. get out of here!
return;
}
else if (newSizePixelA < this.gutterSize) {
newSizePixelB += newSizePixelA;
newSizePixelA = 0;
}
else if (newSizePixelB < this.gutterSize) {
newSizePixelA += newSizePixelB;
newSizePixelB = 0;
}
// ¤ AREAS SIZE PERCENT
if (newSizePixelA === 0) {
areaB.size += areaA.size;
areaA.size = 0;
}
else if (newSizePixelB === 0) {
areaA.size += areaB.size;
areaB.size = 0;
}
else {
// NEW_PERCENT = START_PERCENT / START_PIXEL * NEW_PIXEL;
if (this.dragStartValues.sizePercentA === 0) {
areaB.size = this.dragStartValues.sizePercentB / this.dragStartValues.sizePixelB * newSizePixelB;
areaA.size = this.dragStartValues.sizePercentB - areaB.size;
}
else if (this.dragStartValues.sizePercentB === 0) {
areaA.size = this.dragStartValues.sizePercentA / this.dragStartValues.sizePixelA * newSizePixelA;
areaB.size = this.dragStartValues.sizePercentA - areaA.size;
}
else {
areaA.size = this.dragStartValues.sizePercentA / this.dragStartValues.sizePixelA * newSizePixelA;
areaB.size = (this.dragStartValues.sizePercentA + this.dragStartValues.sizePercentB) - areaA.size;
}
}
this.areaSizeCalculationToBeUsed.calculate(this.createAreaSizeCalculationOptions({
areaA,
areaB,
isDragAndDrop: true,
offsetPixel
}));
this.refreshStyleSizes();
this.notify('progress');
}
stopDragging() {
if (this.isDragging === false && this.draggingWithoutMove === false) {
return;
}
this.displayedAreas.forEach(area => {
area.comp.unlockEvents();
});
while (this.dragListeners.length > 0) {
const fct = this.dragListeners.pop();
if (fct) {
fct();
}
}
if (this.draggingWithoutMove === true) {
this.notify('click');
}
else {
this.notify('end');
}
this.isDragging = false;
this.draggingWithoutMove = false;
//trigger resize-event - components can handle it and react to new sizes accordingly
this.triggerWindowResize();
}
notify(type) {
const areasSize = this.displayedAreas.map(a => a.size * 100);
switch (type) {
case 'start':
return this.dragStart.emit({ gutterNum: this.currentGutterNum, sizes: areasSize });
case 'progress':
return this.dragProgress.emit({ gutterNum: this.currentGutterNum, sizes: areasSize });
case 'end':
return this.dragEnd.emit({ gutterNum: this.currentGutterNum, sizes: areasSize });
case 'click':
return this.gutterClick.emit({ gutterNum: this.currentGutterNum, sizes: areasSize });
case 'transitionEnd':
return this.transitionEndInternal.next(areasSize);
}
}
/**
* Moves the gutter to it's max position between two areas
*
* @param gutterOrderIndex Index of the gutter to be moved
*/
moveGutterMax(gutterOrderIndex) {
this.moveGutterToPosition(gutterOrderIndex, Number.MAX_VALUE);
}
/**
* Moves the gutter to it's min position between two areas
*
* @param gutterOrderIndex Index of the gutter to be moved
*/
moveGutterMin(gutterOrderIndex) {
this.moveGutterToPosition(gutterOrderIndex, 0);
}
/**
* Moves the gutter to given position in Pixels
*
* @param gutterOrderIndex Index of the gutter to be moved
* @param positionInPx new gutter position in px
*
* @version 0.2.8 triggers Window-Resize
*/
moveGutterToPosition(gutterOrderIndex, positionInPx) {
// area on left side of the gutter
const areaA = this.displayedAreas.find(a => a.order === gutterOrderIndex - 1);
// area on right side of the gutter
const areaB = this.displayedAreas.find(a => a.order === gutterOrderIndex + 1);
// if one of both areas couldn't be found
if (!areaA || !areaB) {
// the gutter in between can't be moved
return;
}
const sizePixelA = areaA.comp.getSizePixel((this.direction === 'horizontal') ? 'offsetWidth' : 'offsetHeight');
const sizePixelB = areaB.comp.getSizePixel((this.direction === 'horizontal') ? 'offsetWidth' : 'offsetHeight');
const totalSizePx = sizePixelA + sizePixelB;
const totalSizePcnt = areaA.size + areaB.size;
let newSizePixelA = sizePixelA;
let newSizePixelB = sizePixelB;
// sizing left side component and right side component takes available space (=== true)
const isLeftSideSizing = positionInPx >= 0;
//sizing left side component, if positionInPx is > 0
if (isLeftSideSizing) {
// left side componen will be positionInPx or totalSizePx (if it is smaller than positionInPx)
newSizePixelA = positionInPx < totalSizePx ? positionInPx : totalSizePx;
// right side component will take space still available
newSizePixelB = totalSizePx - newSizePixelA;
}
//sizing right side component, if positionInPx is < 0
else {
// absolute size of required right side component width
const absolutePositionInPx = Math.abs(positionInPx);
// right side componen will be absolutePositionInPx or totalSizePx (if it is smaller than absolutePositionInPx)
newSizePixelB = absolutePositionInPx < totalSizePx ? absolutePositionInPx : totalSizePx;
// left side component will take space still available
newSizePixelA = totalSizePx - newSizePixelB;
}
if (newSizePixelA < this.gutterSize && newSizePixelB < this.gutterSize) {
// WTF.. get out of here!
return;
}
else if (newSizePixelA < this.gutterSize) {
newSizePixelB += newSizePixelA;
newSizePixelA = 0;
}
else if (newSizePixelB < this.gutterSize) {
newSizePixelA += newSizePixelB;
newSizePixelB = 0;
}
areaA.size = newSizePixelA / totalSizePx * totalSizePcnt;
areaB.size = newSizePixelB / totalSizePx * totalSizePcnt;
let draggedAreas = [];
// component sizes will be checked / corrected in given order
// if gutter movement is negative (= sizing area B)
if (!isLeftSideSizing) {
// areaB size should be checked / fixed first and diff space will be taken from areaA
draggedAreas = [areaB, areaA];
}
else {
// areaA size should be checked / fixed first and diff space will be taken from areaB
draggedAreas = [areaA, areaB];
}
//drag-offset in pixel = size of areaA in px before resizing - size of areaA in px after resizing
const pseudoDragOffset = sizePixelA - newSizePixelA;
// reuse drag-and-drop calculation (because it is like a "fast drag-and-drop")
this.areaSizeCalculationToBeUsed.calculate(this.createAreaSizeCalculationOptions({
areaA: draggedAreas[0],
areaB: draggedAreas[1],
isDragAndDrop: true,
offsetPixel: pseudoDragOffset,
}));
this.refreshStyleSizes();
this.triggerWindowResize();
}
ngOnDestroy() {
this.stopDragging();
}
handleWindowResize() {
// if container size remains the same as the size we have checked
if (this.lastCheckedContainerSizePx === this.containerSizePx) {
//there is nothing to do
return;
}
this.lastCheckedContainerSizePx = this.containerSizePx;
this.areaSizeCalculationToBeUsed.calculate(this.createAreaSizeCalculationOptions({
isWindowResize: true
}));
this.refreshStyleSizes();
this.cdRef.markForCheck();
//trigger resize-event - components can handle it and react to new sizes accordingly
this.triggerWindowResize();
}
fireSizeChanged(area, newSize) {
this.currentSizeChange.next({
size: newSize,
areaOrder: area.order
});
}
/**
* Triggers the window-resize event, so component inside the (resized) areas can re-layout with given widths / heights
*/
triggerWindowResize() {
// save current sizePx, so we don't check the sizes within handleWindowResize again
this.lastCheckedContainerSizePx = this.containerSizePx;
try {
// For a full list of event types: https://developer.mozilla.org/en-US/docs/Web/API/document.createEvent
var event = document.createEvent('HTMLEvents');
event.initEvent('resize', true, false);
window.dispatchEvent(event);
}
catch (error) {
console.warn("triggerWindowResize", error);
}
}
};
__decorate$1([
Input(),
__metadata("design:type", String),
__metadata("design:paramtypes", [String])
], SplitComponent.prototype, "direction", null);
__decorate$1([
Input(),
__metadata("design:type", Boolean),
__metadata("design:paramtypes", [Boolean])
], SplitComponent.prototype, "useTransition", null);
__decorate$1([
Input(),
__metadata("design:type", Boolean),
__metadata("design:paramtypes", [Boolean])
], SplitComponent.prototype, "disabled", null);
__decorate$1([
Input(),
__metadata("design:type", Number),
__metadata("design:paramtypes", [Number])
], SplitComponent.prototype, "width", null);
__decorate$1([
Input(),
__metadata("design:type", Number),
__metadata("design:paramtypes", [Number])
], SplitComponent.prototype, "height", null);
__decorate$1([
Input(),
__metadata("design:type", Number),
__metadata("design:paramtypes", [Number])
], SplitComponent.prototype, "gutterSize", null);
__decorate$1([
Input(),
__metadata("design:type", String),
__metadata("design:paramtypes", [String])
], SplitComponent.prototype, "gutterColor", null);
__decorate$1([
Input(),
__metadata("design:type", String),
__metadata("design:paramtypes", [String])
], SplitComponent.prototype, "gutterImageH", null);
__decorate$1([
Input(),
__metadata("design:type", String),
__metadata("design:paramtypes", [String])
], SplitComponent.prototype, "gutterImageV", null);
__decorate$1([
Input(),
__metadata("design:type", String),
__metadata("design:paramtypes", [String])
], SplitComponent.prototype, "dir", null);
__decorate$1([
Output(),
__metadata("design:type", Object)
], SplitComponent.prototype, "dragStart", void 0);
__decorate$1([
Output(),
__metadata("design:type", Object)
], SplitComponent.prototype, "dragProgress", void 0);
__decorate$1([
Output(),
__metadata("design:type", Object)
], SplitComponent.prototype, "dragEnd", void 0);
__decorate$1([
Output(),
__metadata("design:type", Object)
], SplitComponent.prototype, "gutterClick", void 0);
__decorate$1([
Output(),
__metadata("design:type", Object)
], SplitComponent.prototype, "currentSizeChange", void 0);
__decorate$1([
Output(),
__metadata("design:type", Object)
], SplitComponent.prototype, "transitionEnd", void 0);
__decorate$1([
HostBinding('style.flex-direction'),
__metadata("design:type", Object),
__metadata("design:paramtypes", [])
], SplitComponent.prototype, "cssFlexdirection", null);
__decorate$1([
HostBinding('style.width'),
__metadata("design:type", Object),
__metadata("design:paramtypes", [])
], SplitComponent.prototype,