wj-elements
Version:
WebJET Elements is a modern set of user interface tools harnessing the power of web components designed to simplify web application development.
282 lines (281 loc) • 10.3 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import WJElement from "./wje-element.js";
const styles = ".container {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: auto;\n flex-direction: var(--flex-direction);\n}\n\n.container-w-dropzones {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: row;\n}\n\n.reversed {\n --flex-direction: column-reverse;\n}\n\n.basic {\n --flex-direction: column;\n}\n\n.dragging {\n position: absolute;\n pointer-events: none;\n z-index: 1000;\n}\n\n.reorder-item {\n transition:\n top 0.3s ease,\n left 0.3s ease;\n}\n";
class Reorder extends WJElement {
/**
* Creates an instance of Select.
* @class
*/
constructor() {
super();
/**
* The class name for the component.
* @type {string}
*/
__publicField(this, "className", "Select");
this.dragEl = null;
this.items = [];
this.originalIndex = null;
this.isDragging = false;
this.offsetX = 0;
this.offsetY = 0;
this.cloneEl = null;
}
/**
* Returns the CSS styles for the component.
* @static
* @returns {CSSStyleSheet}
*/
static get cssStyleSheet() {
return styles;
}
/**
* Returns the list of attributes to observe for changes.
* @static
* @returns {Array<string>}
*/
static get observedAttributes() {
return [];
}
/**
* Sets up the attributes for the component.
*/
setupAttributes() {
this.isShadowRoot = "open";
}
/**
* Draws the component after it is connected to the DOM.
* @returns {DocumentFragment}
*/
draw() {
const fragment = document.createDocumentFragment();
const container = document.createElement("div");
container.classList.add("container");
container.setAttribute("part", "native");
const slot = document.createElement("slot");
slot.classList.add("reorder-items");
container.appendChild(slot);
fragment.appendChild(container);
this.container = container;
return fragment;
}
/**
* Adds event listeners after the component is drawn.
*/
afterDraw() {
const items = this.querySelectorAll("wje-reorder-item");
const dropZones = this.querySelectorAll("wje-reorder-dropzone");
this.container.classList.add(this.hasAttribute("reverse") ? "reversed" : "basic");
if (dropZones) {
dropZones.forEach((dropZone) => {
this.container.classList.remove("container");
this.container.classList.add("container-w-dropzones");
});
}
if (items) {
items.forEach((item) => {
const handles = item.querySelectorAll("[slot=handle]");
const draggableElement = handles.length > 0 ? handles : [item];
draggableElement.forEach((element) => {
this.attachEventListeners(element);
});
});
}
}
/**
* Attaches event listeners to the element.
* @param element
*/
attachEventListeners(element) {
element.addEventListener("mousedown", this.mouseDown.bind(this), false);
element.addEventListener("touchstart", this.touchStart.bind(this), false);
element.addEventListener("dragstart", this.dragStart.bind(this), false);
}
/**
* Handles the mouse down event.
* @param {object} e
*/
mouseDown(e) {
this.startDragging(e.clientX, e.clientY, e.currentTarget);
document.addEventListener("mousemove", this.mouseMove.bind(this), false);
document.addEventListener("mouseup", this.mouseUp.bind(this), false);
document.body.style.userSelect = "none";
}
/**
* Handles the touch start event.
* @param e
*/
touchStart(e) {
const touch = e.touches[0];
this.startDragging(touch.clientX, touch.clientY, e.currentTarget);
document.addEventListener("touchmove", this.touchMove.bind(this), false);
document.addEventListener("touchend", this.touchEnd.bind(this), false);
document.body.style.userSelect = "none";
}
/**
* Initializes the dragging process for a reorderable item.
* @param {number} clientX The x-coordinate of the mouse pointer when the drag starts.
* @param {number} clientY The y-coordinate of the mouse pointer when the drag starts.
* @param {HTMLElement} target The target element where the drag event originated.
*/
startDragging(clientX, clientY, target) {
if (this.hasAttribute("disabled")) {
return;
}
this.isDragging = true;
this.dragEl = target.closest("wje-reorder-item");
this.createClone();
this.dragEl.style.opacity = "0.3";
const rect = this.dragEl.getBoundingClientRect();
this.offsetX = clientX - rect.left;
this.offsetY = clientY - rect.top;
this.dragEl.classList.add("dragging");
this.originalIndex = [...this.dragEl.parentNode.children].indexOf(this.dragEl);
this.originalParent = this.dragEl.parentNode;
}
/**
* Handles the mouse move event.
* @param e
*/
mouseMove(e) {
if (!this.isDragging) return;
this.moveElement(e.pageX, e.pageY);
if (this.cloneEl) {
this.cloneEl.style.left = `${e.pageX - this.offsetX}px`;
this.cloneEl.style.top = `${e.pageY - this.offsetY}px`;
}
}
/**
* Handles the `touchmove` event and updates the position of the dragged element.
* @param {TouchEvent} e The touch event containing touch position data.
*/
touchMove(e) {
if (!this.isDragging) return;
const touch = e.touches[0];
this.moveElement(touch.pageX, touch.pageY);
}
/**
* Updates the position of the dragged element and handles reordering logic based on the mouse position.
* @param {number} pageX The x-coordinate of the mouse pointer relative to the viewport during the move event.
* @param {number} pageY The y-coordinate of the mouse pointer relative to the viewport during the move event.
*/
moveElement(pageX, pageY) {
const scrollX = window.scrollX || document.documentElement.scrollLeft;
const scrollY = window.scrollY || document.documentElement.scrollTop;
const adjustedPageX = pageX - scrollX;
const adjustedPageY = pageY - scrollY;
this.dragEl.style.left = `${adjustedPageX}px`;
this.dragEl.style.top = `${adjustedPageY}px`;
if (this.cloneEl) {
this.cloneEl.style.left = `${adjustedPageX}px`;
this.cloneEl.style.top = `${adjustedPageY}px`;
}
const items = this.querySelectorAll("wje-reorder-item");
items.forEach((item) => {
if (item === this.dragEl) return;
const boundingBox = item.getBoundingClientRect();
const mouseY = adjustedPageY - boundingBox.top;
const mouseYPercent = mouseY / boundingBox.height * 100;
if (mouseYPercent > 30 && this.isMovingDown(item)) {
item.parentNode.insertBefore(this.dragEl, item.nextSibling);
} else if (mouseYPercent < 30 && !this.isMovingDown(item)) {
item.parentNode.insertBefore(this.dragEl, item);
}
});
}
/**
* Handles the mouse up event.
*/
mouseUp() {
this.stopDragging();
document.removeEventListener("mousemove", this.mouseMove.bind(this), false);
document.removeEventListener("mouseup", this.mouseUp.bind(this), false);
if (this.cloneEl) {
this.cloneEl.remove();
this.cloneEl = null;
}
if (this.dragEl) {
this.dragEl.style.opacity = "1";
}
}
/**
* Handles the touch end event.
*/
touchEnd() {
this.stopDragging();
document.removeEventListener("touchmove", this.touchMove.bind(this), false);
document.removeEventListener("touchend", this.touchEnd.bind(this), false);
}
/**
* Stops dragging the element.
*/
stopDragging() {
if (!this.isDragging) return;
this.isDragging = false;
this.dragEl.classList.remove("dragging");
this.dragEl.style.left = "";
this.dragEl.style.top = "";
const parent = this.dragEl.parentNode;
const newIndex = Array.from(parent.children).indexOf(this.dragEl);
const newOrder = Array.from(parent.children).map((el) => {
const clonedNode = el.cloneNode(true);
const handle = clonedNode.querySelector(".handle");
if (handle) {
handle.remove();
}
return clonedNode.innerText.trim();
});
this.dispatchChange(this.originalIndex, newIndex, newOrder);
document.body.style.userSelect = "";
}
/**
* Prevents the default behavior of the `dragstart` event.
* @param {DragEvent} e The drag event triggered when a drag operation starts.
*/
dragStart(e) {
e.preventDefault();
}
/**
* Creates a clone of the element.
*/
createClone() {
this.cloneEl = this.dragEl.cloneNode(true);
this.cloneEl.style.position = "absolute";
this.cloneEl.style.pointerEvents = "none";
this.cloneEl.style.visibility = "visible";
document.body.appendChild(this.cloneEl);
}
/**
* Checks if the dragged element is moving down.
* @param droppedElement
* @returns {boolean}
*/
isMovingDown(droppedElement) {
const parent = droppedElement.parentNode;
const dragIndex = Array.from(parent.children).indexOf(this.dragEl);
const dropIndex = Array.from(parent.children).indexOf(droppedElement);
return dragIndex < dropIndex;
}
/**
* Dispatches a custom event to signal that a reordering operation has occurred.
* @param {number} from The original index of the dragged item.
* @param {number} to The new index of the dragged item after reordering.
* @param {Array<number>} order The updated order of items after the reordering.
* // @fires wje-reorder:change - Dispatched when the reordering is completed.
* The event includes details about the initial position, the new position, and the new order.
*/
dispatchChange(from, to, order) {
this.dispatchEvent(
new CustomEvent("wje-reorder:change", {
detail: { from, to, order }
})
);
}
}
Reorder.define("wje-reorder", Reorder);
export {
Reorder as default
};
//# sourceMappingURL=wje-reorder.js.map