@medyll/idae-be
Version:
A modern, lightweight, and extensible DOM manipulation library built with TypeScript. Designed for precise element targeting and manipulation using a callback-based approach. Features include advanced DOM traversal, event handling, style management, attri
191 lines (190 loc) • 8.34 kB
JavaScript
import { Be, be } from '../be.js';
import { BeUtils } from '../utils.js';
var positionMethods;
(function (positionMethods) {
positionMethods["clonePosition"] = "clonePosition";
positionMethods["overlapPosition"] = "overlapPosition";
positionMethods["snapTo"] = "snapTo";
})(positionMethods || (positionMethods = {}));
export class PositionHandler {
beElement;
static methods = Object.values(positionMethods);
constructor(beElement) {
this.beElement = beElement;
}
methods = PositionHandler.methods;
handle(actions) {
Object.entries(actions).forEach(([method, props]) => {
switch (method) {
case 'clonePosition':
this.clonePosition(props.source, props.options, props.callback);
break;
case 'overlapPosition':
this.overlapPosition(props.source, props.options, props.callback);
break;
case 'snapTo':
this.snapTo(props.target, props.options, props.callback);
break;
}
});
return this.beElement;
}
/**
* Clones the position of a source element to this element.
* @param source - The element or selector of the element whose position is to be cloned.
* @param options - Additional options for positioning.
* @param options.offsetX - Horizontal offset from the source position.
* @param options.offsetY - Vertical offset from the source position.
* @param options.useTransform - Whether to use CSS transform for positioning.
* @param callback - Optional callback function to execute after cloning the position.
* @returns The Be instance for method chaining.
* @example
* // HTML: <div id="source"></div><div id="target"></div>
* const beInstance = be('#target');
* beInstance.clonePosition('#source', { offsetX: 10, offsetY: 20 });
*/
clonePosition(source, options = {}, callback) {
if (this.beElement.isWhat !== 'element')
return this.beElement;
const sourceEl = typeof source === 'string' ? document.querySelector(source) : source;
if (!sourceEl)
return this.beElement;
const sourceRect = sourceEl.getBoundingClientRect();
const targetRect = this.beElement.inputNode.getBoundingClientRect();
const { offsetX = 0, offsetY = 0, useTransform = false } = options;
this.beElement.eachNode((el) => {
if (useTransform) {
const x = sourceRect.left - targetRect.left + offsetX;
const y = sourceRect.top - targetRect.top + offsetY;
el.style.transform = `translate(${x}px, ${y}px)`;
}
else {
el.style.left = `${sourceRect.left + offsetX}px`;
el.style.top = `${sourceRect.top + offsetY}px`;
}
//
callback?.({
fragment: undefined,
be: be(el),
root: this.beElement
});
});
return this.beElement;
}
/**
* Positions this element to overlap a target element.
* @param targetElement - The element or selector of the element to overlap.
* @param options - Additional options for positioning.
* @param options.alignment - The alignment of this element relative to the target.
* @param options.offset - The distance to offset from the target element.
* @param options.useTransform - Whether to use CSS transform for positioning.
* @param callback - Optional callback function to execute after positioning.
* @returns The Be instance for method chaining.
* @example
* // HTML: <div id="source"></div><div id="target"></div>
* const beInstance = be('#target');
* beInstance.overlapPosition('#source', { alignment: 'center', offset: 10 });
*/
overlapPosition(targetElement, options = {}, callback) {
if (this.beElement.isWhat !== 'element')
return this.beElement;
const targetEl = typeof targetElement === 'string' ? document.querySelector(targetElement) : targetElement;
if (!targetEl)
return this.beElement;
const { alignment = 'center', offset = 0, useTransform = false } = options;
const targetRect = targetEl.getBoundingClientRect();
const selfRect = this.beElement.inputNode.getBoundingClientRect();
let x = 0, y = 0;
switch (alignment) {
case 'center':
x = targetRect.left + (targetRect.width - selfRect.width) / 2;
y = targetRect.top + (targetRect.height - selfRect.height) / 2;
break;
case 'top':
x = targetRect.left + (targetRect.width - selfRect.width) / 2;
y = targetRect.top - selfRect.height - offset;
break;
case 'bottom':
x = targetRect.left + (targetRect.width - selfRect.width) / 2;
y = targetRect.bottom + offset;
break;
case 'left':
x = targetRect.left - selfRect.width - offset;
y = targetRect.top + (targetRect.height - selfRect.height) / 2;
break;
case 'right':
x = targetRect.right + offset;
y = targetRect.top + (targetRect.height - selfRect.height) / 2;
break;
}
this.beElement.eachNode((el) => {
if (useTransform) {
el.style.transform = `translate(${x}px, ${y}px)`;
}
else {
el.style.left = `${x}px`;
el.style.top = `${y}px`;
}
callback?.({
fragment: undefined,
be: be(el),
root: this.beElement
});
});
return this.beElement;
}
/**
* Snaps the element to a target element with specified anchor points.
* @param targetElement - The element or selector of the element to snap to.
* @param options - Snapping options.
* @param options.sourceAnchor - The anchor point on the source element.
* @param options.targetAnchor - The anchor point on the target element.
* @param options.offset - Optional offset from the target anchor point.
* @param callback - Optional callback function to execute after snapping.
* @returns The Be instance for method chaining.
* @example
* // HTML: <div id="source"></div><div id="target"></div>
* const beInstance = be('#target');
* beInstance.snapTo('#source', {
* sourceAnchor: 'center',
* targetAnchor: 'top left',
* offset: { x: 10, y: 20 }
* });
*/
snapTo(targetElement, options, callback) {
if (this.beElement.isWhat !== 'element')
return this.beElement;
const targetEl = typeof targetElement === 'string' ? document.querySelector(targetElement) : targetElement;
if (!targetEl)
return this.beElement;
const sourceRect = this.beElement.inputNode.getBoundingClientRect();
const targetRect = targetEl.getBoundingClientRect();
const { sourceAnchor, targetAnchor, offset = { x: 0, y: 0 } } = options;
const [sourceX, sourceY] = BeUtils.calculateAnchorPoint(sourceRect, sourceAnchor);
const [targetX, targetY] = BeUtils.calculateAnchorPoint(targetRect, targetAnchor);
// Calculate final position
const x = targetX - sourceX + offset.x;
const y = targetY - sourceY + offset.y;
// Apply position
this.beElement.eachNode((el) => {
const computedStyle = window.getComputedStyle(el);
const position = computedStyle.position;
if (position === 'static') {
el.style.position = 'relative';
}
el.style.left = `${x}px`;
el.style.top = `${y}px`;
callback?.({
fragment: undefined,
be: be(el),
root: this.beElement
});
});
return this.beElement;
}
valueOf() {
if (this.beElement.isWhat !== 'element')
return null;
return this.beElement.inputNode.getBoundingClientRect();
}
}