UNPKG

xq-treegrid

Version:

一个基于Bootstrap5和html5sortable的可拖动排序的树状表格列表。

1,309 lines (1,297 loc) 74.8 kB
/*! * xq-treegrid v1.0.7 (https://xqkeji.cn/demo/xq-treegrid/) * Author xqkeji.cn * LICENSE SSPL-1.0 * Copyright 2023 xqkeji.cn */ "use strict"; (() => { // src/ts/xq-option.ts var DEFAULT_OPTIONS = { expanderTemplate: '<span class="xq-treegrid-expander"></span>', indentTemplate: '<span class="xq-treegrid-indent"></span>', indentClass: ".xq-treegrid-indent", expanderClass: ".xq-treegrid-expander", expanderExpandedClass: "xq-treegrid-expander-expanded", expanderCollapsedClass: "xq-treegrid-expander-collapsed", treeColumn: 2, tableClass: ".xq-treegrid", topClass: ".xq-treegrid-top", dragClass: ".xq-drag", checkAllClass: ".xq-treegrid .xq-check-all", addBtnClass: ".xq-treegrid .xq-add", editBtnClass: ".xq-edit", deleteBtnClass: ".xq-delete", copyBtnClass: ".xq-copy", dragOffset: -30, "xq-url": null }; var treegridOptions = {}; var setOption = (options = {}) => { treegridOptions = Object.assign({}, DEFAULT_OPTIONS); if (options) { for (const option in options) { if (Object.hasOwn(treegridOptions, option)) { treegridOptions[option] = options[option]; } } } }; var getOption = (key) => { if (key in treegridOptions) { return treegridOptions[key]; } const tableClass = treegridOptions["tableClass"]; const table2 = document.querySelector(tableClass); if (table2.hasAttribute(key)) { return String(table2.getAttribute(key)); } return ""; }; // src/ts/xq-table.ts var table; var getTable = () => { if (table) { return table; } else { const tableClass = getOption("tableClass"); const container = document.querySelector(tableClass); table = container; return table; } }; // src/ts/xq-check-all.ts var checkAll = () => { const checkAllClass = getOption("checkAllClass"); const check_all = document.querySelector(checkAllClass); if (check_all) { check_all.addEventListener("click", (event) => { const target = event.currentTarget; const { checked } = target; const table2 = getTable(); const checks = table2?.querySelectorAll("tr > td:first-child > input[type=checkbox]"); for (const check of checks) { if (check === target) { continue; } if (checked) { check.checked = true; } else { check.checked = false; } } }); } }; // src/ts/xq-build.ts var build = (table2) => { if (table2) { let tbody = table2.querySelector("tbody"); if (tbody && tbody.querySelectorAll("tr").length > 1) { const firstTr = tbody.querySelector("tbody>tr:first-child"); const firstId = firstTr.getAttribute("id"); const firstTd = firstTr.querySelector("td:first-child"); firstTd?.classList.add("xq-move"); tbody.setAttribute("id", "tbody_" + firstId); const trs = tbody.querySelectorAll("tbody>tr:not(:first-child)"); for (const tr of trs) { const td = tr.querySelector("td:first-child"); td?.classList.add("xq-move"); const innerbody = document.createElement("tbody"); innerbody.append(tr); const trId = tr.getAttribute("id"); innerbody.setAttribute("id", "tbody_" + trId); tbody.after(innerbody); tbody = innerbody; } } } }; // src/ts/xq-tree.ts var getTopNodes = () => { const table2 = getTable(); const topClass = getOption("topClass"); const tops = table2.querySelectorAll("tbody>" + topClass); if (tops) { return tops; } return null; }; var getRootId = () => { const table2 = getTable(); const topClass = getOption("topClass"); const top = table2.querySelector("tbody>" + topClass); if (top) { return top.getAttribute("pid"); } return null; }; var getNodeId = (element) => { const nodeId = element.getAttribute("id"); if (nodeId) { return nodeId; } return ""; }; var getNodeById = (id) => { const table2 = getTable(); if (!id.startsWith("xq_")) { id = "xq_" + id; } return table2.querySelector("#" + id); }; var getChildNodes = (id) => { if (id.startsWith("xq_")) { id = id.replace("xq_", ""); } const table2 = getTable(); const nodes = table2.querySelectorAll('tr[pid="' + id + '"]'); return nodes; }; var isExistChildNode = (id) => { const nodes = getChildNodes(id); return nodes.length > 0; }; var getParent = (element) => { const parentNodeId = element.getAttribute("pid"); if (parentNodeId) { return getNodeById(parentNodeId); } return null; }; var getParentId = (element) => { const parentNodeId = element.getAttribute("pid"); if (parentNodeId) { return parentNodeId; } return null; }; var getDepth = (element) => { const depth = element.getAttribute("depth"); if (depth) { return Number.parseInt(depth, 10); } return 0; }; var isLeaf = (element) => { const is_leaf = element.getAttribute("is_leaf"); return Number.parseInt(is_leaf, 10) !== 0; }; // node_modules/xq-util/dist/index.mjs var domReady = (callBack) => { if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", callBack); } else { callBack(); } }; var parents = (element, selector) => { const parents2 = []; let ancestor = element.parentNode; while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== 3) { if (ancestor.matches(selector)) { parents2.push(ancestor); } ancestor = ancestor.parentNode; } return parents2; }; var append = (element, dom) => { const node = document.createRange().createContextualFragment(dom); element.append(node); }; var prepend = (element, dom) => { const node = document.createRange().createContextualFragment(dom); element.prepend(node); }; // src/ts/xq-edit.ts var bindEdit = (element) => { const editClass = getOption("editBtnClass"); const editBtn = element.querySelector(editClass); if (editBtn) { editBtn.addEventListener("click", (event) => { const targetElement = event.currentTarget; const node = parents(targetElement, "tr")[0]; const nodeId = getNodeId(node); let id = ""; let name = ""; id = nodeId.replace("xq_", ""); if (id) { let url = targetElement.getAttribute("xq-url"); if (!url) { url = window.location.href; if (url.includes(".html") || url.endsWith("/")) { url = url.slice(0, Math.max(0, url.lastIndexOf("/"))); url = url + "/edit.html?id=" + id; } else { url = url.slice(0, Math.max(0, url.lastIndexOf("/"))); url = url + "/edit/" + id; } } const text = node.querySelector("#name_" + id); name = text.getAttribute("value") + "\u8BBE\u7F6E"; if (window !== window.parent) { const tabId = "treegrid-edit-" + id; const message = { "callback": "xqAddTab", "params": [tabId, name, url] }; window.parent.postMessage(message, "*"); } else { const form = document.querySelector("#xq-not-tab-url"); const formId = document.querySelector("#xq-not-tab-url-id"); if (form && formId) { form.setAttribute("action", url); formId.setAttribute("value", id); form.submit(); } else { const template2 = '<form id="xq-not-tab-url" target="_blank" action="' + url + '" method="get"><input type="hidden" id="xq-not-tab-url-id" name="id" value="' + id + '"/></form>'; append(document.body, template2); const form2 = document.querySelector("#xq-not-tab-url"); form2.submit(); } } } }); } }; // node_modules/xq-confirm/dist/index.mjs var template = '<div id="xq-bs-modal" class="modal" tabindex="-1" aria-hidden="true"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title"><i></i><span>title</span></h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><p>Modal content.</p></div><div class="modal-footer"></div></div></div></div>'; var DEFAULT_OPTIONS2 = { id: "xq-bs-modal", type: "alert", size: "modal-sm", position: "modal-dialog-centered", template, title: "\u63D0\u793A\u4FE1\u606F", content: "\u60A8\u786E\u8BA4\u8981\u8FDB\u884C\u64CD\u4F5C\u5417?", icon: "info", confirmButton: "\u786E\u8BA4", cancelButton: "\u53D6\u6D88", confirmButtonClass: "btn-primary", cancelButtonClass: "btn-secondary", // eslint-disable-next-line @typescript-eslint/no-empty-function confirm: () => { }, // eslint-disable-next-line @typescript-eslint/no-empty-function cancel: () => { }, backgroundDismiss: true, autoClose: false // confirm|3000表示3秒后自动确认,cancel|3000表示3秒后自动取消 }; var confirmOptions = {}; var setOption2 = (options = {}) => { confirmOptions = Object.assign({}, DEFAULT_OPTIONS2); if (options) { for (const option in options) { if (Object.prototype.hasOwnProperty.call(confirmOptions, option)) { confirmOptions[option] = options[option]; } } } }; var getOption2 = (key) => { if (key in confirmOptions) { return confirmOptions[key]; } const id = confirmOptions["id"]; const modal = document.querySelector("#" + id); if (modal.hasAttribute(key)) { return String(modal.getAttribute(key)); } return ""; }; var ICONS = { info: "bi bi-info-circle-fill link-primary", warn: "bi bi-info-circle-fill link-warning", error: "bi bi-info-circle-fill link-danger" }; var getIcon = (icon) => { if (Object.prototype.hasOwnProperty.call(ICONS, icon)) { return ICONS[icon]; } else { return icon; } }; var build2 = (options = {}) => { setOption2(options); const template2 = getOption2("template"); append(document.body, template2); const id = getOption2("id"); const xq_bs_modal = document.querySelector("#" + id); if (xq_bs_modal) { const xq_modal_dialog = xq_bs_modal.querySelector(".modal-dialog"); const size = getOption2("size"); const position = getOption2("position"); xq_modal_dialog.classList.add(size); xq_modal_dialog.classList.add(position); const title_icon = xq_bs_modal.querySelector(".modal-title>i"); const title_content = xq_bs_modal.querySelector(".modal-title>span"); const body_content = xq_bs_modal.querySelector(".modal-body>p"); if (title_icon) { const icon = getOption2("icon"); const iconClass = getIcon(icon); title_icon.className = iconClass; } if (title_content) { const title = getOption2("title"); title_content.innerHTML = title; } if (body_content) { const content = getOption2("content"); body_content.innerHTML = content; } if (typeof bootstrap !== void 0 && typeof bootstrap.Modal !== void 0) { const xqModal = new bootstrap.Modal(xq_bs_modal); const footer = document.querySelector(".modal-footer"); if (footer) { const type = getOption2("type"); if (type !== "alert") { const cancelButtonClass = getOption2("cancelButtonClass"); const cancelButton = getOption2("cancelButton"); append(footer, '<button id="xq-bs-modal-cancel" type="button" class="btn ' + cancelButtonClass + '" data-bs-dismiss="modal">' + cancelButton + "</button>"); const cancel = footer.querySelector("#xq-bs-modal-cancel"); cancel?.addEventListener("click", (event) => { event.preventDefault(); xqModal.hide(); const cancel2 = getOption2("cancel"); if (cancel2 !== null) { cancel2(); } }); } const confirmButtonClass = getOption2("confirmButtonClass"); const confirmButton = getOption2("confirmButton"); append(footer, '<button id="xq-bs-modal-confirm" type="button" class="btn ' + confirmButtonClass + '">' + confirmButton + "</button>"); const confirm = footer.querySelector("#xq-bs-modal-confirm"); confirm?.addEventListener("click", (event) => { event.preventDefault(); xqModal.hide(); const confirm2 = getOption2("confirm"); if (confirm2 !== null) { confirm2(); } }); } xq_bs_modal.addEventListener("hidden.bs.modal", () => { xqModal.dispose(); xq_bs_modal.remove(); }); const autoClose = getOption2("autoClose"); if (autoClose) { const close = autoClose; if (close.indexOf("|")) { const closeArr = close.split("|"); const btn = closeArr[0]; const timer = Number.parseInt(closeArr[1], 10); let autoCloseInterval; let autoCloseBtn; let seconds = Math.ceil(timer / 1e3); const countdown = '<span class="countdown"> (' + seconds + ")</span>"; if (btn === "confirm") { autoCloseBtn = footer.querySelector("#xq-bs-modal-confirm"); append(autoCloseBtn, countdown); } else { autoCloseBtn = footer.querySelector("#xq-bs-modal-cancel"); append(autoCloseBtn, countdown); } xq_bs_modal.addEventListener("show.bs.modal", () => { autoCloseInterval = setInterval(function() { const countdown2 = autoCloseBtn.querySelector(".countdown"); seconds = seconds - 1; countdown2.innerHTML = " (" + seconds + ") "; if (seconds <= 0) { clearInterval(autoCloseInterval); autoCloseBtn.click(); } }, 1e3); }); } } xqModal.show(); } else { console.log("error", "the bootstrap not loaded!"); } } }; var xqConfirm = (options = {}) => { build2(options); }; window.xqConfirm = xqConfirm; // src/ts/xq-delete.ts var bindDelete = (element) => { const deleteBtnClass = getOption("deleteBtnClass"); const delBtn = element.querySelector(deleteBtnClass); if (delBtn) { delBtn.addEventListener("click", () => { let deleteUrl = ""; if (isLeaf(element)) { const xq_url = getOption("xq-url"); const id = getNodeId(element); const req_id = id.replace("xq_", ""); const data = { id: req_id }; if (!xq_url) { let url = window.location.href; url = url.slice(0, Math.max(0, url.lastIndexOf("/"))); url += "/delete"; deleteUrl = url; } else { deleteUrl = xq_url + "/delete"; } xqConfirm({ content: "\u786E\u5B9A\u8981\u5220\u9664\u5417\uFF1F", confirm() { fetch(deleteUrl, { body: JSON.stringify(data), headers: { "content-type": "application/json" }, method: "POST" }).then(async (response) => { return response.json(); }).then((data2) => { xqConfirm({ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access content: data2.message, confirm() { if (data2.code === 200) { removeNode(element); } } }); }); } }); } else { xqConfirm({ content: "\u5B58\u5728\u5B50\u680F\u76EE\u65E0\u6CD5\u8FDB\u884C\u5220\u9664\u64CD\u4F5C\uFF01" }); } }); } }; // src/ts/xq-change.ts var bindChange = (element) => { const table2 = getTable(); let id = element.getAttribute("id"); id = id?.replace("xq_", ""); const textes = element.querySelectorAll("input,select"); for (const text of textes) { const elementId = text.getAttribute("id"); if (elementId !== "id" && elementId !== "check_all") { text.addEventListener("change", (event) => { const targetElement = event.currentTarget; let value = null; value = targetElement.type === "checkbox" ? targetElement.checked : targetElement.value; const target_id = targetElement.getAttribute("id"); const field = target_id?.slice(0, Math.max(0, target_id.indexOf("_"))); const pid = table2?.getAttribute("pid"); let xq_url = getOption("xq-url"); const data = { id, field, value }; if (!xq_url) { let url = window.location.href; if (pid) { url = url.replace("/" + pid, ""); } url = url.slice(0, Math.max(0, url.lastIndexOf("/"))); url += "/change"; xq_url = url; } else { xq_url += "/change"; } fetch(xq_url, { body: JSON.stringify(data), headers: { "content-type": "application/json" }, method: "POST" }).then(async (response) => { return response.json(); }).then((data2) => { console.log(data2); }); }); } } }; // src/ts/xq-copy.ts var bindCopy = (element) => { const copyBtnClass = getOption("copyBtnClass"); const copy_btn = element.querySelector(copyBtnClass); if (copy_btn) { copy_btn.addEventListener("click", () => { let id = element.getAttribute("id"); id = id.replace("xq_", ""); if (id) { copy(id); xqConfirm({ content: "\u5DF2\u7ECF\u5C06id\u590D\u5236\u5230\u7C98\u8D34\u677F" }); } }); } }; var copy = (text) => { const el = document.createElement("input"); el.setAttribute("value", text); document.body.append(el); el.select(); document.execCommand("copy"); el.remove(); }; // node_modules/xq-html5sortable/dist/index.mjs function addData(element, key, value) { if (value === void 0) { return element && element.h5s && element.h5s.data && element.h5s.data[key]; } else { element.h5s = element.h5s || {}; element.h5s.data = element.h5s.data || {}; element.h5s.data[key] = value; } } function removeData(element) { if (element.h5s) { delete element.h5s.data; } } var filter = (nodes, selector) => { if (!(nodes instanceof NodeList || nodes instanceof HTMLCollection || nodes instanceof Array)) { throw new Error("You must provide a nodeList/HTMLCollection/Array of elements to be filtered."); } if (typeof selector !== "string") { return Array.from(nodes); } return Array.from(nodes).filter((item) => item.nodeType === 1 && item.matches(selector)); }; var stores = /* @__PURE__ */ new Map(); var Store = class { constructor() { this._config = /* @__PURE__ */ new Map(); this._placeholder = void 0; this._data = /* @__PURE__ */ new Map(); } set config(config) { if (typeof config !== "object") { throw new Error("You must provide a valid configuration object to the config setter."); } const mergedConfig = Object.assign({}, config); this._config = new Map(Object.entries(mergedConfig)); } get config() { const config = {}; this._config.forEach((value, key) => { config[key] = value; }); return config; } setConfig(key, value) { if (!this._config.has(key)) { throw new Error(`Trying to set invalid configuration item: ${key}`); } this._config.set(key, value); } getConfig(key) { if (!this._config.has(key)) { throw new Error(`Invalid configuration item requested: ${key}`); } return this._config.get(key); } get placeholder() { return this._placeholder; } set placeholder(placeholder) { if (!(placeholder instanceof HTMLElement) && placeholder !== null) { throw new Error("A placeholder must be an html element or null."); } this._placeholder = placeholder; } setData(key, value) { if (typeof key !== "string") { throw new Error("The key must be a string."); } this._data.set(key, value); } getData(key) { if (typeof key !== "string") { throw new Error("The key must be a string."); } return this._data.get(key); } deleteData(key) { if (typeof key !== "string") { throw new Error("The key must be a string."); } return this._data.delete(key); } }; var store = (sortableElement) => { if (!(sortableElement instanceof HTMLElement)) { throw new Error("Please provide a sortable to the store function."); } if (!stores.has(sortableElement)) { stores.set(sortableElement, new Store()); } return stores.get(sortableElement); }; function addEventListener(element, eventName, callback) { if (element instanceof Array) { for (let i = 0; i < element.length; ++i) { addEventListener(element[i], eventName, callback); } return; } element.addEventListener(eventName, callback); store(element).setData(`event${eventName}`, callback); } function removeEventListener(element, eventName) { if (element instanceof Array) { for (let i = 0; i < element.length; ++i) { removeEventListener(element[i], eventName); } return; } element.removeEventListener(eventName, store(element).getData(`event${eventName}`)); store(element).deleteData(`event${eventName}`); } function addAttribute(element, attribute, value) { if (element instanceof Array) { for (let i = 0; i < element.length; ++i) { addAttribute(element[i], attribute, value); } return; } element.setAttribute(attribute, value); } function removeAttribute(element, attribute) { if (element instanceof Array) { for (let i = 0; i < element.length; ++i) { removeAttribute(element[i], attribute); } return; } element.removeAttribute(attribute); } var offset = (element) => { if (!element.parentElement || element.getClientRects().length === 0) { throw new Error("target element must be part of the dom"); } const rect = element.getClientRects()[0]; return { left: rect.left + window.pageXOffset, right: rect.right + window.pageXOffset, top: rect.top + window.pageYOffset, bottom: rect.bottom + window.pageYOffset }; }; var debounce = (func, wait = 0) => { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => { func(...args); }, wait); }; }; var getIndex = (element, elementList) => { if (!(element instanceof HTMLElement) || !(elementList instanceof NodeList || elementList instanceof HTMLCollection || elementList instanceof Array)) { throw new Error("You must provide an element and a list of elements."); } return Array.from(elementList).indexOf(element); }; var isInDom = (element) => { if (!(element instanceof HTMLElement)) { throw new Error("Element is not a node element."); } return element.parentNode !== null; }; var insertNode = (referenceNode, newElement, position) => { if (!(referenceNode instanceof HTMLElement) || !(referenceNode.parentElement instanceof HTMLElement)) { throw new Error("target and element must be a node"); } referenceNode.parentElement.insertBefore( newElement, position === "before" ? referenceNode : referenceNode.nextElementSibling ); }; var insertBefore = (target, element) => insertNode(target, element, "before"); var insertAfter = (target, element) => insertNode(target, element, "after"); var serialize = (sortableContainer, customItemSerializer = (serializedItem, sortableContainer2) => serializedItem, customContainerSerializer = (serializedContainer) => serializedContainer) => { if (!(sortableContainer instanceof HTMLElement) || !sortableContainer.isSortable === true) { throw new Error("You need to provide a sortableContainer to be serialized."); } if (typeof customItemSerializer !== "function" || typeof customContainerSerializer !== "function") { throw new Error("You need to provide a valid serializer for items and the container."); } const options = addData(sortableContainer, "opts"); const item = options.items; const items = filter(sortableContainer.children, item); const serializedItems = items.map((item2) => { return { parent: sortableContainer, node: item2, html: item2.outerHTML, index: getIndex(item2, items) }; }); const container = { node: sortableContainer, itemCount: serializedItems.length }; return { container: customContainerSerializer(container), items: serializedItems.map((item2) => customItemSerializer(item2, sortableContainer)) }; }; var makePlaceholder = (sortableElement, placeholder, placeholderClass = "sortable-placeholder") => { if (!(sortableElement instanceof HTMLElement)) { throw new Error("You must provide a valid element as a sortable."); } if (!(placeholder instanceof HTMLElement) && placeholder !== void 0) { throw new Error("You must provide a valid element as a placeholder or set ot to undefined."); } if (placeholder === void 0) { if (["UL", "OL"].includes(sortableElement.tagName)) { placeholder = document.createElement("li"); } else if (["TABLE", "TBODY"].includes(sortableElement.tagName)) { placeholder = document.createElement("tr"); placeholder.innerHTML = '<td colspan="100"></td>'; } else { placeholder = document.createElement("div"); } } if (typeof placeholderClass === "string") { placeholder.classList.add(...placeholderClass.split(" ")); } return placeholder; }; var getElementHeight = (element) => { if (!(element instanceof HTMLElement)) { throw new Error("You must provide a valid dom element"); } const style = window.getComputedStyle(element); if (style.getPropertyValue("box-sizing") === "border-box") { return parseInt(style.getPropertyValue("height"), 10); } return ["height", "padding-top", "padding-bottom"].map((key) => { const int = parseInt(style.getPropertyValue(key), 10); return isNaN(int) ? 0 : int; }).reduce((sum, value) => sum + value); }; var getElementWidth = (element) => { if (!(element instanceof HTMLElement)) { throw new Error("You must provide a valid dom element"); } const style = window.getComputedStyle(element); return ["width", "padding-left", "padding-right"].map((key) => { const int = parseInt(style.getPropertyValue(key), 10); return isNaN(int) ? 0 : int; }).reduce((sum, value) => sum + value); }; var getHandles = (items, selector) => { if (!(items instanceof Array)) { throw new Error("You must provide a Array of HTMLElements to be filtered."); } if (typeof selector !== "string") { return items; } return items.filter((item) => { return item.querySelector(selector) instanceof HTMLElement || item.shadowRoot && item.shadowRoot.querySelector(selector) instanceof HTMLElement; }).map((item) => { return item.querySelector(selector) || item.shadowRoot && item.shadowRoot.querySelector(selector); }); }; var getEventTarget = (event) => { return event.composedPath && event.composedPath()[0] || event.target; }; var defaultDragImage = (draggedElement, elementOffset, event) => { return { element: draggedElement, posX: event.pageX - elementOffset.left, posY: event.pageY - elementOffset.top }; }; var setDragImage = (event, draggedElement, customDragImage) => { if (!(event instanceof Event)) { throw new Error("setDragImage requires a DragEvent as the first argument."); } if (!(draggedElement instanceof HTMLElement)) { throw new Error("setDragImage requires the dragged element as the second argument."); } if (!customDragImage) { customDragImage = defaultDragImage; } if (event.dataTransfer && event.dataTransfer.setDragImage) { const elementOffset = offset(draggedElement); const dragImage = customDragImage(draggedElement, elementOffset, event); if (!(dragImage.element instanceof HTMLElement) || typeof dragImage.posX !== "number" || typeof dragImage.posY !== "number") { throw new Error("The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer]."); } event.dataTransfer.effectAllowed = "copyMove"; event.dataTransfer.setData("text/plain", getEventTarget(event).id); event.dataTransfer.setDragImage(dragImage.element, dragImage.posX, dragImage.posY); } }; var listsConnected = (destination, origin) => { if (destination.isSortable === true) { const acceptFrom = store(destination).getConfig("acceptFrom"); if (acceptFrom !== null && acceptFrom !== false && typeof acceptFrom !== "string") { throw new Error('HTML5Sortable: Wrong argument, "acceptFrom" must be "null", "false", or a valid selector string.'); } if (acceptFrom !== null) { return acceptFrom !== false && acceptFrom.split(",").filter(function(sel) { return sel.length > 0 && origin.matches(sel); }).length > 0; } if (destination === origin) { return true; } if (store(destination).getConfig("connectWith") !== void 0 && store(destination).getConfig("connectWith") !== null) { return store(destination).getConfig("connectWith") === store(origin).getConfig("connectWith"); } } return false; }; var defaultConfiguration = { items: null, connectWith: null, disableIEFix: null, acceptFrom: null, copy: false, placeholder: null, placeholderClass: "sortable-placeholder", draggingClass: "sortable-dragging", hoverClass: false, dropTargetContainerClass: false, debounce: 0, throttleTime: 100, maxItems: 0, itemSerializer: void 0, containerSerializer: void 0, customDragImage: null, orientation: "vertical" }; function throttle(fn, threshold = 250) { if (typeof fn !== "function") { throw new Error("You must provide a function as the first argument for throttle."); } if (typeof threshold !== "number") { throw new Error("You must provide a number as the second argument for throttle."); } let lastEventTimestamp = null; return (...args) => { const now = Date.now(); if (lastEventTimestamp === null || now - lastEventTimestamp >= threshold) { lastEventTimestamp = now; fn.apply(this, args); } }; } var enableHoverClass = (sortableContainer, enable) => { if (typeof store(sortableContainer).getConfig("hoverClass") === "string") { const hoverClasses = store(sortableContainer).getConfig("hoverClass").split(" "); if (enable === true) { addEventListener(sortableContainer, "mousemove", throttle((event) => { if (event.buttons === 0) { filter(sortableContainer.children, store(sortableContainer).getConfig("items")).forEach((item) => { if (item === event.target || item.contains(event.target)) { item.classList.add(...hoverClasses); } else { item.classList.remove(...hoverClasses); } }); } }, store(sortableContainer).getConfig("throttleTime"))); addEventListener(sortableContainer, "mouseleave", () => { filter(sortableContainer.children, store(sortableContainer).getConfig("items")).forEach((item) => { item.classList.remove(...hoverClasses); }); }); } else { removeEventListener(sortableContainer, "mousemove"); removeEventListener(sortableContainer, "mouseleave"); } } }; var dragging; var draggingHeight; var draggingWidth; var originContainer; var originIndex; var originElementIndex; var originItemsBeforeUpdate; var previousContainer; var destinationItemsBeforeUpdate; var removeItemEvents = function(items) { removeEventListener(items, "dragstart"); removeEventListener(items, "dragend"); removeEventListener(items, "dragover"); removeEventListener(items, "dragenter"); removeEventListener(items, "drop"); removeEventListener(items, "mouseenter"); removeEventListener(items, "mouseleave"); }; var removeContainerEvents = function(originContainer2, previousContainer2) { if (originContainer2) { removeEventListener(originContainer2, "dragleave"); } if (previousContainer2 && previousContainer2 !== originContainer2) { removeEventListener(previousContainer2, "dragleave"); } }; var getDragging = function(draggedItem, sortable2) { let ditem = draggedItem; if (store(sortable2).getConfig("copy") === true) { ditem = draggedItem.cloneNode(true); addAttribute(ditem, "aria-copied", "true"); draggedItem.parentElement.appendChild(ditem); ditem.style.display = "none"; ditem.oldDisplay = draggedItem.style.display; } return ditem; }; var removeSortableData = function(sortable2) { removeData(sortable2); removeAttribute(sortable2, "aria-dropeffect"); }; var removeItemData = function(items) { removeAttribute(items, "aria-grabbed"); removeAttribute(items, "aria-copied"); removeAttribute(items, "draggable"); removeAttribute(items, "role"); }; function findSortable(element, event) { if (event.composedPath) { return event.composedPath().find((el) => el.isSortable); } while (element.isSortable !== true) { element = element.parentElement; } return element; } function findDragElement(sortableElement, element) { const options = addData(sortableElement, "opts"); const items = filter(sortableElement.children, options.items); const itemlist = items.filter(function(ele) { return ele.contains(element) || ele.shadowRoot && ele.shadowRoot.contains(element); }); return itemlist.length > 0 ? itemlist[0] : element; } var destroySortable = function(sortableElement) { const opts = addData(sortableElement, "opts") || {}; const items = filter(sortableElement.children, opts.items); const handles = getHandles(items, opts.handle); enableHoverClass(sortableElement, false); removeEventListener(sortableElement, "dragover"); removeEventListener(sortableElement, "dragenter"); removeEventListener(sortableElement, "dragstart"); removeEventListener(sortableElement, "dragend"); removeEventListener(sortableElement, "drop"); removeSortableData(sortableElement); removeEventListener(handles, "mousedown"); removeItemEvents(items); removeItemData(items); removeContainerEvents(originContainer, previousContainer); sortableElement.isSortable = false; }; var enableSortable = function(sortableElement) { const opts = addData(sortableElement, "opts"); const items = filter(sortableElement.children, opts.items); const handles = getHandles(items, opts.handle); addAttribute(sortableElement, "aria-dropeffect", "move"); addData(sortableElement, "_disabled", "false"); addAttribute(handles, "draggable", "true"); enableHoverClass(sortableElement, true); if (opts.disableIEFix === false) { const spanEl = (document || window.document).createElement("span"); if (typeof spanEl.dragDrop === "function") { addEventListener(handles, "mousedown", function() { if (items.indexOf(this) !== -1) { this.dragDrop(); } else { let parent = this.parentElement; while (items.indexOf(parent) === -1) { parent = parent.parentElement; } parent.dragDrop(); } }); } } }; var disableSortable = function(sortableElement) { const opts = addData(sortableElement, "opts"); const items = filter(sortableElement.children, opts.items); const handles = getHandles(items, opts.handle); addAttribute(sortableElement, "aria-dropeffect", "none"); addData(sortableElement, "_disabled", "true"); addAttribute(handles, "draggable", "false"); removeEventListener(handles, "mousedown"); enableHoverClass(sortableElement, false); }; var reloadSortable = function(sortableElement) { const opts = addData(sortableElement, "opts"); const items = filter(sortableElement.children, opts.items); const handles = getHandles(items, opts.handle); addData(sortableElement, "_disabled", "false"); removeItemEvents(items); removeContainerEvents(originContainer, previousContainer); removeEventListener(handles, "mousedown"); removeEventListener(sortableElement, "dragover"); removeEventListener(sortableElement, "dragenter"); removeEventListener(sortableElement, "drop"); }; function sortable(sortableElements, options) { const method = String(options); options = options || {}; if (typeof sortableElements === "string") { sortableElements = document.querySelectorAll(sortableElements); } if (sortableElements instanceof HTMLElement) { sortableElements = [sortableElements]; } sortableElements = Array.prototype.slice.call(sortableElements); if (/serialize/.test(method)) { return sortableElements.map((sortableContainer) => { const opts = addData(sortableContainer, "opts"); return serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer); }); } sortableElements.forEach(function(sortableElement) { if (/enable|disable|destroy/.test(method)) { return sortable[method](sortableElement); } ["connectWith", "disableIEFix"].forEach((configKey) => { if (Object.prototype.hasOwnProperty.call(options, configKey) && options[configKey] !== null) { console.warn(`HTML5Sortable: You are using the deprecated configuration "${configKey}". This will be removed in an upcoming version, make sure to migrate to the new options when updating.`); } }); options = Object.assign({}, defaultConfiguration, store(sortableElement).config, options); store(sortableElement).config = options; addData(sortableElement, "opts", options); sortableElement.isSortable = true; reloadSortable(sortableElement); const listItems = filter(sortableElement.children, options.items); let customPlaceholder; if (options.placeholder !== null && options.placeholder !== void 0) { const tempContainer = document.createElement(sortableElement.tagName); if (options.placeholder instanceof HTMLElement) { tempContainer.appendChild(options.placeholder); } else { tempContainer.innerHTML = options.placeholder; } customPlaceholder = tempContainer.children[0]; } store(sortableElement).placeholder = makePlaceholder(sortableElement, customPlaceholder, options.placeholderClass); addData(sortableElement, "items", options.items); if (options.acceptFrom) { addData(sortableElement, "acceptFrom", options.acceptFrom); } else if (options.connectWith) { addData(sortableElement, "connectWith", options.connectWith); } enableSortable(sortableElement); addAttribute(listItems, "role", "option"); addAttribute(listItems, "aria-grabbed", "false"); addEventListener(sortableElement, "dragstart", function(e) { const target = getEventTarget(e); if (target.isSortable === true) { return; } e.stopImmediatePropagation(); if (options.handle && !target.matches(options.handle) || target.getAttribute("draggable") === "false") { return; } const sortableContainer = findSortable(target, e); const dragItem = findDragElement(sortableContainer, target); originItemsBeforeUpdate = filter(sortableContainer.children, options.items); originIndex = originItemsBeforeUpdate.indexOf(dragItem); originElementIndex = getIndex(dragItem, sortableContainer.children); originContainer = sortableContainer; setDragImage(e, dragItem, options.customDragImage); draggingHeight = getElementHeight(dragItem); draggingWidth = getElementWidth(dragItem); dragItem.classList.add(options.draggingClass); dragging = getDragging(dragItem, sortableContainer); addAttribute(dragging, "aria-grabbed", "true"); sortableContainer.dispatchEvent(new CustomEvent("sortstart", { detail: { origin: { elementIndex: originElementIndex, index: originIndex, container: originContainer }, item: dragging, originalTarget: target, dragEvent: e } })); }); addEventListener(sortableElement, "dragenter", (e) => { const target = getEventTarget(e); const sortableContainer = findSortable(target, e); if (sortableContainer && sortableContainer !== previousContainer) { destinationItemsBeforeUpdate = filter(sortableContainer.children, addData(sortableContainer, "items")).filter((item) => item !== store(sortableElement).placeholder); if (options.dropTargetContainerClass) { sortableContainer.classList.add(options.dropTargetContainerClass); } sortableContainer.dispatchEvent(new CustomEvent("sortenter", { detail: { origin: { elementIndex: originElementIndex, index: originIndex, container: originContainer }, destination: { container: sortableContainer, itemsBeforeUpdate: destinationItemsBeforeUpdate }, item: dragging, originalTarget: target } })); addEventListener(sortableContainer, "dragleave", function(e2) { const outTarget = e2.relatedTarget || e2.fromElement; if (!e2.currentTarget.contains(outTarget)) { if (options.dropTargetContainerClass) { sortableContainer.classList.remove(options.dropTargetContainerClass); } sortableContainer.dispatchEvent(new CustomEvent("sortleave", { detail: { origin: { elementIndex: originElementIndex, index: originIndex, container: sortableContainer }, item: dragging, originalTarget: target } })); } }); } previousContainer = sortableContainer; }); addEventListener(sortableElement, "dragend", function(e) { if (!dragging) { return; } dragging.classList.remove(options.draggingClass); addAttribute(dragging, "aria-grabbed", "false"); if (dragging.getAttribute("aria-copied") === "true" && addData(dragging, "dropped") !== "true") { dragging.remove(); } if (dragging.oldDisplay !== void 0) { dragging.style.display = dragging.oldDisplay; delete dragging.oldDisplay; } const visiblePlaceholder = Array.from(stores.values()).map((data2) => data2.placeholder).filter((placeholder) => placeholder instanceof HTMLElement).filter(isInDom)[0]; if (visiblePlaceholder) { visiblePlaceholder.remove(); } sortableElement.dispatchEvent(new CustomEvent("sortstop", { detail: { origin: { elementIndex: originElementIndex, index: originIndex, container: originContainer }, item: dragging, dragEvent: e } })); previousContainer = null; dragging = null; draggingHeight = null; draggingWidth = null; }); addEventListener(sortableElement, "drop", function(e) { if (!listsConnected(sortableElement, dragging.parentElement)) { return; } e.preventDefault(); e.stopPropagation(); addData(dragging, "dropped", "true"); const visiblePlaceholder = Array.from(stores.values()).map((data2) => { return data2.placeholder; }).filter((placeholder2) => placeholder2 instanceof HTMLElement).filter(isInDom)[0]; if (visiblePlaceholder) { visiblePlaceholder.replaceWith(dragging); if (dragging.oldDisplay !== void 0) { dragging.style.display = dragging.oldDisplay; delete dragging.oldDisplay; } } else { addData(dragging, "dropped", "false"); return; } const placeholder = store(sortableElement).placeholder; const originItems = filter(originContainer.children, options.items).filter((item) => item !== placeholder); const destinationContainer = this.isSortable === true ? this : this.parentElement; const destinationItems = filter(destinationContainer.children, addData(destinationContainer, "items")).filter((item) => item !== placeholder); const destinationElementIndex = getIndex(dragging, Array.from(dragging.parentElement.children).filter((item) => item !== placeholder)); const destinationIndex = getIndex(dragging, destinationItems); if (options.dropTargetContainerClass) { destinationContainer.classList.remove(options.dropTargetContainerClass); } sortableElement.dispatchEvent(new CustomEvent("sortstop", { detail: { origin: { elementIndex: originElementIndex, index: originIndex, container: originContainer }, item: dragging, destination: { index: destinationIndex, elementIndex: destinationElementIndex, container: destinationContainer, itemsBeforeUpdate: destinationItemsBeforeUpdate, items: destinationItems }, dragEvent: e } })); if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) { sortableElement.dispatchEvent(new CustomEvent("sortupdate", { detail: { origin: { elementIndex: originElementIndex, index: originIndex, container: originContainer, itemsBeforeUpdate: originItemsBeforeUpdate, items: originItems }, destination: { index: destinationIndex, elementIndex: destinationElementIndex, container: destinationContainer, itemsBeforeUpdate: destinationItemsBeforeUpdate, items: destinationItems }, item: dragging, dragEvent: e } })); } }); const debouncedDragOverEnter = debounce( (sortableElement2, element, pageX, pageY) => { if (!dragging) { return; } if (options.forcePlaceholderSize) { store(sortableElement2).placeholder.style.height = draggingHeight + "px"; store(sortableElement2).placeholder.style.width = draggingWidth + "px"; } if (Array.from(sortableElement2.children).indexOf(element) > -1) { const thisHeight = getElementHeight(element); const thisWidth = getElementWidth(element); const placeholderIndex = getIndex(store(sortableElement2).placeholder, element.parentElement.children); const thisIndex = getIndex(element, element.parentElement.children); if (thisHeight > draggingHeight || thisWidth > draggingWidth) { const deadZoneVertical = thisHeight - draggingHeight; const deadZoneHorizontal = thisWidth - draggingWidth; const offsetTop = offset(element).top; const offsetLeft = offset(element).left; if (placeholderIndex < thisIndex && (options.orientation === "vertical" && pageY < offsetTop || options.orientation === "horizontal" && pageX < offsetLeft)) { return; } if (placeholderIndex > thisIndex && (options.orientation === "vertical" && pageY > offsetTop + thisHeight - deadZoneVertical || options.orientation === "horizontal" && pageX > offsetLeft + thisWidth - deadZoneHorizontal)) { return;