@deepkit/desktop-ui
Version:
Library for desktop UI widgets in Angular 10+
141 lines (123 loc) • 5.05 kB
text/typescript
import { booleanAttribute, Component, computed, effect, inject, input, model, Renderer2 } from '@angular/core';
import { DragDirective, DuiDragEvent, DuiDragStartEvent } from '../app/drag';
import { clamp } from '../app/utils';
/**
* A splitter component that can be used for layout resizing. With an indicator that shows a handle.
*
* This is typically used to resize layouts such as sidebars, panels, or other UI elements.
*
* Best used in combination with flex-basis CSS property to allow flexible resizing.
*
* ```html
* <div class="layout">
* <div class="sidebar" [style.flex-basis.px]="sidebarSize()">
* <dui-splitter position="right" [size]="sidebarSize" (sizeChange)="sidebarSize.set($event)" indicator></dui-splitter>
* </div>
* <div class="content"></div>
* </div>
* ```
*/
export class SplitterComponent {
/**
* When set, the splitter will show an indicator (handle) to indicate that it can be dragged.
*/
indicator = input(false, { transform: booleanAttribute });
size = model(0);
inverted = input(false, { transform: booleanAttribute });
/**
* If set one of these, the splitter will be positioned absolutely in the layout.
* Make sure to set a parent element with `position: relative;` to allow absolute positioning.
*/
position = input<'left' | 'right' | 'top' | 'bottom'>();
orientation = input<'horizontal' | 'vertical'>();
/**
* Per default splitter is vertical (movement left-to-right), meaning vertical line.
* If set to true, it will be horizontal (movement top-to-bottom).
*/
horizontal = input(false, { transform: booleanAttribute });
min = input(0);
max = input(Infinity);
element = input<Element>();
/**
* When element is set, this CSS property will be used to set the size of the splitter.
* Default is 'flex-basis', which is typically used in flexbox layouts.
*/
property = input<'flex-basis' | 'width' | string>('flex-basis');
isHorizontal = computed(() => (this.horizontal() || this.position() === 'top' || this.position() === 'bottom') || this.orientation() === 'horizontal');
protected startSize = 0;
protected renderer = inject(Renderer2);
constructor() {
effect(() => {
const property = this.property();
const element = this.element();
if (!element) return;
const size = this.size();
if (size) {
this.renderer.setStyle(element, property, `${size}px`);
}
});
}
protected onDuiDragStart($event: DuiDragStartEvent) {
this.startSize = this.size();
const element = this.element();
if (!element) return;
const rect = element.getBoundingClientRect();
if (this.isHorizontal()) {
this.startSize = rect.height;
} else {
this.startSize = rect.width;
}
}
protected onDuiDragEnd(event: DuiDragEvent) {
const element = this.element();
if (!element) return;
// it's important to reset this.size() to the final real size after the drag ends.
// If for example dragged way too far, the size might be negative or too large.
const rect = element.getBoundingClientRect();
if (this.isHorizontal()) {
this.size.set(rect.height);
this.renderer.setStyle(element, this.property(), `${rect.height}px`);
} else {
this.size.set(rect.width);
this.renderer.setStyle(element, this.property(), `${rect.width}px`);
}
}
protected onDuiDrag(event: DuiDragEvent) {
const delta = this.isHorizontal() ? event.deltaY : event.deltaX;
const factor = this.inverted() ? -1 : 1;
const value = clamp(this.startSize + (delta * factor), this.min(), this.max());
this.size.set(value);
const element = this.element();
if (element) {
this.renderer.setStyle(element, this.property(), `${value}px`);
}
}
protected onDuiDragCancel() {
this.size.set(this.startSize);
}
}