I am creating a dynamic component that can contain multiple full-height panel components, where their widths can be resized and adjusted.
For example:
First of all, the container width must always be filled, you can’t have free space.
If I want to increase the size of A, it will decrease the size of B accordingly. If B has reached it’s minimum width, it will try and resize C, and so on till it can’t resize anyone else and the resize function will stop.
This works perfectly fine. However, now I want to do the same for decreasing width.
Let’s say I want to decrease the width of B, what happens now is that it will automatically increase the width of C, but what if B has reached it’s minimum width? it should try and decrease the previous siblings sizes, so A’s width should be decreased accordingly as much as possible.
This creates the effect of pushing panels when resizing.
when I say "decrease B’s width", it means increasing C. same as if I want to increase A’s width and it will decrease everything else one after another until it cannot resize anymore and reached the max available space.
You can resize by hovering the side borders of every panel
What I did
MouseDown
to initialize the resizing:
@HostListener('mousedown', ['$event'])
onMouseDown($event: MouseEvent): void {
$event.preventDefault();
if (!$event.target) {
return;
}
if (!($event.target instanceof HTMLElement)) {
return;
}
const target = $event.target as HTMLElement;
if (target.className !== 'split-resize-toggle') { // Todo use the IDynamicHorizontalSplitChild abstraction to get the toggle element to make it dynamic
return;
}
const id = target.id;
if (!id) {
return;
}
this.currentResizingId = id;
this.currentResizingElement = this.containerCards.find(card => card.getUniqueId() === id)?.getElement();
this.startX = $event.pageX;
this.elementStartWidth = this.currentResizingElement?.clientWidth;
}
mouseMove
handles the resizing:
@HostListener('mousemove', ['$event'])
onMouseMove($event: MouseEvent): void {
if (!this.currentResizingId || !this.currentResizingElement || !this.startX) {
return;
}
$event.preventDefault();
const currentWidth = this.currentResizingElement.clientWidth;
let newWidth = currentWidth + $event.movementX;
// Get computed styles for the current element
const currentStyles = window.getComputedStyle(this.currentResizingElement);
const currentMinWidth = parseFloat(currentStyles.minWidth) || 0;
const currentMaxWidth = parseFloat(currentStyles.maxWidth) || Infinity;
// Constrain the new width of the current element
newWidth = Math.max(currentMinWidth, Math.min(newWidth, currentMaxWidth));
let widthDelta = newWidth - currentWidth;
if (widthDelta !== 0) {
let remainingDelta = this.adjustNextSiblings(this.currentResizingElement, widthDelta);
// If we couldn't distribute all the delta, adjust the current element's width
if (remainingDelta !== 0) {
newWidth = currentWidth + (widthDelta - remainingDelta);
this.disposeResizeHelperProperties();
}
}
// Update the current element's width
this.currentResizingElement.style.width = `${newWidth}px`;
}
And the adjustNextSiblings
function that tries to resize the next siblings accordingly to fill up the free space.
private adjustNextSiblings(element: HTMLElement, delta: number): number {
let currentElement = element.nextElementSibling as HTMLElement | null;
let remainingDelta = delta;
while (currentElement && remainingDelta !== 0) {
const currentWidth = currentElement.clientWidth;
const newWidth = currentWidth - remainingDelta;
const styles = window.getComputedStyle(currentElement);
const minWidth = parseFloat(styles.minWidth) || 0;
const maxWidth = parseFloat(styles.maxWidth) || Infinity;
const constrainedWidth = Math.max(minWidth, Math.min(newWidth, maxWidth));
const actualDelta = currentWidth - constrainedWidth;
currentElement.style.width = `${constrainedWidth}px`;
remainingDelta -= actualDelta;
currentElement = currentElement.nextElementSibling as HTMLElement | null;
}
return remainingDelta;
}
The component name is DynamicHorizontalSplitContainerComponent
How can I get the opposite resizing (decreasing width) affect previous siblings + next siblings?
I have a feeling there is a shorter generic way to handle this, but I am over thinking.
2
Answers
Add this function to resize previous siblings when decreasing width:
Modify your onMouseMove method to handle both resizing directions:
Imagine a layout like
Using css
Any value of the "width" of the "first" div makes the "third" maintance 30% -and fisrt + second maintance the 60%
If third insted of width:33% have flex-grow:2 fill the rest of the row.
In Angular we can use [style]="variable" (or [ngStyle]="variable"). So we can imagine some like:
The only we need is a way to call the function changeStyle with a "width"
There’re severals ways to makes resizable a div, e.g. based in this SO, I create a component that, instead of change the size, only emit an output with width and height
Now we can use some like
You can see the result in this stackblitz
NOTE: I write in styles.css