(function(factory) { typeof define === "function" && define.amd ? define(factory) : factory(); })(function() { "use strict"; const MODE_MAP = { text: ["contains", "equals", "starts", "ends"], textarea: ["contains", "equals", "starts", "ends"], number: ["=", ">", "<", "between"], date: ["=", ">", "<", "between"], select: ["contains", "equals"], TagsInput: ["contains", "equals", "starts", "ends"] }; const MODE_ICONS = { "contains": "bi-search", "equals": "bi-distribute-vertical", "starts": "bi-align-start", "ends": "bi-align-end", "=": "bi-calculator", ">": "bi-chevron-right", "<": "bi-chevron-left", "between": "bi-arrows-expand" }; const DataTableConfig = { _fieldTypes: /* @__PURE__ */ new Map(), registerFieldType: (config) => { DataTableConfig._fieldTypes.set(config.value, config); }, getFieldTypes: () => Array.from(DataTableConfig._fieldTypes.values()) }; DataTableConfig.registerFieldType({ value: "text", label: "{#Text#}", typeForDB: "v4096", schema: [{ name: "placeholder", label: "Placeholder", type: "text", if: 'this.data.user_type=="text"' }] }); DataTableConfig.registerFieldType({ value: "number", label: "{#Number#}", typeForDB: "ff", schema: [ { name: "decimals", label: "Decimals", type: "number", setting: { min: 0, max: 10 }, if: 'this.data.user_type=="number"' }, { name: "prefix", label: "Prefix (e.g. $)", type: "text", if: 'this.data.user_type=="number"' }, { name: "suffix", label: "Suffix (e.g. %)", type: "text", if: 'this.data.user_type=="number"' }, { name: "thousandSep", label: "Thousand Sep", type: "switch", if: 'this.data.user_type=="number"' } ], formatter: (val, field) => { if (val == null || val === "") return ""; let num = Number(val); if (isNaN(num)) return val; const s = field.settings || {}; if (s.decimals !== void 0) num = num.toFixed(s.decimals); let str = String(num); if (s.thousandSep) { const parts = str.split("."); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); str = parts.join("."); } return (s.prefix || "") + str + (s.suffix || ""); } }); DataTableConfig.registerFieldType({ value: "select", label: "{#Single Select#}", typeForDB: "v1024", schema: [{ name: "options_str", label: "Options", type: "textarea", setting: { rows: 3, placeholder: "Label:Value per line" }, if: 'this.data.user_type=="select"' }], formatter: (val, field) => { var _a; if (val == null || val === "") return ""; const opts = ((_a = field.settings) == null ? void 0 : _a.options) || []; const opt = opts.find((o) => typeof o === "object" ? o.value == val : o == val); return opt ? typeof opt === "object" ? opt.label : opt : val; } }); DataTableConfig.registerFieldType({ value: "checkbox", label: "{#Multi Select#}", typeForDB: "v4096", schema: [{ name: "options_str", label: "Options", type: "textarea", setting: { rows: 3, placeholder: "Label:Value per line" }, if: 'this.data.user_type=="checkbox"' }], formatter: (val, field) => { var _a; if (!Array.isArray(val)) return val == null ? "" : String(val); const opts = ((_a = field.settings) == null ? void 0 : _a.options) || []; return val.map((v) => { const opt = opts.find((o) => typeof o === "object" ? o.value == v : o == v); return opt ? typeof opt === "object" ? opt.label : opt : v; }).join(", "); } }); DataTableConfig.registerFieldType({ value: "switch", label: "{#Switch#}", typeForDB: "b", schema: [ { name: "labelOn", label: "Label On", type: "text", if: 'this.data.user_type=="switch"' }, { name: "labelOff", label: "Label Off", type: "text", if: 'this.data.user_type=="switch"' } ], formatter: (val, field) => { const s = field.settings || {}; return val ? s.labelOn || "Yes" : s.labelOff || "No"; } }); DataTableConfig.registerFieldType({ value: "datetime", label: "{#DateTime#}", typeForDB: "dt", schema: [{ name: "format", label: "Format", type: "text", setting: { placeholder: "YYYY-MM-DD" }, if: 'this.data.user_type=="datetime"' }] }); DataTableConfig.registerFieldType({ value: "textarea", label: "{#Long Text#}", typeForDB: "t", schema: [{ name: "placeholder", label: "Placeholder", type: "text", if: 'this.data.user_type=="textarea"' }] }); const createPerfMonitor = () => { let enabled = !!globalThis.__DT_PERF_MODE__; const stats = { refreshTime: 0, refreshCount: 0, scrollCount: 0, totalNodes: 0 }; if (enabled && !globalThis.__statePerformanceTelemetry) { globalThis.__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 = globalThis.__statePerformanceTelemetry) == null ? void 0 : _a.scanCount) || 0, move: ((_b = globalThis.__statePerformanceTelemetry) == null ? void 0 : _b.moveCount) || 0, reuse: ((_c = globalThis.__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 = globalThis.__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 = globalThis.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 = () => { if (globalThis.__DT_FEATURES__ && !globalThis.__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 rowNodes = body.querySelectorAll(".dt-body-row"); rowNodes.forEach((rowNode) => { var _a; const absoluteRow = (((_a = rowNode._ref) == null ? void 0 : _a.rIdx) ?? -1) + state._listStartIndex; const cells = rowNode.querySelectorAll(".dt-cell"); if (!hasSelection || absoluteRow < boundMinRow || absoluteRow > boundMaxRow) { cells.forEach((cell) => cell.classList.remove("dt-cell-selected")); return; } cells.forEach((cell, cIdx) => { if (isCellSelected(absoluteRow, cIdx)) 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, 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 { minRow: startRow, minCol: startCol, maxRow, maxCol } = bounds; 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"); }) : []; let anyRowChanged = false; 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) anyRowChanged = true; }); if (anyRowChanged) state.list = [...state.list]; } catch (err) { console.error("Paste Error:", err); } }; return { applySelectionUI, clearAllActive, startSelect, updateSelect, endSelect, getSelectionBounds, copy, paste }; }; globalThis.Component.register("DataTable", (container) => { if (!container.state) container.state = globalThis.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, activeField: null, activeModes: [], _columnStats: {}, _internalUpdate: false, _appliedHash: "", _fieldsDirty: false, _masterCellNodes: null, isDirty: false, isBulkEdit: null }); const perf = createPerfMonitor(); state.perf = perf.stats; const selection = createSelectionManager(container, state); const scroll = createScrollManager(container, state, () => selection.applySelectionUI()); const menuNode = container.querySelector(".dt-column-menu"); if (menuNode) menuNode._thisObj = container; container.onColumnResizing = (field, e) => container.style.setProperty(`--w-${field.id}`, e.detail.newSize + "px"); container.onColumnResize = (field, e) => { const idx = state.fields.findIndex((f) => f.id === field.id); if (idx !== -1) { state.fields[idx].width = e.detail.newSize; state.fields = [...state.fields]; } }; let _editorOverlay, currentEditingNode = null; container.format = (val, field) => { var _a; if (field.formatter) return field.formatter(val, field); const typeInfo = DataTableConfig._fieldTypes.get(((_a = field.settings) == null ? void 0 : _a.formType) || field.type || "text"); if (typeInfo && typeInfo.formatter) return typeInfo.formatter(val, field); return val == null ? "" : typeof val === "object" ? JSON.stringify(val) : String(val); }; container.onScroll = () => { perf.onScroll(); scroll.refresh(); container.hideColumnMenu(); const prev = container.querySelector(".dt-spacer-prev"), post = container.querySelector(".dt-spacer-post"); if (prev) { prev.style.height = (state.prevHeight || 0) + "px"; prev.style.display = state.prevHeight > 0 ? "block" : "none"; } if (post) { post.style.height = (state.postHeight || 0) + "px"; post.style.display = state.postHeight > 0 ? "block" : "none"; } }; 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; let filtered = [...state._originalList]; Object.entries(targetFilters).forEach(([fId, cfg]) => { if (!cfg.value && (!cfg.selectedValues || cfg.selectedValues.length === 0)) return; filtered = filtered.filter((item) => { var _a; const val = item[fId]; if (((_a = cfg.selectedValues) == null ? void 0 : _a.length) > 0) return cfg.selectedValues.includes(String(val)); const search = String(cfg.value).toLowerCase(); const target = String(val ?? "").toLowerCase(); switch (cfg.mode) { case "contains": return target.includes(search); case "equals": return target === search; case "starts": return target.startsWith(search); case "ends": return target.endsWith(search); case "=": return Number(val) === Number(cfg.value); case ">": return Number(val) > Number(cfg.value); case "<": return Number(val) < Number(cfg.value); case "between": return Number(val) >= Number(cfg.value) && Number(val) <= Number(cfg.value2); default: return true; } }); }); if (targetSort.fieldId && targetSort.direction) { const fId = targetSort.fieldId; const dir = targetSort.direction === "asc" ? 1 : -1; filtered.sort((a, b) => { if (a[fId] == b[fId]) return 0; return a[fId] > b[fId] ? dir : -dir; }); } state._internalUpdate = true; state.filterConfig = targetFilters; state.sortConfig = targetSort; state.list = filtered; state._internalUpdate = false; }; container.showColumnMenu = (field, event) => { var _a; const btn = event.currentTarget, menu = container.querySelector(".dt-column-menu"); const type = ((_a = field.settings) == null ? void 0 : _a.formType) || field.type || "text"; state.activeModes = MODE_MAP[type] || (["boolean", "switch", "checkbox", "radio"].includes(type) ? [] : MODE_MAP.text); if (!state.filterConfig[field.id]) state.filterConfig[field.id] = { mode: state.activeModes[0] || "contains", value: "", selectedValues: [] }; state.activeField = field; state.activeFieldId = field.id; menu.style.display = "block"; const cellNode = btn.closest(".dt-cell"), rect = cellNode.getBoundingClientRect(), rootRect = container.getBoundingClientRect(); const menuWidth = menu.offsetWidth || 260; let leftPos = rect.right - rootRect.left - menuWidth; if (leftPos < 0) leftPos = Math.max(0, rect.left - rootRect.left); menu.style.left = leftPos + "px"; menu.style.top = rect.bottom - rootRect.top + 5 + "px"; const onGlobalClick = (ev) => { if (menu.contains(ev.target) || btn.contains(ev.target)) return; container.hideColumnMenu(); container.applySortFilter(); document.removeEventListener("mousedown", onGlobalClick); }; document.addEventListener("mousedown", onGlobalClick); setTimeout(() => { var _a2; return (_a2 = menu.querySelector("input")) == null ? void 0 : _a2.focus(); }, 50); }; container.toggleSelectedValue = (val) => { const filter = state.filterConfig[state.activeFieldId]; if (!filter) return; const idx = filter.selectedValues.indexOf(val); if (idx === -1) filter.selectedValues.push(val); else filter.selectedValues.splice(idx, 1); state.filterConfig = { ...state.filterConfig }; container.applySortFilter(); }; container.filterOnlyThis = (val) => { state.filterConfig[state.activeFieldId] = { mode: "contains", value: "", selectedValues: [String(val)] }; state.filterConfig = { ...state.filterConfig }; container.applySortFilter(); }; container.hideColumnMenu = () => { const menu = container.querySelector(".dt-column-menu"); if (menu) menu.style.display = "none"; }; container.setSort = (dir) => { const newDir = state.sortConfig.direction === dir && state.sortConfig.fieldId === state.activeFieldId ? null : dir; container.applySortFilter({ sort: newDir }); }; container.clearColumnSettings = () => { if (state.activeFieldId) { delete state.filterConfig[state.activeFieldId]; state.filterConfig = { ...state.filterConfig }; container.applySortFilter(); } }; container._initRow = (rowNode) => { var _a; const row = (_a = rowNode._ref) == null ? void 0 : _a.item; if (row && row._editingF === void 0) { Object.defineProperty(row, "_editingF", { set: (v) => { if (v === null) container.hideEditor(true); }, configurable: true }); } Array.from(rowNode.children).forEach((cell) => { const fIdx = parseInt(cell.dataset.fidx); if (!isNaN(fIdx)) cell._ref = { ...cell._ref || rowNode._ref, f: state.fields[fIdx], fIdx }; }); }; state.__watch("fields", (fields) => { if (!fields) return; state._fieldsDirty = true; state._masterCellNodes = null; container.style.setProperty("--dt-grid-template", fields.map((f) => { var _a; return `var(--w-${f.id}, ${((_a = f.settings) == null ? void 0 : _a.width) || f.width || 150}px)`; }).join(" ")); container.style.setProperty("--dt-row-width", fields.reduce((sum, f) => { var _a; return sum + (((_a = f.settings) == null ? void 0 : _a.width) || f.width || 150); }, 0) + "px"); let leftSum = 0; fields.forEach((f) => { var _a, _b; const pinned = ((_a = f.settings) == null ? void 0 : _a.pinned) || f.pinned; if (pinned === "left") { container.style.setProperty(`--l-${f.id}`, leftSum + "px"); leftSum += ((_b = f.settings) == null ? void 0 : _b.width) || f.width || 150; } }); let rightSum = 0; [...fields].reverse().forEach((f) => { var _a, _b; const pinned = ((_a = f.settings) == null ? void 0 : _a.pinned) || f.pinned; if (pinned === "right") { container.style.setProperty(`--r-${f.id}`, rightSum + "px"); rightSum += ((_b = f.settings) == null ? void 0 : _b.width) || f.width || 150; } }); }); state.__watch("list", (list) => { var _a; if (state._fieldsDirty) { state._fieldsDirty = false; const fieldTemplate = (_a = container.querySelector('.dt-body template[index="rIdx"]')) == null ? void 0 : _a.content.querySelector('template[as="f"]'); if (fieldTemplate) { const masters = state._masterCellNodes || (state._masterCellNodes = Array.from(fieldTemplate.content.childNodes).map((n) => n.cloneNode(true))); fieldTemplate.removeAttribute("$each"); fieldTemplate.setAttribute("$if", "true"); fieldTemplate.content.textContent = ""; state.fields.forEach((f, fIdx) => masters.forEach((master) => { var _a2; const clone = master.cloneNode(true); if (clone.nodeType === 1) { clone.dataset.fidx = fIdx; const pinned = ((_a2 = f.settings) == null ? void 0 : _a2.pinned) || f.pinned; if (pinned) { clone.classList.add("pinned-" + pinned); clone.style.position = "sticky"; clone.style.zIndex = "1"; clone.style.backgroundColor = "inherit"; if (pinned === "left") { clone.style.left = `var(--l-${f.id})`; clone.style.borderRight = "1px solid var(--bs-border-color)"; clone.style.boxShadow = "2px 0 5px -2px rgba(0,0,0,0.1)"; } else { clone.style.right = `var(--r-${f.id})`; clone.style.borderLeft = "1px solid var(--bs-border-color)"; clone.style.boxShadow = "-2px 0 5px -2px rgba(0,0,0,0.1)"; } } } fieldTemplate.content.appendChild(clone); })); } } if (!state._internalUpdate) { state._originalList = [...list || []]; setTimeout(() => { const stats = {}; state.fields.forEach((f) => { const counts = {}; state._originalList.forEach((item) => { const val = item[f.id], key = val == null || val === "" ? "" : String(val); counts[key] = (counts[key] || 0) + 1; }); stats[f.id] = Object.entries(counts).sort((a, b) => b[1] - a[1]).slice(0, 20).map(([val, count]) => ({ val, count })); }); state._columnStats = stats; }, 200); } scroll.init(); scroll.reset(list); }); container.editCell = (row, field, cellNode) => { var _a, _b; const overlay = container.querySelector(".dt-editor-overlay"), rect = cellNode.getBoundingClientRect(), rootRect = container.getBoundingClientRect(); currentEditingNode = cellNode; const formType = ((_a = field.settings) == null ? void 0 : _a.formType) || field.type || "text"; const form = overlay.querySelector("AutoForm"); if (form) { form.data = row; form.state.schema = [{ ...field, type: formType, options: ((_b = field.settings) == null ? void 0 : _b.options) || field.options, name: field.id, label: "" }]; } const isComplex = ["textarea", "TagsInput", "checkbox", "radio"].includes(formType); Object.assign(overlay.style, { display: "flex", left: rect.left - rootRect.left - 1 + "px", top: rect.top - rootRect.top - 1 + "px", width: Math.max(rect.width + 2, isComplex ? 300 : 0) + "px", height: "auto", minHeight: rect.height + 2 + "px", padding: "0" }); setTimeout(() => { var _a2; return (_a2 = overlay.querySelector("input, textarea, select, .form-control")) == null ? void 0 : _a2.focus(); }, 30); }; container.hideEditor = (save = true) => { if (!_editorOverlay) _editorOverlay = container.querySelector(".dt-editor-overlay"); if (!_editorOverlay || _editorOverlay.style.display === "none") return; const form = _editorOverlay.querySelector("AutoForm"); if (save && form && form.data) { const input = _editorOverlay.querySelector("input:focus, select:focus, textarea:focus"); if (input) input.dispatchEvent(new Event(input.type === "number" || input.tagName === "SELECT" ? "change" : "input", { bubbles: true })); const schema = form.state.schema || []; schema.forEach((field) => { var _a, _b; const row = (_b = (_a = currentEditingNode == null ? void 0 : currentEditingNode.closest(".dt-row")) == null ? void 0 : _a._ref) == null ? void 0 : _b.item; if (row) row[field.name] = form.data[field.name]; }); if (state.isBulkEdit) { const { minRow, maxRow, fIdx } = state.isBulkEdit; const field = state.fields[fIdx]; const newValue = form.data[field.id]; for (let i = minRow; i <= maxRow; i++) { if (state.list[i]) state.list[i][field.id] = newValue; } } state.list = [...state.list]; state.isDirty = true; } _editorOverlay.style.display = "none"; if (form) { form.state.schema = []; form.data = null; } currentEditingNode = null; state.isBulkEdit = null; container.focus(); }; container.onMainMouseDown = (e) => { var _a; const cell = e.target.closest(".dt-cell"), row = cell == null ? void 0 : cell.closest(".dt-row"); if (!row || row.classList.contains("dt-header-row")) return; const fIdx = cell.dataset.fidx ? parseInt(cell.dataset.fidx) : Array.from(row.querySelectorAll(".dt-cell")).indexOf(cell); const rIdx = ((_a = row._ref) == null ? void 0 : _a.rIdx) ?? Array.from(container.querySelectorAll(".dt-body-row")).indexOf(row); selection.startSelect(rIdx + state._listStartIndex, fIdx, e); }; container.onMainMouseOver = (e) => { var _a; if (state.isSelecting) { const cell = e.target.closest(".dt-cell"), row = cell == null ? void 0 : cell.closest(".dt-row"); if (row && !row.classList.contains("dt-header-row")) { const fIdx = cell.dataset.fidx ? parseInt(cell.dataset.fidx) : Array.from(row.querySelectorAll(".dt-cell")).indexOf(cell); const rIdx = ((_a = row._ref) == null ? void 0 : _a.rIdx) ?? Array.from(container.querySelectorAll(".dt-body-row")).indexOf(row); selection.updateSelect(rIdx + state._listStartIndex, fIdx); } } }; container.onMainDblClick = (e) => { var _a, _b, _c; const cell = e.target.closest(".dt-cell"), row = cell == null ? void 0 : cell.closest(".dt-row"); if (row && !row.classList.contains("dt-header-row")) { const item = (_a = row._ref) == null ? void 0 : _a.item, fIdx = cell.dataset.fidx ? parseInt(cell.dataset.fidx) : Array.from(row.querySelectorAll(".dt-cell")).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; if (item && state.fields[fIdx]) { const bounds = selection.getSelectionBounds(); if (bounds && absoluteRow >= bounds.minRow && absoluteRow <= bounds.maxRow && fIdx >= bounds.minCol && fIdx <= bounds.maxCol) { const affectedRows = bounds.maxRow - bounds.minRow + 1; if (affectedRows > 1) { state.isBulkEdit = { ...bounds, fIdx }; if ((_c = globalThis.UI) == null ? void 0 : _c.toast) globalThis.UI.toast(`Bulk Edit: Updating ${affectedRows} rows in column "${state.fields[fIdx].name}"`, { type: "warning" }); } } container.editCell(item, state.fields[fIdx], cell); } } }; container.addRow = () => { const newRow = {}; state.fields.forEach((f) => newRow[f.id] = ""); state._originalList.push(newRow); state.list = [...state._originalList]; state.isDirty = true; setTimeout(() => { scroll.reset(state.list); container.querySelector(".dt-main").scrollTop = container.querySelector(".dt-main").scrollHeight; }, 50); }; container.deleteSelectedRow = async () => { const bounds = selection.getSelectionBounds(); if (!bounds) return; const count = bounds.maxRow - bounds.minRow + 1; if (await globalThis.UI.confirm(`Are you sure you want to delete ${count} row(s)?`)) { const rMin = bounds.minRow, rMax = bounds.maxRow; const removedItems = state.list.slice(rMin, rMax + 1); state.list = state.list.filter((_, i) => !(i >= rMin && i <= rMax)); state._originalList = state._originalList.filter((item) => !removedItems.includes(item)); state.isDirty = true; selection.clearAllActive(); container.dispatchEvent(new CustomEvent("remove", { detail: { items: removedItems } })); } }; container.saveChanges = () => { container.dispatchEvent(new CustomEvent("save", { detail: { list: state._originalList, fields: state.fields } })); state.isDirty = false; }; const getFieldSchema = () => { const types = globalThis.DataTable.getFieldTypes(); const baseSchema = [ { name: "id", label: "Field ID", type: "text", setting: { required: true, placeholder: "e.g. user_name" } }, { name: "name", label: "Display Name", type: "text", setting: { required: true, placeholder: "e.g. 用户名" } }, { name: "user_type", label: "Field Type", type: "select", options: types.map((t) => ({ label: t.label, value: t.value })) } ]; const dynamicSchema = types.reduce((acc, t) => acc.concat(t.schema || []), []); return baseSchema.concat(dynamicSchema, [{ name: "isIndex", label: "Index", type: "switch" }, { name: "memo", label: "Memo", type: "text" }]); }; const parseOptionsStr = (str) => { if (!str) return void 0; return str.split("\n").map((s) => s.trim()).filter(Boolean).map((line) => { const idx = line.indexOf(":"); if (idx > -1) return { label: line.slice(0, idx).trim(), value: line.slice(idx + 1).trim() }; return line; }); }; const formatOptionsStr = (opts) => { if (!opts) return ""; return opts.map((o) => typeof o === "object" ? `${o.label}:${o.value}` : o).join("\n"); }; container.addField = async () => { container.hideColumnMenu(); const data = globalThis.NewState({ id: "c" + Date.now().toString().slice(-4), name: "New Field", user_type: "text", decimals: 0, isIndex: false, memo: "", options_str: "" }); const d = container.querySelector(`Modal[id="${container.id}_field_modal"]`); if (!d) return; Object.assign(d.state, { title: "Add Field", buttons: ["Cancel", "Save"] }); const form = d.querySelector("AutoForm"); if (form) { form.data = data; form.state.schema = getFieldSchema(); } d.show(); const result = await new Promise((resolve) => d.addEventListener("change", (e) => resolve(d.result), { once: true })); if (result === 2) { const typeInfo = globalThis.DataTable.getFieldTypes().find((t) => t.value === data.user_type); let dbType = (typeInfo == null ? void 0 : typeInfo.typeForDB) || "v1024"; if (data.user_type === "number") dbType = data.decimals > 0 ? "ff" : "bi"; const field = { id: data.id, name: data.name, memo: data.memo, isIndex: !!data.isIndex, type: dbType, settings: { formType: data.user_type, decimals: data.decimals, prefix: data.prefix, suffix: data.suffix, thousandSep: data.thousandSep, labelOn: data.labelOn, labelOff: data.labelOff, format: data.format, placeholder: data.placeholder, options: parseOptionsStr(data.options_str) } }; state.fields = [...state.fields, field]; state.isDirty = true; container.dispatchEvent(new CustomEvent("savefields", { detail: state.fields })); state.list = [...state.list]; } }; container.editField = async () => { if (!state.activeField) return; container.hideColumnMenu(); const f = state.activeField; const s = f.settings || {}; const data = globalThis.NewState({ id: f.id, name: f.name, memo: f.memo || "", isIndex: !!f.isIndex, user_type: s.formType || "text", decimals: s.decimals || 0, prefix: s.prefix || "", suffix: s.suffix || "", thousandSep: !!s.thousandSep, labelOn: s.labelOn || "", labelOff: s.labelOff || "", format: s.format || "", placeholder: s.placeholder || "", options_str: formatOptionsStr(s.options) }); const d = container.querySelector(`Modal[id="${container.id}_field_modal"]`); if (!d) return; Object.assign(d.state, { title: "Edit Field", buttons: ["Cancel", "Save"] }); const form = d.querySelector("AutoForm"); if (form) { form.data = data; form.state.schema = getFieldSchema(); } d.show(); const result = await new Promise((resolve) => d.addEventListener("change", (e) => resolve(d.result), { once: true })); if (result === 2) { const idx = state.fields.findIndex((item) => item.id === f.id); if (idx !== -1) { const typeInfo = globalThis.DataTable.getFieldTypes().find((t) => t.value === data.user_type); let dbType = (typeInfo == null ? void 0 : typeInfo.typeForDB) || "v1024"; if (data.user_type === "number") dbType = data.decimals > 0 ? "ff" : "bi"; const updatedField = { ...f, id: data.id, name: data.name, memo: data.memo, isIndex: !!data.isIndex, type: dbType, settings: { ...f.settings, formType: data.user_type, decimals: data.decimals, prefix: data.prefix, suffix: data.suffix, thousandSep: data.thousandSep, labelOn: data.labelOn, labelOff: data.labelOff, format: data.format, placeholder: data.placeholder, options: parseOptionsStr(data.options_str) } }; state.fields[idx] = updatedField; state.fields = [...state.fields]; state.isDirty = true; container.dispatchEvent(new CustomEvent("savefields", { detail: state.fields })); state.list = [...state.list]; } } }; container.deleteField = async () => { if (!state.activeField) return; container.hideColumnMenu(); if (await globalThis.UI.confirm(`Are you sure you want to delete field "${state.activeField.name}"?`)) { const idx = state.fields.findIndex((f) => f.id === state.activeField.id); if (idx !== -1) { state.fields.splice(idx, 1); state.fields = [...state.fields]; state.isDirty = true; container.dispatchEvent(new CustomEvent("savefields", { detail: state.fields })); state.list = [...state.list]; } } }; window.addEventListener("mouseup", selection.endSelect); document.addEventListener("mousedown", (e) => { const overlay = container.querySelector(".dt-editor-overlay"); const menu = container.querySelector(".dt-column-menu"); if ((overlay == null ? void 0 : overlay.style.display) !== "none" && !overlay.contains(e.target)) container.hideEditor(true); if (!container.contains(e.target) && !(overlay == null ? void 0 : overlay.contains(e.target)) && !(menu == null ? void 0 : menu.contains(e.target))) selection.clearAllActive(); }); container.addEventListener("keydown", (e) => { if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "c") { e.preventDefault(); selection.copy(); } if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "v") { e.preventDefault(); selection.paste(); } }); state._MODE_ICONS = MODE_ICONS; }, globalThis.Util.makeDom( /*html*/ `