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 = (isLayoutChange = false) => { if (!scrollEl) return; const res = vs.calc(scrollEl, state.list); if (res) { if (!isLayoutChange && 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, isLayoutChange); } }; return { init: () => { scrollEl = container.querySelector(".dt-main"); }, reset: (list) => { state._listStartIndex = 0; vs.reset(list, scrollEl || container); if (state.list === list) { vs.init(list, () => refresh(true)); } }, refresh, onScroll: () => refresh(false) }; }; 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; if (window.__DT_FEATURES__ && !window.__DT_FEATURES__.selection) return; 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 nodes = body.children; for (let r = 0; r < nodes.length; r++) { const rowNode = nodes[r]; if (!rowNode.classList.contains("dt-body-row")) continue; const absoluteRow = (((_a = rowNode._ref) == null ? void 0 : _a.rIdx) ?? -1) + state._listStartIndex; if (!hasSelection || absoluteRow < boundMinRow || absoluteRow > boundMaxRow) { const cellNodes2 = rowNode.children; for (let i = 0; i < cellNodes2.length; i++) { cellNodes2[i].classList.remove("dt-cell-selected"); } continue; } const cellNodes = rowNode.children; for (let i = 0; i < cellNodes.length; i++) { const isSelected = isCellSelected(absoluteRow, i); const cell = cellNodes[i]; if (isSelected) cell.classList.add("dt-cell-selected"); else cell.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, _originalList: [], sortConfig: { fieldId: null, direction: null }, filterConfig: {}, activeFieldId: null, _internalUpdate: false, _appliedHash: "", _fieldsDirty: false, _masterCellNodes: null }); const perf = createPerfMonitor(); state.perf = perf.stats; const selection = createSelectionManager(container, state); const scroll = createScrollManager(container, state, (renderedCount, isLayoutChange) => { 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); }; let _scrollRaf = null; container.onScroll = () => { perf.onScroll(); container.refresh(); if (_scrollRaf) return; _scrollRaf = requestAnimationFrame(() => { _scrollRaf = null; const menu = container.querySelector(".dt-column-menu"); if (menu && menu.style.display !== "none" && state.activeFieldId) { const headerCell = container.querySelector(`.header-cell[data-id="${state.activeFieldId}"]`); if (headerCell) { const btn = headerCell.querySelector(".header-menu-btn"); const btnRect = btn.getBoundingClientRect(); const rootRect = container.getBoundingClientRect(); menu.style.left = btnRect.right - rootRect.left - 180 + "px"; menu.style.top = btnRect.bottom - rootRect.top + 5 + "px"; } } }); }; container.applySortFilter = (options = {}) => { if (state._internalUpdate) return; const targetFilters = { ...state.filterConfig, ...options.filters || {} }; const targetSort = options.sort !== void 0 ? options.sort ? { fieldId: state.activeFieldId, direction: options.sort } : { fieldId: null, direction: null } : state.sortConfig; const currentHash = JSON.stringify({ s: targetSort, f: targetFilters }); if (state._appliedHash === currentHash && !options.force) return; state._internalUpdate = true; const startTime = performance.now(); let list = options.force ? state.list : [...state._originalList || []]; if (!options.force) { Object.keys(targetFilters).forEach((fieldId) => { const val = targetFilters[fieldId]; if (val) { const lowerVal = String(val).toLowerCase(); list = list.filter((item) => String(item[fieldId] ?? "").toLowerCase().includes(lowerVal)); } }); if (targetSort && targetSort.fieldId && targetSort.direction) { list.sort((a, b) => { let va = a[targetSort.fieldId], vb = b[targetSort.fieldId]; if (va === vb) return 0; if (va === null || va === void 0) return 1; if (vb === null || vb === void 0) return -1; const res = va > vb ? 1 : -1; return targetSort.direction === "asc" ? res : -res; }); } } window.__perfTrace = { evalCount: 0, evalTotal: 0 }; performance.now(); state._appliedHash = currentHash; if (options.sort !== void 0) state.sortConfig = targetSort; state.list = list; state._internalUpdate = false; const frameEnd = performance.now(); const totalTime = frameEnd - startTime; console.log(`[DataTable Performance Profile] Sync Block: ${totalTime.toFixed(2)}ms (Eval: ${window.__perfTrace.evalCount})`); requestAnimationFrame(() => { setTimeout(() => { console.log(`[DataTable Performance Profile] E2E Paint: ${(performance.now() - startTime).toFixed(2)}ms`); }); }); window.__perfTrace = null; }; container.showColumnMenu = (field, e) => { e.stopPropagation(); const btn = e.currentTarget; const rect = btn.getBoundingClientRect(); const rootRect = container.getBoundingClientRect(); const menu = container.querySelector(".dt-column-menu"); state.activeFieldId = field.id; menu.style.display = "block"; menu.style.left = rect.right - rootRect.left - 180 + "px"; menu.style.top = rect.bottom - rootRect.top + 5 + "px"; const onGlobalClick = (ev) => { if (menu.contains(ev.target)) return; if (!btn.contains(ev.target)) { container.hideColumnMenu(); container.applySortFilter(); document.removeEventListener("mousedown", onGlobalClick); } }; document.addEventListener("mousedown", onGlobalClick); RefreshState(menu); setTimeout(() => { const input = menu.querySelector("input"); if (input) input.focus(); }, 50); }; container.hideColumnMenu = () => { const menu = container.querySelector(".dt-column-menu"); if (menu) menu.style.display = "none"; }; container.setSort = (direction) => { container.applySortFilter({ sort: direction }); }; container.clearColumnSettings = () => { const filters = { ...state.filterConfig }; delete filters[state.activeFieldId]; state.filterConfig = filters; container.applySortFilter({ sort: null }); container.hideColumnMenu(); }; container._initRow = (rowNode) => { if (!rowNode || !rowNode.children) return; const cells = rowNode.children; for (let i = 0; i < cells.length; i++) { const cell = cells[i]; const fIdx = parseInt(cell.dataset.fidx); if (!isNaN(fIdx)) { cell._refExt = { f: state.fields[fIdx], fIdx }; } } }; 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 = cell.dataset.fidx !== void 0 ? parseInt(cell.dataset.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); container.startSelect(rIdx + state._listStartIndex, 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 = cell.dataset.fidx !== void 0 ? parseInt(cell.dataset.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); container.updateSelect(rIdx + state._listStartIndex, fIdx); }; container.onMainDblClick = (e) => { var _a, _b, _c; 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 fIdx = cell.dataset.fidx !== void 0 ? parseInt(cell.dataset.fidx) : ((_b = cell._ref) == null ? void 0 : _b.fIdx) ?? Array.from(row.children).indexOf(cell); const field = ((_c = cell._ref) == null ? void 0 : _c.f) ?? state.fields[fIdx]; if (item && field) container.editCell(item, field, cell); }; state.__watch("fields", (fields) => { if (!fields) return; state._fieldsDirty = true; const gridTemplate = fields.map((f) => `var(--w-${f.id}, ${f.width || 150}px)`).join(" "); const totalWidth = fields.reduce((sum, f) => sum + (f.width || 150), 0); container.style.setProperty("--dt-grid-template", gridTemplate); container.style.setProperty("--dt-row-width", totalWidth + "px"); }); state.__watch("list", (list) => { if (state._fieldsDirty) { state._fieldsDirty = false; const rowTemplate = container.querySelector('.dt-body template[index="rIdx"]'); if (rowTemplate) { const fieldTemplate = rowTemplate.content.querySelector('template[as="f"]'); if (fieldTemplate) { if (!state._masterCellNodes) { state._masterCellNodes = Array.from(fieldTemplate.content.childNodes).map((n) => n.cloneNode(true)); } fieldTemplate.removeAttribute("$each"); fieldTemplate.removeAttribute("as"); fieldTemplate.removeAttribute("index"); fieldTemplate.setAttribute("$if", "true"); fieldTemplate.content.textContent = ""; state.fields.forEach((f, fIdx) => { state._masterCellNodes.forEach((master) => { const clone = master.cloneNode(true); if (clone.nodeType === 1) clone.dataset.fidx = fIdx; fieldTemplate.content.appendChild(clone); }); }); } } } if (!state._internalUpdate) state._originalList = [...list || []]; scroll.init(); scroll.reset(list); }); 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 = field.type === "textarea" || field.type === "TagsInput" ? "auto" : 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 = State.editingData = currentEditingNode = null; container.focus(); }; container.startSelect = selection.startSelect; container.updateSelect = selection.updateSelect; container.deleteSelected = selection.deleteSelected; 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*/ `