aurelia-split-pane
Version:
A custom element for resizable split panes.
157 lines (124 loc) • 5.14 kB
text/typescript
import {
ComponentBind,
ComponentDetached,
customElement,
DOM,
inject
} from 'aurelia-framework';
import { ResizeEvent } from './resize-event';
import { SplitPane } from './split-pane';
import { setFlexBasis } from './util';
import { EventAggregator } from 'aurelia-event-aggregator';
export class SplitPaneDivider implements ComponentBind, ComponentDetached {
static readonly TAG_NAME = 'split-pane-divider';
static isDivider(element: Element): boolean {
return element.tagName.toLowerCase() === SplitPaneDivider.TAG_NAME;
}
parent!: SplitPane;
private startCoordinate = 0;
private previousSiblingSize = 0;
private nextSiblingSize = 0;
private readonly element: Element;
private readonly events: EventAggregator;
constructor(element: any, events: EventAggregator) {
this.element = element;
this.events = events;
}
bind(bindingContext: SplitPane): void {
this.parent = bindingContext;
}
detached(): void {
this.removeListeners();
}
mouseDown(event: MouseEvent): void {
if (event.button === 0 && this.startDrag(event.clientX, event.clientY)) {
// style rules may have affected the flex-basis of the children,
// so recalculate them now we are starting a drag
this.parent.setChildrenFlexBasis();
DOM.addEventListener('mousemove', this.mouseMove, false);
DOM.addEventListener('mouseup', this.stopDrag, false);
}
}
touchStart(event: TouchEvent): void {
let touch = event.touches[0];
if (this.startDrag(touch.clientX, touch.clientY)) {
// style rules may have affected the flex-basis of the children,
// so recalculate them now we are starting a drag
this.parent.setChildrenFlexBasis();
DOM.addEventListener('touchmove', this.touchMove, false);
DOM.addEventListener('touchend', this.stopDrag, false);
}
}
private startDrag(clientX: number, clientY: number): boolean {
let prev = this.element.previousElementSibling;
let next = this.element.nextElementSibling;
if (prev !== null && next !== null) {
let prevRect = prev.getBoundingClientRect();
let nextRect = next.getBoundingClientRect();
switch (this.parent.direction) {
case 'horizontal':
this.startCoordinate = clientX;
this.previousSiblingSize = prevRect.width;
this.nextSiblingSize = nextRect.width;
this.events.publish('split-pane:start');
return true;
case 'vertical':
this.startCoordinate = clientY;
this.previousSiblingSize = prevRect.height;
this.nextSiblingSize = nextRect.height;
this.events.publish('split-pane:start');
return true;
default:
return false;
}
} else {
return false;
}
}
private mouseMove = (event: MouseEvent): void => {
this.drag(event.clientX, event.clientY);
};
private touchMove = (event: TouchEvent): void => {
event.preventDefault(); // stop page from scrolling
let touch = event.touches[0];
this.drag(touch.clientX, touch.clientY);
};
private stopDrag = (): void => {
this.events.publish('split-pane:stop');
this.removeListeners();
};
private drag(clientX: number, clientY: number): void {
let currentCoordinate = this.parent.direction === 'horizontal' ? clientX : clientY;
let delta = currentCoordinate - this.startCoordinate;
let prev = this.element.previousElementSibling;
let next = this.element.nextElementSibling;
if (prev !== null && next !== null) {
let prevSize: number;
let nextSize: number;
if (delta < 0) {
prevSize = Math.max(0, this.previousSiblingSize + delta);
nextSize = this.nextSiblingSize - (prevSize - this.previousSiblingSize);
} else if (delta > 0) {
nextSize = Math.max(0, this.nextSiblingSize - delta);
prevSize = this.previousSiblingSize - (nextSize - this.nextSiblingSize);
} else {
return;
}
setFlexBasis(prev, prevSize);
setFlexBasis(next, nextSize);
let resizeEvent: ResizeEvent = {
clientX,
clientY
};
this.events.publish('split-pane:resize', resizeEvent);
}
}
private removeListeners() {
DOM.removeEventListener('mousemove', this.mouseMove, false);
DOM.removeEventListener('mouseup', this.stopDrag, false);
DOM.removeEventListener('touchmove', this.touchMove, false);
DOM.removeEventListener('touchend', this.stopDrag, false);
}
}