import { Component, NewState, Util, RefreshState } from '@web/state' import { VirtualScroll } from '@web/base' Component.register('DataTable', container => { const vs = VirtualScroll() if (!container.state) container.state = NewState({}) const state = container.state Object.assign(state, { list: [], fields: [], renderedList: [], prevHeight: 0, postHeight: 0, _listStartIndex: 0, selStartR: -1, selStartF: -1, selEndR: -1, selEndF: -1, multiSelections: [], isSelecting: false, idField: 'id', activeCell: { rIdx: -1, fIdx: -1 }, editingCell: { row: null, rowId: null, fieldId: null, fIdx: -1 }, features: { idSystem: true } }) // --- 渲染刷新 --- const refresh = () => { const start = performance.now() const scrollEl = container.querySelector('.dt-body') if (!scrollEl) return const res = vs.calc(scrollEl, state.list) if (res) { Object.assign(state, { prevHeight: res.prevHeight, postHeight: res.postHeight, _listStartIndex: res.listStartIndex, renderedList: res.renderedList }) const end = performance.now() if (end - start > 10) console.warn(`[DataTable] Keyed Refresh: ${(end - start).toFixed(2)}ms`); } } container.refresh = refresh state.__watch('list', list => { if (!list) return if (!state.idField) { if (list.length && list[0].id !== undefined) state.idField = 'id'; else state.idField = '_rowId'; } const idKey = state.idField; list.forEach((item, i) => { if (item[idKey] === undefined) item[idKey] = 'ui_' + Math.random().toString(36).substr(2, 9) }) state._listStartIndex = 0 const scrollEl = container.querySelector('.dt-body') state.renderedList = vs.reset(list, scrollEl || container) || [] if (scrollEl) { vs.init(list, refresh); requestAnimationFrame(refresh); } }) container.onItemUpdate = (rIdx, node) => vs.update(rIdx + state._listStartIndex, node) container.isCellSelected = (r, f) => { const rMin = Math.min(state.selStartR, state.selEndR), rMax = Math.max(state.selStartR, state.selEndR) const fMin = Math.min(state.selStartF, state.selEndF), fMax = Math.max(state.selStartF, state.selEndF) if (r >= rMin && r <= rMax && f >= fMin && f <= fMax) return true return state.multiSelections.some(s => r >= s.r1 && r <= s.r2 && f >= s.f1 && f <= s.f2) } container.clearAllActive = (keepSelection = false) => { state.editingCell = { row: null, rowId: null, fieldId: null, fIdx: -1 } state.activeCell = { rIdx: -1, fIdx: -1 } if (!keepSelection) { state.selStartR = -1; state.multiSelections = [] } } container.startSelect = (r, f, e) => { const alreadySelected = container.isCellSelected(r, f) if (e.shiftKey && state.selStartR !== -1) { state.selEndR = r; state.selEndF = f } else { if (!alreadySelected) { if (!e.ctrlKey && !e.metaKey) container.clearAllActive() else if (state.selStartR !== -1) { state.multiSelections.push({ r1: Math.min(state.selStartR, state.selEndR), r2: Math.max(state.selStartR, state.selEndR), f1: Math.min(state.selStartF, state.selEndF), f2: Math.max(state.selStartF, state.selEndF) }) } state.selStartR = state.selEndR = r state.selStartF = state.selEndF = f } state.isSelecting = true state.activeCell = { rIdx: r, fIdx: f } } } container.updateSelect = (r, f) => state.isSelecting && (state.selEndR = r, state.selEndF = f) container.endSelect = () => { state.isSelecting = false } container.editCell = (row, f, fIdx) => { container.clearAllActive(true) const wrappedRow = row.__watch ? row : NewState(row) const listIdx = state.list.indexOf(row) if (listIdx !== -1) state.list[listIdx] = wrappedRow state.editingCell = { row: wrappedRow, rowId: wrappedRow[state.idField], fieldId: f.id, fIdx } } const onGlobalMouseUp = () => state.isSelecting && container.endSelect() window.addEventListener('mouseup', onGlobalMouseUp) const onGlobalMouseDown = e => !container.contains(e.target) && container.clearAllActive() document.addEventListener('mousedown', onGlobalMouseDown) container._onUnload = () => { document.removeEventListener('mousedown', onGlobalMouseDown) window.removeEventListener('mouseup', onGlobalMouseUp) } state.__watch('fields', fields => { if (!fields) return const leftOffsets = [], rightOffsets = [] let lSum = 0 fields.forEach((f, i) => { if (f.pinned === 'left') { leftOffsets[i] = lSum; lSum += (f.width || 150); } }) fields.forEach((f, i) => { if (f.pinned === 'right') { let rs = 0 for (let j = i + 1; j < fields.length; j++) { if (fields[j].pinned === 'right') rs += (fields[j].width || 150); } rightOffsets[i] = rs } }) fields.forEach((f, i) => { const width = f.width || 150; const left = leftOffsets[i] || 0; const right = rightOffsets[i] || 0; f._preStyle = `width: var(--w-${f.id}, ${width}px); min-width: var(--w-${f.id}, ${width}px); height:40px;` + (f.pinned ? 'position:sticky; z-index:10;' : '') + (f.pinned === 'left' ? `left:${left}px;` : '') + (f.pinned === 'right' ? `right:${right}px;` : ''); }); }) }, Util.makeDom(/*html*/`