Source: plugins/draggable-headers.js

import BasePlugin from "../core/base-plugin.js";
import getParentElement from "../utils/getParentElement.js";
import { dispatch, findAll, getAttribute, on, setAttribute } from "../utils/shortcuts.js";

/**
 * Allows to move headers
 */
class DraggableHeaders extends BasePlugin {
    /**
     * @param {HTMLTableCellElement} th
     */
    makeHeaderDraggable(th) {
        const grid = this.grid;
        th.draggable = true;
        on(th, "dragstart", (e) => {
            if (grid.plugins.ColumnResizer?.isResizing && e.preventDefault) {
                e.preventDefault();
                return;
            }
            grid.log("reorder col");
            e.dataTransfer.effectAllowed = "move";
            e.dataTransfer.setData("text/plain", e.target.getAttribute("aria-colindex"));
        });
        on(th, "dragover", (e) => {
            if (e.preventDefault) {
                e.preventDefault();
            }
            e.dataTransfer.dropEffect = "move";
            return false;
        });
        on(th, "drop", (e) => {
            if (e.stopPropagation) {
                e.stopPropagation();
            }
            const t = e.target;
            const target = getParentElement(t, "TH");
            const index = Number.parseInt(e.dataTransfer.getData("text/plain"));
            const targetIndex = Number.parseInt(target.getAttribute("aria-colindex"));

            if (index === targetIndex) {
                grid.log("reordered col stayed the same");
                return;
            }
            grid.log(`reordered col from ${index} to ${targetIndex}`);

            const offset = grid.startColIndex();
            const tmp = grid.options.columns[index - offset];
            grid.options.columns[index - offset] = grid.options.columns[targetIndex - offset];
            grid.options.columns[targetIndex - offset] = tmp;

            const swapNodes = (selector, el1) => {
                const rowIndex = el1.parentNode.getAttribute("aria-rowindex");
                const el2 = grid.querySelector(
                    `${selector} tr[aria-rowindex="${rowIndex}"] [aria-colindex="${targetIndex}"]`,
                );
                setAttribute(el1, "aria-colindex", targetIndex);
                setAttribute(el2, "aria-colindex", index);
                const newNode = document.createElement("th");
                el1.parentNode.insertBefore(newNode, el1);
                el2.parentNode.replaceChild(el1, el2);
                newNode.parentNode.replaceChild(el2, newNode);
            };

            // Swap all rows in header and body
            for (const el1 of findAll(grid, `thead th[aria-colindex="${index}"]`)) {
                swapNodes("thead", el1);
            }
            for (const el1 of findAll(grid, `tbody td[aria-colindex="${index}"]`)) {
                swapNodes("tbody", el1);
            }

            // Updates the columns
            grid.options.columns = findAll(grid, "thead tr.dg-head-columns th[field]").map((th) =>
                grid.options.columns.find((c) => c.field === getAttribute(th, "field")),
            );

            dispatch(grid, "columnReordered", {
                col: tmp.field,
                from: index,
                to: targetIndex,
            });
            return false;
        });
    }
}

export default DraggableHeaders;