import { Component, NewState, Util, RefreshState } from '@web/state' import { createPerfMonitor } from './perf.js' import { createScrollManager } from './scroll.js' import { createSelectionManager } from './selection.js' 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, // --- Editing State --- editing: null // { row, field, node, style } }) const perf = createPerfMonitor(); state.perf = perf.stats; const selection = createSelectionManager(container, state); const scroll = createScrollManager(container, state, (renderedCount) => { if (state.editing) { // Close editor on scroll to prevent floating away container.cancelEdit(); } selection.applySelectionUI(); }); container.refresh = () => { const frameStart = perf.startFrame(); scroll.refresh(); perf.endFrame(frameStart, state._renderedList.length); }; container.onScroll = () => { perf.onScroll(); container.refresh(); }; 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) }) // Expose selection methods to template container.startSelect = selection.startSelect; container.updateSelect = selection.updateSelect; container.deleteSelected = selection.deleteSelected; // --- Editing Logic --- container.editCell = (row, field, cellNode) => { const body = container.querySelector('.dt-body'); const rect = cellNode.getBoundingClientRect(); const bodyRect = body.getBoundingClientRect(); // Ensure row is a State object for reliable binding const listIdx = state.list.indexOf(row); let targetRow = row; if (listIdx !== -1 && !row.__watch) { targetRow = NewState(row); state.list[listIdx] = targetRow; } state.editing = { row: targetRow, field, node: cellNode, style: `left:${rect.left - bodyRect.left}px; top:${rect.top - bodyRect.top}px; width:${rect.width}px; height:${rect.height}px;` }; // Optimization: $. attributes are not reactive. We must manually update the editor // if it's already in the DOM, or the next frame after $if renders it. const syncEditor = () => { const editor = container.querySelector('.dt-editor-container AutoForm'); if (editor) { editor.state.schema = [{ ...field, name: field.id, label: '' }]; editor.data = targetRow; RefreshState(editor); const el = editor.querySelector('.form-control, .form-select, .form-check-input'); if (el) el.focus(); } else { requestAnimationFrame(syncEditor); } }; syncEditor(); }; container.finishEdit = () => { const node = state.editing?.node; state.editing = null; if (node) RefreshState(node); container.focus(); // Return focus to table }; container.cancelEdit = () => { state.editing = null; }; // Copy & Paste (simplified) const escapeTSV = val => { const str = String(val ?? '') return (str.includes('\t') || str.includes('\n') || str.includes('"')) ? '"' + str.replace(/"/g, '""') + '"' : str } container.copy = async () => { const bounds = selection.getSelectionBounds(); if (!bounds) return; const text = state.list.slice(bounds.minRow, bounds.maxRow + 1).map(row => state.fields.slice(bounds.minCol, bounds.maxCol + 1).map(f => escapeTSV(row[f.id])).join('\t') ).join('\n') await navigator.clipboard.writeText(text) } container.addEventListener('keydown', e => { if (e.ctrlKey || e.metaKey) { if (e.key === 'c') { e.preventDefault(); container.copy() } } }) const onGlobalMouseUp = () => selection.endSelect() const onGlobalMouseDown = e => { if (!container.contains(e.target)) selection.clearAllActive() } window.addEventListener('mouseup', onGlobalMouseUp) document.addEventListener('mousedown', onGlobalMouseDown) container._onUnload = () => { document.removeEventListener('mousedown', onGlobalMouseDown) window.removeEventListener('mouseup', onGlobalMouseUp) } }, Util.makeDom(/*html*/`
`), Util.makeDom(/*html*/` `)) if (typeof document !== 'undefined') RefreshState(document.documentElement)