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.
219 lines (218 loc) • 8.2 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 = ".disabled {\n opacity: 0.3;\n}\n";
class ReorderHandle extends WJElement {
/**
* Creates an instance of ReorderHandle.
*/
constructor() {
super();
/**
* The class name for the component.
* @type {string}
*/
__publicField(this, "className", "ReorderHandle");
this.addEventListener("mousedown", this.startDrag.bind(this));
this.addEventListener("touchstart", this.startTouchDrag.bind(this));
}
/**
* Returns the CSS styles for the component.
* @returns {*}
*/
static get cssStyleSheet() {
return styles;
}
/**
* Returns the list of attributes to observe for changes.
* @returns {string[]}
*/
static get observedAttributes() {
return ["dropzone", "parent"];
}
setupAttributes() {
this.isShadowRoot = "open";
}
/**
* Draws the component.
* @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");
container.appendChild(slot);
fragment.appendChild(container);
return fragment;
}
/**
* Draws the component after it is connected to the DOM.
*/
afterDraw() {
if (this.hasAttribute("disabled")) {
this.style.opacity = ".3";
}
}
/**
* Handles the attribute changes.
* @param {DragEvent} event
*/
startDrag(event) {
if (this.hasAttribute("disabled") || this.hasAttribute("locked")) return;
this.startDragAction(event.clientX, event.clientY);
}
/**
* Handles the touch start event.
* @param {TouchEvent} event
*/
startTouchDrag(event) {
if (this.hasAttribute("disabled") || this.hasAttribute("locked")) return;
const touch = event.touches[0];
this.startDragAction(touch.clientX, touch.clientY);
}
/**
* Initiates the drag-and-drop action for a sortable element.
* @param {number} clientX The x-coordinate of the mouse pointer at the start of the drag action.
* @param {number} clientY The y-coordinate of the mouse pointer at the start of the drag action.
*/
startDragAction(clientX, clientY) {
let draggable;
if (this.hasAttribute("parent")) {
const parentSelector = this.getAttribute("parent");
draggable = this.closest(parentSelector);
} else {
draggable = this.parentElement;
}
const initialContainer = this.getDropzone(draggable);
if (!this.getAttribute("dropzone")) {
this.setAttribute("dropzone", initialContainer.localName);
}
const rect = draggable.getBoundingClientRect();
const offsetX = clientX - rect.left;
const offsetY = clientY - rect.top;
let placeholder = document.createElement("div");
placeholder.classList.add("sortable-item");
placeholder.style.visibility = "hidden";
placeholder.style.height = `${rect.height}px`;
draggable.classList.add("dragging");
draggable.style.position = "fixed";
draggable.style.zIndex = "1000";
draggable.style.width = `${rect.width}px`;
const moveAt = (pageX, pageY) => {
draggable.style.left = `${pageX - offsetX - document.documentElement.scrollLeft}px`;
draggable.style.top = `${pageY - offsetY - document.documentElement.scrollTop}px`;
};
moveAt(clientX, clientY);
const onMouseMove = (event) => {
var _a;
moveAt(event.pageX, event.pageY);
const dropzone = this.getClosestDropzone(event.clientX, event.clientY);
if (!dropzone) return;
const siblings = Array.from(dropzone.children).filter(
(child) => child !== draggable && child !== placeholder
);
for (const sibling of siblings) {
const siblingRect = sibling.getBoundingClientRect();
if ((_a = sibling.children[0]) == null ? void 0 : _a.hasAttribute("locked")) continue;
if (event.clientY > siblingRect.top && event.clientY < siblingRect.bottom) {
if (event.clientY < siblingRect.top + siblingRect.height / 2) {
dropzone.insertBefore(placeholder, sibling);
} else {
dropzone.insertBefore(placeholder, sibling.nextSibling);
}
break;
}
}
};
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
draggable.classList.remove("dragging");
draggable.style.position = "";
draggable.style.zIndex = "";
draggable.style.left = "";
draggable.style.top = "";
draggable.style.width = "";
const finalContainer = placeholder.parentElement;
finalContainer.insertBefore(draggable, placeholder);
finalContainer.removeChild(placeholder);
this.reIndexItems(finalContainer);
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
initialContainer.insertBefore(placeholder, draggable);
}
/**
* Retrieves the dropzone associated with the given element.
* @param {HTMLElement} element The element from which to search for the closest dropzone.
* @returns {HTMLElement|null} - The closest dropzone element matching the `dropzone` attribute, or the parent element if no dropzone is found.
*/
getDropzone(element) {
const dropzoneAttr = this.getAttribute("dropzone");
if (dropzoneAttr) {
let dropzone = element.closest(dropzoneAttr);
if (dropzone) return dropzone;
}
return element.parentElement;
}
/**
* Retrieves the closest dropzone element at the specified coordinates.
* @param {number} clientX The x-coordinate relative to the viewport.
* @param {number} clientY The y-coordinate relative to the viewport.
* @returns {HTMLElement|null} - The closest dropzone element matching the `dropzone` attribute, or `null` if none is found.
*/
getClosestDropzone(clientX, clientY) {
const elements = this.getElementsFromPointAll(clientX, clientY);
for (const element of elements) {
if (element.matches(this.getAttribute("dropzone"))) {
return element;
}
}
return null;
}
/**
* Retrieves all elements at the specified coordinates, including those within shadow DOMs.
* @param {number} x The x-coordinate relative to the viewport.
* @param {number} y The y-coordinate relative to the viewport.
* @param {Document|ShadowRoot} [root] The root context in which to search. Defaults to the main document.
* @param {Set<Node>} [visited] A set of already visited nodes to avoid infinite recursion in nested shadow DOMs.
* @returns {HTMLElement[]} An array of all elements found at the specified coordinates, including shadow DOM elements.
*/
getElementsFromPointAll(x, y, root = document, visited = /* @__PURE__ */ new Set()) {
if (visited.has(root)) return [];
visited.add(root);
const elements = root.elementsFromPoint(x, y);
let allElements = [...elements];
for (const element of elements) {
if (element.shadowRoot && !visited.has(element.shadowRoot)) {
allElements = allElements.concat(this.getElementsFromPointAll(x, y, element.shadowRoot, visited));
}
}
return allElements;
}
/**
* Re-indexes the items in the container.
* @param container
*/
reIndexItems(container) {
const items = Array.from(container.children);
let index = 0;
items.forEach((child) => {
var _a;
if ((_a = child.children[0]) == null ? void 0 : _a.hasAttribute("locked")) {
child.dataset.index = index;
} else {
child.dataset.index = index;
}
index++;
});
}
}
ReorderHandle.define("wje-reorder-handle", ReorderHandle);
export {
ReorderHandle as default
};
//# sourceMappingURL=wje-reorder-handle.js.map