UNPKG

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
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