// Audit results grid column UX (#23 follow-ups Task 10). // // A tiny, dependency-free helper for the AuditResultsGrid component: // - drag-to-resize: a pointer-driven handle on each
| DOM // node for a different column (after a reorder re-render) is harmless. init: function (table, dotNet) { if (!table) { return; } table.__auditGridDotNet = dotNet; var headerRow = table.tHead && table.tHead.rows[0]; if (!headerRow) { return; } for (var i = 0; i < headerRow.cells.length; i++) { this._bindHeaderCell(table, headerRow.cells[i]); } }, // Bind resize + reorder handlers to a single | . Idempotent — a cell // already carrying handlers is skipped. The handlers resolve the column // key live (th.getAttribute) so they stay correct if the renderer reuses // the element for another column. _bindHeaderCell: function (table, th) { var self = this; if (th.__auditGridCellBound) { return; } th.__auditGridCellBound = true; // --- resize: pointer drag on the handle --------------------------- var handle = th.querySelector(".audit-grid-resize-handle"); if (handle) { handle.addEventListener("pointerdown", function (ev) { ev.preventDefault(); // Stop the pointerdown from also starting a header drag. ev.stopPropagation(); var startX = ev.clientX; var startWidth = th.getBoundingClientRect().width; handle.setPointerCapture(ev.pointerId); th.classList.add("resizing"); function onMove(moveEv) { var next = Math.max(self.minWidth, startWidth + (moveEv.clientX - startX)); self._applyWidth(th, next); } function onUp() { handle.releasePointerCapture(ev.pointerId); handle.removeEventListener("pointermove", onMove); handle.removeEventListener("pointerup", onUp); handle.removeEventListener("pointercancel", onUp); th.classList.remove("resizing"); var key = th.getAttribute("data-col-key"); var finalWidth = Math.round(th.getBoundingClientRect().width); var dn = table.__auditGridDotNet; if (key && dn) { dn.invokeMethodAsync("OnColumnResized", key, finalWidth); } } handle.addEventListener("pointermove", onMove); handle.addEventListener("pointerup", onUp); handle.addEventListener("pointercancel", onUp); }); } // --- reorder: native HTML5 drag-and-drop on the header ------------ // The whole | is draggable; dropping it onto another header swaps // the dragged column into the drop target's position. th.setAttribute("draggable", "true"); th.addEventListener("dragstart", function (ev) { // A resize in progress sets .resizing; never start a reorder then. if (th.classList.contains("resizing")) { ev.preventDefault(); return; } var key = th.getAttribute("data-col-key"); if (!key) { ev.preventDefault(); return; } table.__auditGridDragKey = key; ev.dataTransfer.effectAllowed = "move"; // Some browsers require data to be set for the drag to begin. try { ev.dataTransfer.setData("text/plain", key); } catch { /* ignore */ } th.classList.add("dragging"); }); th.addEventListener("dragend", function () { th.classList.remove("dragging"); table.__auditGridDragKey = null; self._clearDropTargets(table); }); th.addEventListener("dragover", function (ev) { // Allowing the drop is what lets dragover/drop fire at all. var key = th.getAttribute("data-col-key"); if (key && table.__auditGridDragKey && table.__auditGridDragKey !== key) { ev.preventDefault(); ev.dataTransfer.dropEffect = "move"; th.classList.add("drop-target"); } }); th.addEventListener("dragleave", function () { th.classList.remove("drop-target"); }); th.addEventListener("drop", function (ev) { ev.preventDefault(); th.classList.remove("drop-target"); var key = th.getAttribute("data-col-key"); var fromKey = table.__auditGridDragKey; table.__auditGridDragKey = null; if (!key || !fromKey || fromKey === key) { return; } var dn = table.__auditGridDotNet; if (dn) { // fromKey moves to occupy toKey's slot; the component computes // the resulting order and re-renders + persists. dn.invokeMethodAsync("OnColumnReordered", fromKey, key); } }); }, // Apply a width to a | via a CSS custom property. The scoped stylesheet // reads --audit-col-width; absent it, the column falls back to auto. // // Known, intentional behaviour: during a live resize drag this updates the // | width immediately, but the | body cells only catch up on the next // .NET re-render (driven by OnColumnResized at pointer-up). The brief // header/body width mismatch mid-drag is an accepted trade-off for an // internal tool — not a bug. _applyWidth: function (th, widthPx) { th.style.setProperty("--audit-col-width", widthPx + "px"); }, _clearDropTargets: function (table) { var hits = table.querySelectorAll(".drop-target, .dragging"); for (var i = 0; i < hits.length; i++) { hits[i].classList.remove("drop-target", "dragging"); } } }; |
|---|