import { Component, NewState, RefreshState, Util } from "@web/state"; import { VirtualScroll, State } from "@web/base"; const createPerfMonitor = () => { let enabled = !!window.__DT_PERF_MODE__; const stats = { refreshTime: 0, refreshCount: 0, scrollCount: 0, totalNodes: 0 }; if (enabled && !window.__statePerformanceTelemetry) { window.__statePerformanceTelemetry = { scanCount: 0, reuseCount: 0, moveCount: 0 }; } return { get stats() { return stats; }, enable: () => { enabled = true; }, disable: () => { enabled = false; }, onScroll: () => { if (enabled) stats.scrollCount++; }, startFrame: () => { var _a, _b, _c; if (!enabled) return null; return { start: performance.now(), scan: ((_a = window.__statePerformanceTelemetry) == null ? void 0 : _a.scanCount) || 0, move: ((_b = window.__statePerformanceTelemetry) == null ? void 0 : _b.moveCount) || 0, reuse: ((_c = window.__statePerformanceTelemetry) == null ? void 0 : _c.reuseCount) || 0 }; }, endFrame: (startData, renderedCount) => { if (!enabled || !startData) return; stats.refreshCount++; stats.totalNodes += renderedCount; const elapsed = performance.now() - startData.start; stats.refreshTime += elapsed; const stPerf = window.__statePerformanceTelemetry; if (stPerf) { const scans = stPerf.scanCount - startData.scan; const moves = stPerf.moveCount - startData.move; const reuses = stPerf.reuseCount - startData.reuse; if (scans > 0 || elapsed > 2) { console.log(`[DataTable Frame] Time: ${elapsed.toFixed(2)}ms, Scans: ${scans}, Moves: ${moves}, Reuses: ${reuses}, Rows: ${renderedCount}`); } } } }; }; const createScrollManager = (container, state, onRenderedListChange) => { const vs = VirtualScroll({ itemHeight: 40 }); let scrollEl = null; const refresh = () => { if (!scrollEl) return; const res = vs.calc(scrollEl, state.list); if (res) { if (state.prevHeight === res.prevHeight && state.postHeight === res.postHeight && state._listStartIndex === res.listStartIndex && state._renderedList.length === res.renderedList.length) { return; } Object.assign(state, { prevHeight: res.prevHeight, postHeight: res.postHeight, _listStartIndex: res.listStartIndex, _renderedList: res.renderedList }); onRenderedListChange == null ? void 0 : onRenderedListChange(res.renderedList.length); } }; return { init: () => { scrollEl = container.querySelector(".dt-main"); }, reset: (list) => { state._listStartIndex = 0; state._renderedList = vs.reset(list, scrollEl || container) || []; if (state.list === list) { vs.init(list, refresh); } }, refresh, onScroll: refresh }; }; const createSelectionManager = (container, state) => { let activeBounds = null; let startCell = null; let multiSelections = []; const isCellSelected = (r, c) => { if (activeBounds && r >= activeBounds.minRow && r <= activeBounds.maxRow && c >= activeBounds.minCol && c <= activeBounds.maxCol) return true; return multiSelections.some((s) => r >= s.minRow && r <= s.maxRow && c >= s.minCol && c <= s.maxCol); }; let lastHadSelection = false; const applySelectionUI = () => { var _a; let boundMinRow = Infinity, boundMaxRow = -Infinity; if (activeBounds) { boundMinRow = Math.min(boundMinRow, activeBounds.minRow); boundMaxRow = Math.max(boundMaxRow, activeBounds.maxRow); } multiSelections.forEach((s) => { boundMinRow = Math.min(boundMinRow, s.minRow); boundMaxRow = Math.max(boundMaxRow, s.maxRow); }); const hasSelection = boundMinRow !== Infinity; if (!hasSelection && !lastHadSelection) return; lastHadSelection = hasSelection; const body = container.querySelector(".dt-body"); if (!body) return; const rowNodes = body.children; for (let r = 0; r < rowNodes.length; r++) { const rowNode = rowNodes[r]; if (!rowNode.classList.contains("dt-body-row")) continue; const absoluteRow = (((_a = rowNode._ref) == null ? void 0 : _a.rIdx) ?? -1) + state._listStartIndex; const cellNodes = rowNode.children; if (!hasSelection || absoluteRow < boundMinRow || absoluteRow > boundMaxRow) { for (let i = 0; i < cellNodes.length; i++) { if (cellNodes[i].classList.contains("dt-cell-selected")) { cellNodes[i].classList.remove("dt-cell-selected"); } } continue; } for (let i = 0; i < cellNodes.length; i++) { const isSelected = isCellSelected(absoluteRow, i); const hasClass = cellNodes[i].classList.contains("dt-cell-selected"); if (isSelected && !hasClass) { cellNodes[i].classList.add("dt-cell-selected"); } else if (!isSelected && hasClass) { cellNodes[i].classList.remove("dt-cell-selected"); } } } }; const updateStatus = () => { let count = 0; if (activeBounds) count += activeBounds.maxRow - activeBounds.minRow + 1; multiSelections.forEach((s) => count += s.maxRow - s.minRow + 1); state.selectedRowCount = count; }; const clearAllActive = (keepSelection = false) => { if (!keepSelection) { activeBounds = null; startCell = null; multiSelections = []; applySelectionUI(); updateStatus(); } }; const startSelect = (row, col, e) => { const alreadySelected = isCellSelected(row, col); const isRange = activeBounds && (activeBounds.minRow !== activeBounds.maxRow || activeBounds.minCol !== activeBounds.maxCol) || multiSelections.length > 0; if (e.shiftKey && startCell) { activeBounds = { minRow: Math.min(startCell.row, row), maxRow: Math.max(startCell.row, row), minCol: Math.min(startCell.col, col), maxCol: Math.max(startCell.col, col) }; } else { if (alreadySelected && !e.ctrlKey && !e.metaKey) { if (!isRange) container._potentialCancel = { row, col }; } else { if (!e.ctrlKey && !e.metaKey) { clearAllActive(); } else if (activeBounds && !alreadySelected) { multiSelections.push(activeBounds); } startCell = { row, col }; activeBounds = { minRow: row, maxRow: row, minCol: col, maxCol: col }; } state.isSelecting = true; } applySelectionUI(); updateStatus(); container.focus(); }; const updateSelect = (row, col) => { if (state.isSelecting && startCell) { activeBounds = { minRow: Math.min(startCell.row, row), maxRow: Math.max(startCell.row, row), minCol: Math.min(startCell.col, col), maxCol: Math.max(startCell.col, col) }; container._potentialCancel = null; applySelectionUI(); updateStatus(); } }; const endSelect = () => { if (container._potentialCancel) { const { row, col } = container._potentialCancel; if (isCellSelected(row, col)) { clearAllActive(); } container._potentialCancel = null; } state.isSelecting = false; }; const getSelectionBounds = () => { if (!activeBounds) return null; let minRow = activeBounds.minRow, maxRow = activeBounds.maxRow; let minCol = activeBounds.minCol, maxCol = activeBounds.maxCol; multiSelections.forEach((s) => { minRow = Math.min(minRow, s.minRow); maxRow = Math.max(maxRow, s.maxRow); minCol = Math.min(minCol, s.minCol); maxCol = Math.max(maxCol, s.maxCol); }); return { minRow, maxRow, minCol, maxCol }; }; const copy = async () => { const bounds = getSelectionBounds(); if (!bounds) return; const text = state.list.slice(bounds.minRow, bounds.maxRow + 1).map((row) => { return state.fields.slice(bounds.minCol, bounds.maxCol + 1).map((f) => { let val = String(row[f.id] ?? ""); if (val.includes(" ") || val.includes("\n") || val.includes('"')) { val = '"' + val.replace(/"/g, '""') + '"'; } return val; }).join(" "); }).join("\n"); await navigator.clipboard.writeText(text); }; const paste = async () => { try { const text = await navigator.clipboard.readText(); if (!text) return; const bounds = getSelectionBounds(); if (!bounds) return; const rows = text.split(/\r?\n/).filter((line) => line.length > 0).map((line) => { const cells = []; let current = "", inQuotes = false; for (let i = 0; i < line.length; i++) { const char = line[i]; if (char === '"') { if (inQuotes && line[i + 1] === '"') { current += '"'; i++; } else inQuotes = !inQuotes; } else if (char === " " && !inQuotes) { cells.push(current); current = ""; } else { current += char; } } cells.push(current); return cells; }); const startRow = bounds.minRow; const startCol = bounds.minCol; const maxRow = bounds.maxRow; const maxCol = bounds.maxCol; const body = container.querySelector(".dt-body"); const rowNodes = body ? Array.from(body.childNodes).filter((n) => { var _a; return (_a = n.classList) == null ? void 0 : _a.contains("dt-body-row"); }) : []; rows.forEach((rowData, rOffset) => { const rIdx = startRow + rOffset; if (rIdx > maxRow || rIdx >= state.list.length) return; const rowItem = state.list[rIdx]; let rowChanged = false; rowData.forEach((cellData, cOffset) => { const cIdx = startCol + cOffset; if (cIdx > maxCol || cIdx >= state.fields.length) return; const field = state.fields[cIdx]; rowItem[field.id] = cellData; rowChanged = true; }); if (rowChanged && container.refreshNode) { const domNode = rowNodes.find((n) => { var _a; return (((_a = n._ref) == null ? void 0 : _a.rIdx) ?? -1) + state._listStartIndex === rIdx; }); if (domNode) container.refreshNode(domNode); } }); } catch (err) { console.error("Paste Error:", err); } }; const deleteSelected = () => { const bounds = getSelectionBounds(); if (!bounds) return; const rMin = bounds.minRow, rMax = bounds.maxRow; const newList = state.list.filter((_, i) => !(i >= rMin && i <= rMax)); state.list = newList; clearAllActive(); }; return { applySelectionUI, clearAllActive, startSelect, updateSelect, endSelect, deleteSelected, getSelectionBounds, copy, paste }; }; Component.register("DataTable", (container) => { if (!container.state) container.state = NewState({}); const state = container.state; Object.assign(state, { list: [], fields: [], _renderedList: [], prevHeight: 0, postHeight: 0, _listStartIndex: 0, selectedRowCount: 0 }); const perf = createPerfMonitor(); state.perf = perf.stats; const selection = createSelectionManager(container, state); const scroll = createScrollManager(container, state, (renderedCount) => { container.hideEditor(); selection.applySelectionUI(); }); let _prevSpacer, _postSpacer, _editorOverlay; container.format = (val, field) => { if (field.formatter) return field.formatter(val, field); if (typeof val === "string") return val; if (val === null || val === void 0) return ""; return typeof val === "object" ? JSON.stringify(val) : String(val); }; container.refreshNode = (node) => RefreshState(node); container.refresh = () => { const frameStart = perf.startFrame(); scroll.refresh(); if (!_prevSpacer) _prevSpacer = container.querySelector(".dt-spacer-prev"); if (_prevSpacer) { const h = state.prevHeight || 0; _prevSpacer.style.height = h + "px"; _prevSpacer.style.display = h > 0 ? "block" : "none"; } if (!_postSpacer) _postSpacer = container.querySelector(".dt-spacer-post"); if (_postSpacer) { const h = state.postHeight || 0; _postSpacer.style.height = h + "px"; _postSpacer.style.display = h > 0 ? "block" : "none"; } perf.endFrame(frameStart, state._renderedList.length); }; container.onScroll = () => { perf.onScroll(); container.refresh(); }; container.onMainMouseDown = (e) => { var _a, _b; const cell = e.target.closest(".dt-cell"); if (!cell) return; const row = cell.closest(".dt-row"); if (!row || row.classList.contains("dt-header-row")) return; const fIdx = ((_a = cell._ref) == null ? void 0 : _a.fIdx) ?? Array.from(row.children).indexOf(cell); const rIdx = ((_b = row._ref) == null ? void 0 : _b.rIdx) ?? Array.from(container.querySelectorAll(".dt-body-row")).indexOf(row); const absoluteRow = rIdx + state._listStartIndex; container.startSelect(absoluteRow, fIdx, e); }; container.onMainMouseOver = (e) => { var _a, _b; if (!state.isSelecting) return; const cell = e.target.closest(".dt-cell"); if (!cell) return; const row = cell.closest(".dt-row"); if (!row || row.classList.contains("dt-header-row")) return; const fIdx = ((_a = cell._ref) == null ? void 0 : _a.fIdx) ?? Array.from(row.children).indexOf(cell); const rIdx = ((_b = row._ref) == null ? void 0 : _b.rIdx) ?? Array.from(container.querySelectorAll(".dt-body-row")).indexOf(row); const absoluteRow = rIdx + state._listStartIndex; container.updateSelect(absoluteRow, fIdx); }; container.onMainDblClick = (e) => { var _a, _b; const cell = e.target.closest(".dt-cell"); if (!cell) return; const row = cell.closest(".dt-row"); if (!row || row.classList.contains("dt-header-row")) return; const item = (_a = row._ref) == null ? void 0 : _a.item; const field = (_b = cell._ref) == null ? void 0 : _b.f; if (item && field) container.editCell(item, field, cell); }; state.__watch("list", (list) => { scroll.init(); scroll.reset(list); }); state.__watch("fields", (fields) => { if (!fields) return; const gridTemplate = fields.map((f) => `var(--w-${f.id}, ${f.width || 150}px)`).join(" "); container.style.setProperty("--dt-grid-template", gridTemplate); }); let currentEditingNode = null; container.editCell = (row, field, cellNode) => { const overlay = container.querySelector(".dt-editor-overlay"); const editor = overlay.querySelector("AutoForm"); const rect = cellNode.getBoundingClientRect(); const rootRect = container.getBoundingClientRect(); currentEditingNode = cellNode; let minW = rect.width; if (field.type === "textarea" || field.type === "TagsInput") minW = Math.max(rect.width, 300); else if (field.type === "radio") minW = Math.max(rect.width, 240); overlay.style.display = "flex"; overlay.style.left = rect.left - rootRect.left + "px"; overlay.style.top = rect.top - rootRect.top + "px"; overlay.style.width = minW + "px"; overlay.style.height = ""; overlay.style.minHeight = ""; overlay.style.alignItems = "center"; if (field.type === "textarea" || field.type === "TagsInput") { overlay.style.minHeight = (field.type === "textarea" ? 150 : rect.height) + "px"; overlay.style.height = "auto"; overlay.style.alignItems = "flex-start"; } else { overlay.style.height = rect.height + "px"; } State.editingSchema = [{ ...field, name: field.id, label: "" }]; State.editingData = row; RefreshState(overlay); setTimeout(() => { const input = editor.querySelector(".form-control, .form-select, .form-check-input, input"); if (input) input.focus(); }, 30); }; container.hideEditor = (save = true) => { if (!_editorOverlay) _editorOverlay = container.querySelector(".dt-editor-overlay"); if (!_editorOverlay || _editorOverlay.style.display === "none") return; _editorOverlay.style.display = "none"; if (save && currentEditingNode) { RefreshState(currentEditingNode); } State.editingSchema = null; State.editingData = null; currentEditingNode = null; container.focus(); }; container.startSelect = selection.startSelect; container.updateSelect = selection.updateSelect; container.deleteSelected = selection.deleteSelected; container.addEventListener("keydown", (e) => { if (e.ctrlKey || e.metaKey) { const k = e.key.toLowerCase(); if (k === "c") { e.preventDefault(); selection.copy(); } if (k === "v") { e.preventDefault(); selection.paste(); } } if (e.key === "Escape") container.hideEditor(false); }); const onGlobalMouseDown = (e) => { const overlay = container.querySelector(".dt-editor-overlay"); if (overlay && overlay.style.display !== "none" && !overlay.contains(e.target)) { container.hideEditor(true); } if (!container.contains(e.target) && !(overlay == null ? void 0 : overlay.contains(e.target))) selection.clearAllActive(); }; window.addEventListener("mouseup", selection.endSelect); document.addEventListener("mousedown", onGlobalMouseDown); container._onUnload = () => { document.removeEventListener("mousedown", onGlobalMouseDown); window.removeEventListener("mouseup", selection.endSelect); }; }, Util.makeDom( /*html*/ `
` ), Util.makeDom( /*html*/ ` ` )); if (typeof document !== "undefined") RefreshState(document.documentElement);