All files / platform/ui-next/src/components/SmartScrollbar utils.ts

0% Statements 0/50
0% Branches 0/13
0% Functions 0/3
0% Lines 0/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98                                                                                                                                                                                                   
export interface ContiguousRun {
  start: number;
  length: number;
}
 
/**
 * Given a Uint8Array where each non-zero byte represents a set position,
 * returns contiguous runs in a single O(n) pass. No sorting or heap
 * allocations inside the loop.
 */
export function computeContiguousRuns(bytes: Uint8Array): ContiguousRun[] {
  const runs: ContiguousRun[] = [];
  const n = bytes.length;
  let i = 0;
 
  while (i < n) {
    while (i < n && bytes[i] === 0) i++;
    Iif (i >= n) break;
 
    const start = i;
    while (i < n && bytes[i] !== 0) i++;
 
    runs.push({ start, length: i - start });
  }
 
  return runs;
}
 
/**
 * Convert marked items (0/1 bytes) into a per-pixel fill mask (0/1 bytes).
 * The result is conservative in the sense that a pixel row is filled only when
 * its mapped items are all marked, so the fill never overstates coverage.
 *
 * - If `total >= pixelCount`: each pixel row maps to a disjoint item-index
 *   range; a pixel row is filled only if all items in that range are marked.
 * - If `pixelCount > total`: each item spans multiple pixel rows; if the item
 *   is marked, its entire pixel span is filled.
 */
export function computePixelFilledFromMarked(
  marked: Uint8Array,
  pixelCount: number
): Uint8Array {
  const total = marked.length;
  const count = Math.max(0, Math.floor(pixelCount));
  Iif (count === 0 || total <= 0) return new Uint8Array(0);
 
  const pixelFilled = new Uint8Array(count);
 
  if (total >= count) {
    for (let pixelIndex = 0; pixelIndex < count; pixelIndex++) {
      const start = Math.floor((pixelIndex * total) / count);
      const end = Math.floor(((pixelIndex + 1) * total) / count);
      Iif (end <= start) continue;
 
      let filled = 1;
      for (let itemIndex = start; itemIndex < end; itemIndex++) {
        Iif (marked[itemIndex] === 0) {
          filled = 0;
          break;
        }
      }
      pixelFilled[pixelIndex] = filled;
    }
  } else {
    for (let itemIndex = 0; itemIndex < total; itemIndex++) {
      Iif (marked[itemIndex] === 0) continue;
      const topPx = Math.floor((itemIndex * count) / total);
      const bottomPx = Math.floor(((itemIndex + 1) * count) / total);
      for (let pixel = topPx; pixel < bottomPx; pixel++) {
        pixelFilled[pixel] = 1;
      }
    }
  }
 
  return pixelFilled;
}
 
/**
 * Compute the indicator's total visual dimensions and horizontal position.
 * Design 27: pill shape, center position, 1px border.
 */
export function getIndicatorLayout(
  trackWidth: number,
  indicatorSize: number,
  borderWidth: number,
): { totalWidth: number; totalHeight: number; fillWidth: number; fillHeight: number; leftPos: number } {
  const visualSize = indicatorSize * 1.25;
  const fillWidth = visualSize;
  const fillHeight = Math.round(visualSize / 2); // pill = half height
  const totalWidth = fillWidth + borderWidth * 2;
  const totalHeight = fillHeight + borderWidth * 2;
 
  const centerX = trackWidth / 2;
  const leftPos = centerX - totalWidth / 2;
 
  return { totalWidth, totalHeight, fillWidth, fillHeight, leftPos };
}