@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
183 lines (182 loc) • 6.95 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
import { minMax } from './utils/utils.js';
/**
* Enum representing different directions.
* With "t" standing for top, "l" for left, "b" for bottom and "r" for right.
*/
var Direction;
(function (Direction) {
Direction["tl"] = "tl";
Direction["t"] = "t";
Direction["tr"] = "tr";
Direction["r"] = "r";
Direction["br"] = "br";
Direction["b"] = "b";
Direction["bl"] = "bl";
Direction["l"] = "l";
})(Direction || (Direction = {}));
/**
* Class adding listener on existing buttons to let the user resizing a window.
* The window expects to be displayed in an absolute position.
*
* With "t" standing for top, "l" for left, "b" for bottom and "r" for right.
* The window can implement one or multiple of these buttons:
*
* <button tabindex="-1" class="girafe-resizer tl corner"></button>
* <button tabindex="-1" class="girafe-resizer t top-bottom"></button>
* <button tabindex="-1" class="girafe-resizer tr corner"></button>
* <button tabindex="-1" class="girafe-resizer r left-right"></button>
* <button tabindex="-1" class="girafe-resizer br corner"></button>
* <button tabindex="-1" class="girafe-resizer b top-bottom"></button>
* <button tabindex="-1" class="girafe-resizer bl corner"></button>
* <button tabindex="-1" class="girafe-resizer l left-right"></button>
*
*/
export default class ResizeWindow {
directions = [
Direction.tl,
Direction.t,
Direction.tr,
Direction.r,
Direction.br,
Direction.b,
Direction.bl,
Direction.l
];
shadow;
host;
top = 0;
left = 0;
minHeight = 0;
maxHeight = 0;
minWidth = 0;
maxWidth = 0;
height = 0;
width = 0;
originX = 0;
originY = 0;
elements = [];
className = 'girafe-resizer';
/**
* Create the instance and calls init.
*/
constructor(shadow) {
this.shadow = shadow;
this.host = this.shadow.getRootNode().host;
this.init();
}
/**
* Initializes the class by cleaning and attaching events to new existing elements.
*/
init() {
this.destroy();
this.elements = this.directions.map((direction) => {
return this.attachEvent(direction);
});
}
/**
* Destroys all elements in the object and removes their onmousedown event listeners.
*/
destroy() {
this.elements.filter((element) => element !== null).forEach((element) => (element.onmousedown = null));
this.elements = [];
}
/**
* Attaches an event listener to existing element with the specified direction.
* @return The element with the attached event listener, or null if no matching element was found.
*/
attachEvent(direction) {
const element = this.shadow.querySelector(`.${this.className}.${direction}`);
if (!element) {
return null;
}
element.onmousedown = (event) => {
this.onResizeStarts(event, direction);
};
return element;
}
/**
* Sets up the resize functionality for an element based on the provided MouseEvent and direction.
* Attach onmousemove and onmouseup to document to handle and stop the resize.
* @param event - The MouseEvent containing the clientX and clientY coordinates.
* @param direction - The direction in which the element should be resized.
*/
onResizeStarts(event, direction) {
this.originX = event.clientX;
this.originY = event.clientY;
const dimension = this.host.getBoundingClientRect();
this.width = dimension.width;
this.height = dimension.height;
const css = getComputedStyle(this.host);
this.minHeight = parseInt(css.minHeight) || 0;
this.maxHeight = parseInt(css.maxHeight) || Infinity;
this.minWidth = parseInt(css.minWidth) || 0;
this.maxWidth = parseInt(css.maxWidth) || Infinity;
this.top = parseInt(css.top) || 0;
this.left = parseInt(css.left) || 0;
// Call a function whenever the cursor moves.
document.onmousemove = (dragEvent) => this.resize(dragEvent, direction);
// Detach events.
document.onmouseup = () => this.stopResize();
}
/**
* Resizes the element based on the given mouse event and direction.
* @param event - The MouseEvent containing the "ondrag" clientX and clientY coordinates.
* @param direction - The direction in which the element should be resized.
*/
resize(event, direction) {
const [newTop, newLeft, newHeight, newWidth] = this.getNewSizeAndPosition(event.clientX, event.clientY, direction);
this.host.style.top = `${newTop}px`;
this.host.style.left = `${newLeft}px`;
this.host.style.width = `${newWidth}px`;
this.host.style.height = `${newHeight}px`;
}
/**
* Calculates the new size and position based on the current client coordinates and direction.
* @returns an array containing the new top, left, height, and width values in that order.
* @private
*/
getNewSizeAndPosition(clientX, clientY, direction) {
let newHeight = this.height;
let newWidth = this.width;
let newTop = this.top;
let newLeft = this.left;
let deltaX = clientX - this.originX;
let deltaY = clientY - this.originY;
if (direction.includes('t')) {
const rawNewHeight = this.height - deltaY;
newHeight = minMax(rawNewHeight, this.minHeight, this.maxHeight);
// If the height reach the min-max limit, reverse calculate
// the maxed out deltaY to limit the new top and don't move the window.
if (newHeight !== rawNewHeight) {
deltaY = (newHeight - this.height) * -1;
}
newTop = this.top + deltaY;
}
else if (direction.includes('b')) {
newHeight = minMax(this.height + deltaY, this.minHeight, this.maxHeight);
}
if (direction.includes('l')) {
const rawNewWidth = this.width - deltaX;
newWidth = minMax(rawNewWidth, this.minWidth, this.maxWidth);
// If the width reach the min-max limit, reverse calculate
// the maxed out deltaX to limit the new top and don't move the window.
if (newWidth !== rawNewWidth) {
deltaX = (newWidth - this.width) * -1;
}
newLeft = this.left + deltaX;
}
else if (direction.includes('r')) {
newWidth = minMax(this.width + deltaX, this.minWidth, this.maxWidth);
}
return [newTop, newLeft, newHeight, newWidth];
}
/**
* Stops the resizing functionality by clearing the
* event listeners for mousemove and mouseup events.
*/
stopResize() {
document.onmousemove = null;
document.onmouseup = null;
}
}