AkurAI Build
Menu

Public repository / master

AkurAI-Mail / src/lib/webmail/virtual.js

Latest change 10d19fe0941c186ec244d1f0e2af6b19200dcb0c - OLI-248: pure webmail logic as plain ES modules with JSDoc types; inline icon module by Ólafur Búi Ólafsson

/** Variable-height list windowing for the message list. */

/** Prefix sums: offsets[i] is the top of item i; offsets[n] is the total height. */
/**
 * Cumulative row offsets: `offsets[i]` is where item i starts, `offsets[n]` the total height.
 * @template T
 * @param {T[]} items
 * @param {(item: T) => number} height
 * @returns {number[]}
 */
export function threadOffsets(items, height) {
  const offsets = new Array        (items.length + 1);
  offsets[0] = 0;
  for (let i = 0; i < items.length; i++) offsets[i + 1] = offsets[i]  + height(items[i]);
  return offsets;
}

/**
 * Index of the item containing pixel `y`.
 * @param {number[]} offsets
 * @param {number} y
 */
export function indexAt(offsets, y) {
  const count = offsets.length - 1;
  let low = 0;
  let high = count - 1;
  while (low < high) {
    const mid = (low + high) >> 1;
    if (offsets[mid + 1]  > y) high = mid;
    else low = mid + 1;
  }
  return count ? low : 0;
}

/**
 * Items to render for a viewport, padded by `overscan` items on each side.
 * @param {number[]} offsets
 * @param {number} scrollTop
 * @param {number} viewportHeight
 * @param {number} overscan
 */
export function visibleRange(offsets, scrollTop, viewportHeight, overscan) {
  const count = offsets.length - 1;
  const start = Math.max(0, indexAt(offsets, scrollTop) - overscan);
  const bottom = scrollTop + viewportHeight;
  let end = start;
  while (end < count && offsets[end]  < bottom) end++;
  return { start, end: Math.min(count, end + overscan) };
}