diff --git a/frontend/app.js b/frontend/app.js index 4f4b54b..cdc6517 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -4,6 +4,8 @@ const SCHEMA_URL = "/schemeta.schema.json"; const NET_CLASSES = ["power", "ground", "signal", "analog", "differential", "clock", "bus"]; const PIN_SIDES = ["left", "right", "top", "bottom"]; const PIN_TYPES = ["power_in", "power_out", "input", "output", "bidirectional", "passive", "analog", "ground"]; +const LIST_ROW_HEIGHT = 36; +const LIST_OVERSCAN_ROWS = 8; const state = { model: null, @@ -721,6 +723,26 @@ function resolveLabelCollisions(svg) { } } +function renderVirtualList(listEl, allItems, renderRow, rowHeight = LIST_ROW_HEIGHT) { + const items = Array.isArray(allItems) ? allItems : []; + if (!items.length) { + listEl.innerHTML = ""; + return; + } + + const viewportHeight = Math.max(rowHeight * 4, listEl.clientHeight || 230); + const scrollTop = listEl.scrollTop; + const total = items.length; + const visibleRows = Math.ceil(viewportHeight / rowHeight); + const start = Math.max(0, Math.floor(scrollTop / rowHeight) - LIST_OVERSCAN_ROWS); + const end = Math.min(total, start + visibleRows + LIST_OVERSCAN_ROWS * 2); + const topPad = start * rowHeight; + const bottomPad = (total - end) * rowHeight; + + const rows = items.slice(start, end).map(renderRow).join(""); + listEl.innerHTML = `
${rows}`; +} + function renderInstances() { if (!state.model) { el.instanceList.innerHTML = ""; @@ -730,12 +752,15 @@ function renderInstances() { const q = el.instanceFilter.value.trim().toLowerCase(); const items = state.model.instances.filter((i) => i.ref.toLowerCase().includes(q)); - el.instanceList.innerHTML = items - .map((inst) => { + renderVirtualList( + el.instanceList, + items, + (inst) => { const cls = state.selectedRefs.includes(inst.ref) ? "active" : ""; return `