dataTable/src/index.js

118 lines
4.6 KiB
JavaScript
Raw Normal View History

2026-05-17 17:03:21 +08:00
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({})
2026-05-17 17:03:21 +08:00
const state = container.state
Object.assign(state, {
list: [], fields: [], _renderedList: [],
2026-05-17 17:03:21 +08:00
prevHeight: 0, postHeight: 0, _listStartIndex: 0,
perf: { refreshTime: 0, refreshCount: 0, scrollCount: 0, totalNodes: 0 }
2026-05-17 17:03:21 +08:00
})
/* PERFORMANCE_TELEMETRY_START - THIS BLOCK IS FOR TESTING AND SHOULD BE REMOVED IN PRODUCTION */
if (!window.__statePerformanceTelemetry) {
window.__statePerformanceTelemetry = { scanCount: 0, reuseCount: 0, moveCount: 0 };
}
/* PERFORMANCE_TELEMETRY_END */
container.refresh = () => {
const start = performance.now()
// Optimization: Expand the virtual viewport to 1.6x height to create a buffer
// that prevents "white flashes" during extremely fast scrolling.
const virtualContainer = {
clientHeight: container.clientHeight * 1.6,
scrollTop: container.scrollTop
};
const res = vs.calc(virtualContainer, state.list)
if (res) {
/* PERFORMANCE_TELEMETRY_START */
const frameStartScan = window.__statePerformanceTelemetry.scanCount;
const frameStartMove = window.__statePerformanceTelemetry.moveCount;
const frameStartReuse = window.__statePerformanceTelemetry.reuseCount;
/* PERFORMANCE_TELEMETRY_END */
state.perf.refreshCount++
Object.assign(state, {
prevHeight: res.prevHeight, postHeight: res.postHeight,
_listStartIndex: res.listStartIndex, _renderedList: res.renderedList
})
const elapsed = performance.now() - start;
state.perf.refreshTime += elapsed;
state.perf.totalNodes += res.renderedList.length;
/* PERFORMANCE_TELEMETRY_START */
const frameScans = window.__statePerformanceTelemetry.scanCount - frameStartScan;
const frameMoves = window.__statePerformanceTelemetry.moveCount - frameStartMove;
const frameReuses = window.__statePerformanceTelemetry.reuseCount - frameStartReuse;
if (frameScans > 0 || elapsed > 2) {
console.log(`[DataTable Frame] Time: ${elapsed.toFixed(2)}ms, Scans: ${frameScans}, Moves: ${frameMoves}, Reuses: ${frameReuses}, Rows: ${res.renderedList.length}`);
}
/* PERFORMANCE_TELEMETRY_END */
}
2026-05-17 17:03:21 +08:00
}
container.onItemUpdate = (index, node) => {
// vs.update(index + (state._listStartIndex || 0), node)
}
2026-05-17 17:03:21 +08:00
state.__watch('list', list => {
state._listStartIndex = 0
state._renderedList = vs.reset(list, container) || []
setTimeout(() => { if (state.list === list) vs.init(list, container.refresh) })
2026-05-17 17:03:21 +08:00
})
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)
2026-05-17 17:03:21 +08:00
})
container.onScroll = () => {
state.perf.scrollCount++
container.refresh()
2026-05-17 17:03:21 +08:00
}
}, Util.makeDom(/*html*/`
<div class="dt-root h-100 overflow-auto" onscroll="this.onScroll()" style="overflow-anchor:none; display: block">
<div class="dt-header border-bottom bg-light sticky-top" style="z-index:10">
<div class="dt-flex-row fw-bold text-muted small" style="height:40px">
<div $each="this.state.fields" class="dt-cell border-end px-2 d-flex align-items-center overflow-hidden" $style="'width: var(--w-' + item.id + ', ' + (item.width || 150) + 'px); min-width: var(--w-' + item.id + ', ' + (item.width || 150) + 'px)'">
<span $text="item.name" class="text-truncate"></span>
</div>
2026-05-17 17:03:21 +08:00
</div>
</div>
<div $if="(this.state?.prevHeight || 0) > 0" $style="'height:' + this.state.prevHeight + 'px;'" class="flex-shrink-0"></div>
<div $each="this.state?._renderedList" key="id" class="dt-row dt-flex-row border-bottom bg-white" style="height:40px" $onupdate="this.onItemUpdate(index, thisNode)">
<div $each="this.state.fields" as="f" class="dt-cell border-end px-2 d-flex align-items-center overflow-hidden" $style="'width: var(--w-' + f.id + ', ' + (f.width || 150) + 'px); min-width: var(--w-' + f.id + ', ' + (f.width || 150) + 'px)'">
<span $text="item[f.id] ?? ''" class="text-truncate"></span>
2026-05-17 17:03:21 +08:00
</div>
</div>
<div $if="(this.state?.postHeight || 0) > 0" $style="'height:' + this.state.postHeight + 'px;'" class="flex-shrink-0"></div>
2026-05-17 17:03:21 +08:00
</div>
`), Util.makeDom(/*html*/`
<style>
DataTable { display: block; }
.dt-flex-row {
display: flex;
flex-direction: row;
width: max-content;
min-width: 100%;
}
.dt-cell {
background: inherit;
white-space: nowrap;
flex-shrink: 0;
}
.dt-row:hover {
background-color: #f8f9fa !important;
}
</style>
2026-05-17 17:03:21 +08:00
`))
if (typeof document !== 'undefined') RefreshState(document.documentElement)