How to prepend line number cells as a HTML column to a given table matrix?

What I have so far:

I’m working on a very generic table based on a configuration. This table is generated based on a graph, the final calculated data structure is

type TableMatrix = Cell[][];

interface Cell {
  isCoveredByPreviousCell: boolean;
  rowspan: number;
  offset: number;
  hasContent: boolean;
  columnIndex: number;
}

I grabbed data from a correct table to test with ( Playground with test data )

Sidenote: Based on this data I know there will be 14 columns to render

When it comes to rendering I decided to use VueJs ( Playground Link )

<script setup>
import { tableMatrix } from './data.ts'
</script>

<template>
  <table>
    <thead>
      <tr>
        <th v-for="headerIndex in 14">
          Header {{ headerIndex }}
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(row, rowIndex) in tableMatrix" :key="rowIndex">
        <!-- Inser line number cell here -->
        <template v-for="(cell, columnIndex) in row" :key="columnIndex">
          <td v-if="cell.isCoveredByPreviousCell" :style="{ 'display': 'none' }"/>
          <td v-else :rowspan="cell.rowspan">
            <template v-if="cell.hasContent">
              <div>I belong to col {{ cell.columnIndex + 1 }}</div>
            </template>
          </td>
        </template>
      </tr>
    </tbody>
  </table>
</template>

<style>
table, th, td {
  border: 1px solid black;
}
</style>

What I want to achieve:

I want to add line numbers to every leading row.

A row is a leading row if every cell in that row is not covered by a previous cell. Pseudo example: (row /* Cell[] */ ).every((cell) => !cell.isCoveredByPreviousCell);

So for this example ( the first leading row in the first data column might have multiple cells but not in this example ) the desired output would be

enter image description here

What I’ve tried so far:

I thought I could create a structure that knows the correct row index for a line number cell and the required rowspan.

const lineNumbersMatrix = computed(() => {
  interface LineNumberInfo {
    index: number;
    rowspan: number;
  }

  const matrix = new Map<number, LineNumberInfo>();

  let currentLineIndex = 0;

  for (let rowIndex = 0; rowIndex < tableMatrix.value.length; rowIndex++) {
    const row = tableMatrix.value[rowIndex];
    
    // skip if not a leading row
    if (row.some((cell) => cell.isCoveredByPreviousCell)) {
      continue;
    }

    let lineNumberRowspan = 1;

    for (const cell of row) {
      if (lineNumberRowspan >= cell.rowspan) {
        continue;
      }

      lineNumberRowspan = cell.rowspan;
    }

    matrix.set(rowIndex, {
      index: currentLineIndex,
      rowspan: lineNumberRowspan
    });

    currentLineIndex ++;
  }

  return matrix;
});

After that I thought I could add a new table header

<th>Line</th>

and replace the comment

<!-- Inser line number cell here -->

with

<td v-if="lineNumbersMatrix.has(rowIndex)" :rowspan="lineNumbersMatrix.get(rowIndex)!.rowspan">{{ lineNumbersMatrix.get(rowIndex)!.index }}</td>

to get this Result, which seems to work as expected.

Unfortunately my real project renders an additional blank column at the end of the table

enter image description here

and I can’t reproduce it in the sandbox…

I’m assuming my approach is not 100% correct and there might be better ways. Do you have any ideas how to solve this?