← Spreadsheet results

Spreadsheet

Qwen 3.6 27B MTP GGUF UD Q5 K XL · typescript

Initial view of Spreadsheet

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 65,899 bytes · SHA-256 8cf310c30f09
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spreadsheet</title>
<style>
/* ── css: base ── */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-size: 13px;
  color: #1d1d1f;
  background: #f9f9f9;
  overflow: hidden;
  height: 100vh;
  display: flex;
  flex-direction: column;
}
/* ── css: toolbar ── */
.toolbar {
  display: flex;
  align-items: center;
  padding: 4px 8px;
  background: #fff;
  border-bottom: 1px solid #dadce0;
  gap: 2px;
  flex-shrink: 0;
}

.toolbar button {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 4px;
  padding: 4px 10px;
  font-size: 12px;
  color: #1d1d1f;
  cursor: pointer;
  white-space: nowrap;
}

.toolbar button:hover {
  background: #e8eaed;
  border-color: #dadce0;
}

.toolbar-sep {
  width: 1px;
  height: 20px;
  background: #dadce0;
  margin: 0 6px;
}
/* ── css: formula-bar ── */
#formula-bar {
  display: flex;
  align-items: center;
  background: #fff;
  border-bottom: 1px solid #dadce0;
  padding: 0 4px;
  height: 32px;
  flex-shrink: 0;
}

.cell-ref {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  min-width: 56px;
  height: 28px;
  border-right: 1px solid #dadce0;
  font-size: 12px;
  font-weight: 500;
  color: #5f6368;
  user-select: none;
}

#formula-input {
  flex: 1;
  border: none;
  outline: none;
  padding: 0 8px;
  font-size: 13px;
  font-family: inherit;
  color: #1d1d1f;
  height: 28px;
  margin: 2px 0;
}

#formula-input:focus {
  box-shadow: inset 0 0 0 2px #1a73e8;
}
/* ── css: grid ── */
#grid-container {
  flex: 1;
  overflow: hidden;
  position: relative;
  background: #fff;
}

#grid-scroll {
  width: 100%;
  height: 100%;
  overflow: auto;
  position: relative;
}

#grid {
  border-collapse: collapse;
  table-layout: fixed;
  /* Width/height set by JS */
}

#grid th, #grid td {
  border: 1px solid #dadce0;
  padding: 0;
  margin: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Column headers (top row) */
#grid thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: #f8f9fa;
  color: #5f6368;
  font-weight: 500;
  font-size: 12px;
  text-align: center;
  user-select: none;
  border-bottom: 1px solid #c0c0c0;
}

/* Row headers (first column) */
#grid tbody th.row-header {
  position: sticky;
  left: 0;
  z-index: 1;
  background: #f8f9fa;
  color: #5f6368;
  font-weight: 400;
  font-size: 12px;
  text-align: center;
  user-select: none;
  border-right: 1px solid #c0c0c0;
}

/* Top-left corner cell */
#grid thead th.corner {
  position: sticky;
  top: 0;
  left: 0;
  z-index: 3;
  background: #f8f9fa;
  border-bottom: 1px solid #c0c0c0;
  border-right: 1px solid #c0c0c0;
}

/* Data cells */
#grid td.cell {
  cursor: cell;
  font-size: 13px;
  padding: 2px 4px;
  min-height: 24px;
  box-sizing: border-box;
  height: 24px;
  vertical-align: middle;
}

#grid td.cell.selected {
  outline: 2px solid #1a73e8;
  outline-offset: -1px;
  z-index: 1;
  position: relative;
}

#grid td.cell.text-val { text-align: left; }
#grid td.cell.number-val { text-align: right; }
#grid td.cell.error-val { text-align: right; color: #d93025; font-size: 11px; }

/* Context menu */
.context-menu {
  position: fixed;
  background: #fff;
  border: 1px solid #dadce0;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
  z-index: 100;
  min-width: 160px;
  padding: 4px 0;
}

.context-menu.hidden { display: none; }

.context-menu-item {
  padding: 6px 16px;
  cursor: pointer;
  font-size: 13px;
  color: #1d1d1f;
}

.context-menu-item:hover {
  background: #e8eaed;
}

.context-menu-sep {
  height: 1px;
  background: #dadce0;
  margin: 4px 0;
}
/* ── css: selection ── */
#grid td.cell.selected {
  outline: 2px solid #1a73e8;
  outline-offset: -1px;
  z-index: 1;
  position: relative;
}

#grid td.cell.editing {
  outline: 2px solid #1a73e8;
  outline-offset: -1px;
  z-index: 1;
  position: relative;
}

/* Cell editing input (for in-cell editing) */
.cell-input {
  border: 2px solid #1a73e8;
  outline: none;
  width: 100%;
  height: 100%;
  font-size: 13px;
  font-family: inherit;
  padding: 1px 3px;
  margin: -2px -1px;
  box-sizing: border-box;
  background: #fff;
}
/* ── css: scrollbar ── */
#grid-scroll::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

#grid-scroll::-webkit-scrollbar-track {
  background: #f1f1f1;
}

#grid-scroll::-webkit-scrollbar-thumb {
  background: #c1c1c1;
  border-radius: 5px;
}

#grid-scroll::-webkit-scrollbar-thumb:hover {
  background: #a1a1a1;
}

#grid-scroll::-webkit-scrollbar-corner {
  background: #f1f1f1;
}
</style>
</head>
<body>
<div id="toolbar" class="toolbar">
  <button id="btn-insert-row" title="Insert Row Above">Insert Row</button>
  <button id="btn-insert-col" title="Insert Column Left">Insert Column</button>
  <button id="btn-delete-row" title="Delete Selected Row">Delete Row</button>
  <button id="btn-delete-col" title="Delete Selected Column">Delete Column</button>
  <button id="btn-clear" title="Clear Selected Cell (Delete key)">Clear</button>
  <span class="toolbar-sep"></span>
  <button id="btn-save" title="Save to localStorage">Save</button>
</div>
<div id="formula-bar">
  <span id="cell-ref" class="cell-ref"></span>
  <input id="formula-input" type="text" spellcheck="false" autocomplete="off">
</div>
<div id="grid-container">
  <div id="grid-scroll">
    <table id="grid"></table>
  </div>
</div>
<div id="context-menu" class="context-menu hidden"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./render":"src/render.ts","./editing":"src/editing.ts","./state":"src/state.ts","./types":"src/types.ts","./engine":"src/engine.ts","./utils":"src/utils.ts"},"src/state.ts":{"./types":"src/types.ts","./utils":"src/utils.ts"},"src/utils.ts":{"./types":"src/types.ts"},"src/tokenizer.ts":{"./types":"src/types.ts"},"src/parser.ts":{"./types":"src/types.ts","./tokenizer":"src/tokenizer.ts","./utils":"src/utils.ts"},"src/evaluator.ts":{"./types":"src/types.ts","./parser":"src/parser.ts","./utils":"src/utils.ts","./state":"src/state.ts"},"src/engine.ts":{"./state":"src/state.ts","./evaluator":"src/evaluator.ts","./types":"src/types.ts","./utils":"src/utils.ts"},"src/render.ts":{"./state":"src/state.ts","./types":"src/types.ts","./utils":"src/utils.ts"},"src/editing.ts":{"./state":"src/state.ts","./engine":"src/engine.ts","./render":"src/render.ts","./utils":"src/utils.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.showContextMenu = showContextMenu;
const render_1 = require("./render");
const editing_1 = require("./editing");
const state_1 = require("./state");
const engine_1 = require("./engine");
const utils_1 = require("./utils");
function refreshCellDom(addr) {
    if (!addr)
        return;
    const ref = (0, utils_1.parseCellRef)(addr);
    if (ref && (0, state_1.inBounds)(ref.row, ref.col)) {
        (0, render_1.updateCellAt)(ref.row, ref.col);
    }
}
const types_1 = require("./types");
const STORAGE_KEY = 'spreadsheet_data_v2';
// ─── Init ──────────────────────────────────────────────────────────────────────
function init() {
    loadFromStorage();
    (0, render_1.initGrid)();
    wireEvents();
    (0, engine_1.recalcAll)();
    (0, render_1.rerenderAll)();
}
// ─── Events ────────────────────────────────────────────────────────────────────
function wireEvents() {
    // Grid click → select cell
    const grid = document.getElementById('grid');
    grid.addEventListener('click', onGridClick);
    grid.addEventListener('dblclick', onGridDblClick);
    // Keyboard
    document.addEventListener('keydown', onKeyDown);
    // Formula bar
    const formulaInput = document.getElementById('formula-input');
    formulaInput.addEventListener('keydown', onFormulaBarKey);
    formulaInput.addEventListener('input', onFormulaBarInput);
    // Toolbar buttons
    document.getElementById('btn-insert-row')?.addEventListener('click', () => insertRowAt(selRow()));
    document.getElementById('btn-insert-col')?.addEventListener('click', () => insertColAt(selCol()));
    document.getElementById('btn-delete-row')?.addEventListener('click', () => deleteRowAt(selRow()));
    document.getElementById('btn-delete-col')?.addEventListener('click', () => deleteColAt(selCol()));
    document.getElementById('btn-clear')?.addEventListener('click', () => { (0, editing_1.clearCell)(); saveToStorage(); });
    document.getElementById('btn-save')?.addEventListener('click', () => { saveToStorage(); flashSave(); });
    // Close context menu on outside click
    document.addEventListener('click', () => hideContextMenu());
    // Save before unload
    window.addEventListener('beforeunload', saveToStorage);
}
function selCol() {
    const s = (0, state_1.getSelection)();
    return s.col;
}
function selRow() {
    const s = (0, state_1.getSelection)();
    return s.row;
}
function onGridClick(e) {
    const td = e.target.closest('td.cell');
    if (!td)
        return;
    const col = parseInt(td.dataset.col || '0', 10);
    const row = parseInt(td.dataset.row || '0', 10);
    (0, editing_1.selectCell)(col, row);
}
function onGridDblClick(e) {
    const td = e.target.closest('td.cell');
    if (!td)
        return;
    const col = parseInt(td.dataset.col || '0', 10);
    const row = parseInt(td.dataset.row || '0', 10);
    (0, editing_1.selectCell)(col, row);
    (0, editing_1.startEdit)();
}
function onKeyDown(e) {
    const formulaInput = document.getElementById('formula-input');
    // If formula bar is focused or is the event target, handle formula bar editing
    if (e.target === formulaInput) {
        if (e.key === 'Escape') {
            (0, editing_1.cancelEdit)();
            e.preventDefault();
            e.stopPropagation();
            return;
        }
        if (e.key === 'Enter') {
            const updated = (0, editing_1.commitFromFormulaBar)();
            for (const addr of updated)
                refreshCellDom(addr);
            e.preventDefault();
            e.stopPropagation();
            (0, editing_1.moveSelection)(0, 1);
            saveToStorage();
            return;
        }
        if (e.key === 'Tab') {
            const updated = (0, editing_1.commitFromFormulaBar)();
            for (const addr of updated)
                refreshCellDom(addr);
            e.preventDefault();
            e.stopPropagation();
            (0, editing_1.moveSelection)(e.shiftKey ? -1 : 1, 0);
            saveToStorage();
            return;
        }
        // Allow normal typing in formula bar
        return;
    }
    // Grid navigation and editing (formula bar not focused)
    if ((0, editing_1.isEditing)()) {
        if (e.key === 'Escape') {
            (0, editing_1.cancelEdit)();
            e.preventDefault();
            return;
        }
        return;
    }
    switch (e.key) {
        case 'ArrowUp':
            e.preventDefault();
            (0, editing_1.moveSelection)(0, -1);
            break;
        case 'ArrowDown':
            e.preventDefault();
            (0, editing_1.moveSelection)(0, 1);
            break;
        case 'ArrowLeft':
            e.preventDefault();
            (0, editing_1.moveSelection)(-1, 0);
            break;
        case 'ArrowRight':
            e.preventDefault();
            (0, editing_1.moveSelection)(1, 0);
            break;
        case 'Tab':
            e.preventDefault();
            (0, editing_1.moveSelection)(e.shiftKey ? -1 : 1, 0);
            break;
        case 'Enter':
            e.preventDefault();
            (0, editing_1.startEdit)();
            formulaInput.focus();
            break;
        case 'Delete':
        case 'Backspace':
            e.preventDefault();
            (0, editing_1.clearCell)();
            saveToStorage();
            break;
        case 'F2':
            e.preventDefault();
            (0, editing_1.startEdit)();
            formulaInput.focus();
            break;
        default:
            // If printable character, start editing with that character
            if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
                e.preventDefault();
                (0, editing_1.startEdit)();
                formulaInput.focus();
                formulaInput.value = e.key;
            }
            break;
    }
}
function onFormulaBarKey(e) {
    // Handled by onKeyDown since e.target === formulaInput matches there
}
function onFormulaBarInput() {
    // No live preview needed — value commits on Enter
}
function showContextMenu(x, y, type, index) {
    const menu = document.getElementById('context-menu');
    menu.innerHTML = '';
    menu.classList.remove('hidden');
    if (type === 'row') {
        addItem(menu, `Insert Row Above (Row ${index + 1})`, () => {
            insertRowAt(index);
            hideContextMenu();
        });
        addItem(menu, `Delete Row ${index + 1}`, () => {
            deleteRowAt(index);
            hideContextMenu();
        });
    }
    else {
        addItem(menu, `Insert Column Left of ${(0, utils_1.colLabel)(index)}`, () => {
            insertColAt(index);
            hideContextMenu();
        });
        addItem(menu, `Delete Column ${(0, utils_1.colLabel)(index)}`, () => {
            deleteColAt(index);
            hideContextMenu();
        });
    }
    // Position
    const menuRect = menu.getBoundingClientRect();
    let menuX = x;
    let menuY = y;
    if (x + menuRect.width > window.innerWidth)
        menuX = window.innerWidth - menuRect.width - 4;
    if (y + menuRect.height > window.innerHeight)
        menuY = window.innerHeight - menuRect.height - 4;
    menu.style.left = menuX + 'px';
    menu.style.top = menuY + 'px';
}
function hideContextMenu() {
    const menu = document.getElementById('context-menu');
    menu.classList.add('hidden');
}
function addItem(menu, text, onClick) {
    const item = document.createElement('div');
    item.className = 'context-menu-item';
    item.textContent = text;
    item.addEventListener('click', onClick);
    menu.appendChild(item);
}
window.__spreadshowContextMenu = showContextMenu;
// ─── Row/Column Operations ─────────────────────────────────────────────────────
function insertRowAt(index) {
    if (state_1.cellGrid.length >= types_1.ROW_COUNT)
        return; // Max rows
    // Create a new empty row
    const newRow = [];
    for (let c = 0; c < state_1.cellGrid[0].length; c++) {
        newRow.push((0, state_1.emptyCell)());
    }
    // Insert at index
    state_1.cellGrid.splice(index, 0, newRow);
    // Shift all references in formulas that point to rows >= index
    rewriteReferencesForRowInsert(index);
    (0, engine_1.recalcAll)();
    (0, render_1.rerenderAll)();
    saveToStorage();
}
function deleteRowAt(index) {
    if (state_1.cellGrid.length <= 1)
        return;
    // Remove row
    state_1.cellGrid.splice(index, 1);
    // Shift all references in formulas
    rewriteReferencesForRowDelete(index);
    (0, engine_1.recalcAll)();
    (0, render_1.rerenderAll)();
    saveToStorage();
}
function insertColAt(index) {
    if (state_1.cellGrid[0].length >= types_1.COL_COUNT)
        return;
    // Create new column (empty cells)
    for (let r = 0; r < state_1.cellGrid.length; r++) {
        state_1.cellGrid[r].splice(index, 0, (0, state_1.emptyCell)());
    }
    rewriteReferencesForColInsert(index);
    (0, engine_1.recalcAll)();
    (0, render_1.rerenderAll)();
    saveToStorage();
}
function deleteColAt(index) {
    if (state_1.cellGrid[0].length <= 1)
        return;
    for (let r = 0; r < state_1.cellGrid.length; r++) {
        state_1.cellGrid[r].splice(index, 1);
    }
    rewriteReferencesForColDelete(index);
    (0, engine_1.recalcAll)();
    (0, render_1.rerenderAll)();
    saveToStorage();
}
// ─── Formula Rewriting ─────────────────────────────────────────────────────────
// When a row/col is inserted/deleted, rewrite cell references in all formulas
function rewriteReferencesForRowInsert(rowIndex) {
    for (let r = 0; r < state_1.cellGrid.length; r++) {
        for (let c = 0; c < state_1.cellGrid[r].length; c++) {
            const cell = state_1.cellGrid[r][c];
            if (!cell.raw.startsWith('='))
                continue;
            cell.raw = rewriteFormulaForRowShift(cell.raw, rowIndex, 1);
        }
    }
}
function rewriteReferencesForRowDelete(deleteRow) {
    for (let r = 0; r < state_1.cellGrid.length; r++) {
        for (let c = 0; c < state_1.cellGrid[r].length; c++) {
            const cell = state_1.cellGrid[r][c];
            if (!cell.raw.startsWith('='))
                continue;
            const rewritten = rewriteFormulaForRowShift(cell.raw, deleteRow, -1);
            if (rewritten === '#REF!') {
                // Mark as error cell
                cell.raw = '=REF';
                cell.kind = 'error';
                cell.value = '#REF!';
                cell.error = '#REF!';
            }
            else {
                cell.raw = rewritten;
            }
        }
    }
}
function rewriteReferencesForColInsert(colIndex) {
    for (let r = 0; r < state_1.cellGrid.length; r++) {
        for (let c = 0; c < state_1.cellGrid[r].length; c++) {
            const cell = state_1.cellGrid[r][c];
            if (!cell.raw.startsWith('='))
                continue;
            cell.raw = rewriteFormulaForColShift(cell.raw, colIndex, 1);
        }
    }
}
function rewriteReferencesForColDelete(deleteCol) {
    for (let r = 0; r < state_1.cellGrid.length; r++) {
        for (let c = 0; c < state_1.cellGrid[r].length; c++) {
            const cell = state_1.cellGrid[r][c];
            if (!cell.raw.startsWith('='))
                continue;
            const rewritten = rewriteFormulaForColShift(cell.raw, deleteCol, -1);
            if (rewritten === '#REF!') {
                cell.raw = '=REF';
                cell.kind = 'error';
                cell.value = '#REF!';
                cell.error = '#REF!';
            }
            else {
                cell.raw = rewritten;
            }
        }
    }
}
// Rewrite cell references in a formula string when rows shift
// Returns rewritten formula, or '#REF!' if any reference is invalid
function rewriteFormulaForRowShift(formula, pivotRow, delta) {
    let invalid = false;
    const rewritten = formula.replace(/[A-Z]\d+/gi, (match) => {
        const ref = (0, utils_1.parseCellRef)(match);
        if (!ref)
            return match;
        if (delta > 0) {
            // Insert row: refs to rows >= pivot shift +1
            if (ref.row >= pivotRow) {
                const newRow = ref.row + delta;
                if (newRow < state_1.cellGrid.length) {
                    return (0, utils_1.colLabel)(ref.col) + (newRow + 1);
                }
            }
        }
        else {
            // Delete row: refs to rows > pivot shift -1
            if (ref.row > pivotRow) {
                const newRow = ref.row + delta;
                if (newRow >= 0) {
                    return (0, utils_1.colLabel)(ref.col) + (newRow + 1);
                }
                else {
                    invalid = true;
                    return match;
                }
            }
            // Reference exactly to the deleted row → invalid
            if (ref.row === pivotRow) {
                invalid = true;
                return match;
            }
        }
        return match;
    });
    return invalid ? '#REF!' : rewritten;
}
// Rewrite cell references in a formula string when columns shift
// Returns rewritten formula, or '#REF!' if any reference is invalid
function rewriteFormulaForColShift(formula, pivotCol, delta) {
    let invalid = false;
    const rewritten = formula.replace(/[A-Z]\d+/gi, (match) => {
        const ref = (0, utils_1.parseCellRef)(match);
        if (!ref)
            return match;
        if (delta > 0) {
            if (ref.col >= pivotCol) {
                const newCol = ref.col + delta;
                if (newCol < state_1.cellGrid[0].length) {
                    return (0, utils_1.colLabel)(newCol) + (ref.row + 1);
                }
            }
        }
        else {
            if (ref.col > pivotCol) {
                const newCol = ref.col + delta;
                if (newCol >= 0) {
                    return (0, utils_1.colLabel)(newCol) + (ref.row + 1);
                }
                else {
                    invalid = true;
                    return match;
                }
            }
            if (ref.col === pivotCol) {
                invalid = true;
                return match;
            }
        }
        return match;
    });
    return invalid ? '#REF!' : rewritten;
}
// ─── Persistence ───────────────────────────────────────────────────────────────
function saveToStorage() {
    try {
        const data = {};
        for (let r = 0; r < (0, state_1.getGridRowCount)(); r++) {
            for (let c = 0; c < (0, state_1.getGridColCount)(); c++) {
                const cell = state_1.cellGrid[r][c];
                if (cell && cell.raw !== '') {
                    data[(0, utils_1.cellAddress)(c, r)] = cell.raw;
                }
            }
        }
        localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
    }
    catch {
        // Storage full or unavailable
    }
}
function loadFromStorage() {
    try {
        const raw = localStorage.getItem(STORAGE_KEY);
        if (!raw)
            return;
        const data = JSON.parse(raw);
        for (const [addr, value] of Object.entries(data)) {
            const ref = (0, utils_1.parseCellRef)(addr);
            if (ref && (0, state_1.inBounds)(ref.row, ref.col)) {
                (0, state_1.setCellRaw)(ref.row, ref.col, value);
            }
        }
    }
    catch {
        // Corrupted data
    }
}
function flashSave() {
    const btn = document.getElementById('btn-save');
    const orig = btn.textContent;
    btn.textContent = '✓ Saved!';
    setTimeout(() => { btn.textContent = orig; }, 1200);
}
// ─── Start ─────────────────────────────────────────────────────────────────────
init();
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ERR_PARSE = exports.ERR_REF = exports.ERR_DIV0 = exports.ERR_CYCLE = exports.COL_HEADER_HEIGHT = exports.ROW_HEADER_WIDTH = exports.ROW_HEIGHT = exports.COL_WIDTH = exports.ROW_COUNT = exports.COL_COUNT = void 0;
// Tunable constants
exports.COL_COUNT = 26; // A-Z
exports.ROW_COUNT = 100;
exports.COL_WIDTH = 100;
exports.ROW_HEIGHT = 24;
exports.ROW_HEADER_WIDTH = 40;
exports.COL_HEADER_HEIGHT = 24;
// Error strings
exports.ERR_CYCLE = '#CYCLE!';
exports.ERR_DIV0 = '#DIV/0!';
exports.ERR_REF = '#REF!';
exports.ERR_PARSE = '#ERR!';
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.selRow = exports.selCol = exports.rowOrder = exports.colOrder = exports.cellGrid = void 0;
exports.emptyCell = emptyCell;
exports.setCellRaw = setCellRaw;
exports.getCellRaw = getCellRaw;
exports.setCellValue = setCellValue;
exports.getCell = getCell;
exports.getGridColCount = getGridColCount;
exports.getGridRowCount = getGridRowCount;
exports.initOrder = initOrder;
exports.inBounds = inBounds;
exports.getSelection = getSelection;
exports.setSelection = setSelection;
const types_1 = require("./types");
const utils_1 = require("./utils");
// Grid is a 2D array of Cell objects.
// colOrder / rowOrder track insertion order (may differ from default after inserts/deletes)
exports.cellGrid = createEmptyGrid();
exports.colOrder = [];
exports.rowOrder = [];
exports.selCol = 0;
exports.selRow = 0;
function createEmptyGrid() {
    const grid = [];
    for (let r = 0; r < types_1.ROW_COUNT; r++) {
        const row = [];
        for (let c = 0; c < types_1.COL_COUNT; c++) {
            row.push(emptyCell());
        }
        grid.push(row);
    }
    return grid;
}
function emptyCell() {
    return { raw: '', kind: 'text', value: '' };
}
function setCellRaw(r, c, raw) {
    if (r < 0 || r >= exports.cellGrid.length || c < 0 || c >= exports.cellGrid[0].length)
        return;
    exports.cellGrid[r][c] = { raw, kind: 'text', value: raw };
}
function getCellRaw(r, c) {
    if (r < 0 || r >= exports.cellGrid.length || c < 0 || c >= exports.cellGrid[0].length)
        return '';
    return exports.cellGrid[r][c].raw;
}
function setCellValue(r, c, kind, value, error) {
    if (r < 0 || r >= exports.cellGrid.length || c < 0 || c >= exports.cellGrid[0].length)
        return;
    const cell = exports.cellGrid[r][c];
    cell.kind = kind;
    cell.value = value;
    cell.error = error;
}
function getCell(r, c) {
    if (r < 0 || r >= exports.cellGrid.length || c < 0 || c >= exports.cellGrid[0].length)
        return null;
    return exports.cellGrid[r][c];
}
function getGridColCount() {
    return exports.cellGrid[0]?.length ?? 0;
}
function getGridRowCount() {
    return exports.cellGrid.length;
}
// Initialize column and row order arrays
function initOrder() {
    exports.colOrder = [];
    for (let c = 0; c < types_1.COL_COUNT; c++) {
        exports.colOrder.push((0, utils_1.colLabel)(c));
    }
    exports.rowOrder = [];
    for (let r = 0; r < types_1.ROW_COUNT; r++) {
        exports.rowOrder.push(r);
    }
}
// Check if a position is within grid bounds
function inBounds(r, c) {
    return r >= 0 && r < exports.cellGrid.length && c >= 0 && c < exports.cellGrid[0]?.length;
}
function getSelection() {
    return { col: exports.selCol, row: exports.selRow };
}
function setSelection(col, row) {
    exports.selCol = col;
    exports.selRow = row;
}
};

// ── module: src/utils.ts ──
__mods["src/utils.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.colLabel = colLabel;
exports.colIndex = colIndex;
exports.parseCellRef = parseCellRef;
exports.cellAddress = cellAddress;
exports.formatNumber = formatNumber;
exports.isValidCellRef = isValidCellRef;
exports.isValidRangeRef = isValidRangeRef;
const types_1 = require("./types");
// Convert column index (0-based) to letter label (A, B, ..., Z)
function colLabel(c) {
    if (c < 0 || c >= types_1.COL_COUNT)
        return '';
    return String.fromCharCode(65 + c);
}
// Convert column label to 0-based index
function colIndex(label) {
    const upper = label.toUpperCase();
    if (upper.length === 1) {
        const idx = upper.charCodeAt(0) - 65;
        return idx >= 0 && idx < types_1.COL_COUNT ? idx : -1;
    }
    return -1;
}
// Parse cell reference "A1" into { col, row }
function parseCellRef(ref) {
    if (!ref || typeof ref !== 'string')
        return null;
    const match = ref.match(/^([A-Z])(\d+)$/i);
    if (!match)
        return null;
    const col = colIndex(match[1]);
    const row = parseInt(match[2], 10) - 1;
    if (col < 0 || col >= types_1.COL_COUNT || row < 0 || row >= 1000)
        return null; // generous row bound
    return { col, row };
}
// Cell address string from col/row indices
function cellAddress(c, r) {
    return colLabel(c) + (r + 1);
}
// Format a number for display: up to 10 significant digits, no trailing zeros
function formatNumber(n) {
    if (isNaN(n))
        return '#ERR!';
    if (!isFinite(n))
        return '#ERR!';
    if (Number.isInteger(n) && Math.abs(n) < 1e15) {
        return n.toString();
    }
    const s = n.toPrecision(10);
    return parseFloat(s).toString();
}
// Check if a string is a valid cell reference (e.g. "A1", "Z100")
function isValidCellRef(s) {
    return /^([A-Z])(\d+)$/i.test(s);
}
// Check if a string is a valid range reference (e.g. "A1:A5")
function isValidRangeRef(s) {
    return /^[A-Z]\d+:[A-Z]\d+$/i.test(s);
}
};

// ── module: src/tokenizer.ts ──
__mods["src/tokenizer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.tokenize = tokenize;
function tokenize(source) {
    const tokens = [];
    let i = 0;
    while (i < source.length) {
        const ch = source[i];
        // Skip whitespace
        if (ch === ' ' || ch === '\t') {
            i++;
            continue;
        }
        // Number (integer or decimal)
        if ((ch >= '0' && ch <= '9') || (ch === '.')) {
            let numStr = '';
            const start = i;
            while (i < source.length && ((source[i] >= '0' && source[i] <= '9') || source[i] === '.')) {
                numStr += source[i];
                i++;
            }
            tokens.push({ type: 'NUMBER', value: parseFloat(numStr), col: start });
            continue;
        }
        // Letter → identifier (cell ref or function name)
        if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
            let ident = '';
            const start = i;
            while (i < source.length && ((source[i] >= 'A' && source[i] <= 'Z') || (source[i] >= 'a' && source[i] <= 'z') || (source[i] >= '0' && source[i] <= '9'))) {
                ident += source[i];
                i++;
            }
            // Check if next char is ':' → it's a range start
            if (i < source.length && source[i] === ':') {
                tokens.push({ type: 'IDENT', value: ident, col: start });
                continue;
            }
            tokens.push({ type: 'IDENT', value: ident, col: start });
            continue;
        }
        // Operators and punctuation
        switch (ch) {
            case '+':
                tokens.push({ type: 'PLUS', value: '+', col: i });
                break;
            case '-':
                tokens.push({ type: 'MINUS', value: '-', col: i });
                break;
            case '*':
                tokens.push({ type: 'STAR', value: '*', col: i });
                break;
            case '/':
                tokens.push({ type: 'SLASH', value: '/', col: i });
                break;
            case '(':
                tokens.push({ type: 'LPAREN', value: '(', col: i });
                break;
            case ')':
                tokens.push({ type: 'RPAREN', value: ')', col: i });
                break;
            case ',':
                tokens.push({ type: 'COMMA', value: ',', col: i });
                break;
            case ':':
                tokens.push({ type: 'COLON', value: ':', col: i });
                break;
            default:
                throw new Error(`Unexpected character '${ch}' at position ${i}`);
        }
        i++;
    }
    tokens.push({ type: 'EOF', value: '', col: i });
    return tokens;
}
};

// ── module: src/parser.ts ──
__mods["src/parser.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = void 0;
exports.parseFormula = parseFormula;
const tokenizer_1 = require("./tokenizer");
const utils_1 = require("./utils");
class Parser {
    constructor(source) {
        this.pos = 0;
        this.tokens = (0, tokenizer_1.tokenize)(source);
    }
    parse() {
        const expr = this.parseExpression();
        if (this.current().type !== 'EOF') {
            throw new Error(`Unexpected token '${this.current().value}' at position ${this.current().col}`);
        }
        return expr;
    }
    current() {
        return this.tokens[this.pos] || this.tokens[this.tokens.length - 1];
    }
    eat(type) {
        const tok = this.current();
        if (tok.type === 'EOF' && type !== 'EOF') {
            throw new Error(`Unexpected end of expression`);
        }
        this.pos++;
        return tok;
    }
    match(type) {
        return this.current().type === type;
    }
    // Expression = Term (('+' | '-') Term)*
    parseExpression() {
        let left = this.parseTerm();
        while (this.match('PLUS') || this.match('MINUS')) {
            const op = this.current().value;
            this.eat(op);
            const right = this.parseTerm();
            left = { kind: 'binop', op, left, right };
        }
        return left;
    }
    // Term = Unary (('*' | '/') Unary)*
    parseTerm() {
        let left = this.parseUnary();
        while (this.match('STAR') || this.match('SLASH')) {
            const op = this.current().value;
            this.eat(op);
            const right = this.parseUnary();
            left = { kind: 'binop', op, left, right };
        }
        return left;
    }
    // Unary = '-' Unary | Primary
    parseUnary() {
        if (this.match('MINUS')) {
            this.eat('MINUS');
            const operand = this.parsePrimary();
            return { kind: 'unary', op: '-', operand };
        }
        if (this.match('PLUS')) {
            this.eat('PLUS');
            return this.parsePrimary();
        }
        return this.parsePrimary();
    }
    // Primary = NUMBER | CELL_COLON_CELL | CELL '(' Args ')' | '(' Expression ')'
    parsePrimary() {
        const tok = this.current();
        // Number literal
        if (tok.type === 'NUMBER') {
            this.eat('NUMBER');
            return { kind: 'number', value: tok.value };
        }
        // Identifier: could be cell ref, range, or function call
        if (tok.type === 'IDENT') {
            const identStr = tok.value;
            this.eat('IDENT');
            // Function call: IDENT '(' ...
            if (this.match('LPAREN')) {
                this.eat('LPAREN');
                const args = [];
                if (!this.match('RPAREN')) {
                    args.push(this.parseExpression());
                    while (this.match('COMMA')) {
                        this.eat('COMMA');
                        args.push(this.parseExpression());
                    }
                }
                this.eat('RPAREN');
                return { kind: 'call', name: identStr.toUpperCase(), args };
            }
            // Range: IDENT ':' IDENT (e.g. A1:B5)
            if (this.match('COLON')) {
                this.eat('COLON');
                const endTok = this.eat('IDENT');
                const startRef = (0, utils_1.parseCellRef)(identStr);
                const endRef = (0, utils_1.parseCellRef)(endTok.value);
                if (!startRef || !endRef) {
                    throw new Error(`Invalid range '${identStr}:${endTok.value}'`);
                }
                return { kind: 'range', col1: startRef.col, row1: startRef.row, col2: endRef.col, row2: endRef.row };
            }
            // Cell reference: A1, B7
            const ref = (0, utils_1.parseCellRef)(identStr);
            if (!ref) {
                throw new Error(`Invalid cell reference '${identStr}'`);
            }
            return { kind: 'cell', col: ref.col, row: ref.row };
        }
        // Parenthesized expression
        if (tok.type === 'LPAREN') {
            this.eat('LPAREN');
            const expr = this.parseExpression();
            this.eat('RPAREN');
            return expr;
        }
        throw new Error(`Unexpected token '${tok.value}' at position ${tok.col}`);
    }
}
exports.Parser = Parser;
// Top-level parse function
function parseFormula(source) {
    const parser = new Parser(source);
    return parser.parse();
}
};

// ── module: src/evaluator.ts ──
__mods["src/evaluator.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseRaw = parseRaw;
exports.evaluate = evaluate;
exports.setCell = setCell;
exports.collectRefs = collectRefs;
const types_1 = require("./types");
const parser_1 = require("./parser");
const utils_1 = require("./utils");
const state_1 = require("./state");
// Parse a formula string (after the =) into an AST, or throw on error
function parseRaw(raw) {
    if (raw === '') {
        return { isFormula: false };
    }
    if (!raw.startsWith('=')) {
        // Plain text or number
        const num = Number(raw);
        if (!isNaN(num) && raw.trim() !== '') {
            return { isFormula: false, error: undefined };
        }
        return { isFormula: false, error: undefined };
    }
    try {
        const expr = (0, parser_1.parseFormula)(raw.substring(1));
        return { isFormula: true, expr };
    }
    catch (e) {
        return { isFormula: true, error: types_1.ERR_PARSE };
    }
}
// Evaluate an AST node at position (col, row)
function evaluate(expr, col, row) {
    try {
        const result = evalExpr(expr, col, row);
        if (result.error) {
            return result;
        }
        return { value: result.value, kind: 'number' };
    }
    catch (e) {
        const msg = e?.message ?? '';
        if (msg.includes('#CYCLE!')) {
            return { value: types_1.ERR_CYCLE, kind: 'error', error: types_1.ERR_CYCLE };
        }
        if (msg.includes('#DIV/0!')) {
            return { value: types_1.ERR_DIV0, kind: 'error', error: types_1.ERR_DIV0 };
        }
        if (msg.includes('#REF!')) {
            return { value: types_1.ERR_REF, kind: 'error', error: types_1.ERR_REF };
        }
        return { value: types_1.ERR_PARSE, kind: 'error', error: types_1.ERR_PARSE };
    }
}
// Set a cell's raw value and classify it (don't evaluate formulas here)
function setCell(r, c, raw) {
    const cell = { raw, kind: 'text', value: raw };
    if (raw === '') {
        cell.kind = 'text';
        cell.value = '';
    }
    else if (!raw.startsWith('=')) {
        const num = Number(raw);
        if (!isNaN(num) && raw.trim() !== '') {
            cell.kind = 'number';
            cell.value = num;
        }
        else {
            cell.kind = 'text';
            cell.value = raw;
        }
    }
    else {
        cell.kind = 'formula';
        cell.value = raw; // placeholder until evaluated
    }
    state_1.cellGrid[r][c] = cell;
    return cell;
}
function evalExpr(expr, _col, _row) {
    switch (expr.kind) {
        case 'number':
            return { value: expr.value, kind: 'number' };
        case 'cell': {
            const cell = (0, state_1.getCell)(expr.row, expr.col);
            if (!cell || !(0, state_1.inBounds)(expr.row, expr.col)) {
                return { value: types_1.ERR_REF, kind: 'error', error: types_1.ERR_REF };
            }
            if (cell.kind === 'error') {
                return { value: cell.value, kind: 'error', error: cell.error };
            }
            if (cell.raw.startsWith('=') && (cell.value === '' || (typeof cell.value === 'string' && cell.value.startsWith('=')))) {
                // Formula not yet evaluated or in a cycle
                if (cell.value === '')
                    return { value: 0, kind: 'number' };
                return { value: types_1.ERR_CYCLE, kind: 'error', error: types_1.ERR_CYCLE };
            }
            const v = cell.kind === 'number' ? cell.value : Number(cell.value);
            if (isNaN(v) && cell.kind === 'text' && cell.value !== '') {
                // Text that can't be a number in a numeric context → 0
                return { value: 0, kind: 'number' };
            }
            return { value: isNaN(v) ? 0 : v, kind: cell.kind };
        }
        case 'range': {
            // Range evaluates to an array of values — but in arithmetic context, this doesn't make sense alone.
            // Range is primarily used as a function argument.
            // When used directly in arithmetic, treat as #ERR!
            return { value: types_1.ERR_PARSE, kind: 'error', error: types_1.ERR_PARSE };
        }
        case 'binop': {
            const left = evalExpr(expr.left, _col, _row);
            if (left.error)
                return left;
            const right = evalExpr(expr.right, _col, _row);
            if (right.error)
                return right;
            const lNum = Number(left.value);
            const rNum = Number(right.value);
            switch (expr.op) {
                case '+': return { value: lNum + rNum, kind: 'number' };
                case '-': return { value: lNum - rNum, kind: 'number' };
                case '*': return { value: lNum * rNum, kind: 'number' };
                case '/':
                    if (rNum === 0)
                        return { value: types_1.ERR_DIV0, kind: 'error', error: types_1.ERR_DIV0 };
                    return { value: lNum / rNum, kind: 'number' };
                default: return { value: types_1.ERR_PARSE, kind: 'error', error: types_1.ERR_PARSE };
            }
        }
        case 'unary': {
            const operand = evalExpr(expr.operand, _col, _row);
            if (operand.error)
                return operand;
            if (expr.op === '-')
                return { value: -Number(operand.value), kind: 'number' };
            return operand;
        }
        case 'call': {
            return evalFunction(expr.name, expr.args, _col, _row);
        }
    }
}
// Get a list of numeric values from an expression (handles cells, ranges, numbers)
function resolveArgsToValues(args, col, row) {
    const values = [];
    for (const arg of args) {
        switch (arg.kind) {
            case 'number':
                values.push(arg.value);
                break;
            case 'cell': {
                const cell = (0, state_1.getCell)(arg.row, arg.col);
                if (!cell || !(0, state_1.inBounds)(arg.row, arg.col))
                    continue;
                if (cell.kind === 'error') {
                    return { values: [], hasError: true, error: cell.error };
                }
                const v = cell.kind === 'number' ? cell.value : Number(cell.value);
                if (!isNaN(v))
                    values.push(v);
                break;
            }
            case 'range': {
                const { col1, row1, col2, row2 } = arg;
                const minC = Math.min(col1, col2);
                const maxC = Math.max(col1, col2);
                const minR = Math.min(row1, row2);
                const maxR = Math.max(row1, row2);
                for (let r = minR; r <= maxR; r++) {
                    for (let c = minC; c <= maxC; c++) {
                        const cell = (0, state_1.getCell)(r, c);
                        if (!cell || !(0, state_1.inBounds)(r, c))
                            continue;
                        if (cell.kind === 'error') {
                            return { values: [], hasError: true, error: cell.error };
                        }
                        const v = cell.kind === 'number' ? cell.value : Number(cell.value);
                        if (!isNaN(v))
                            values.push(v);
                    }
                }
                break;
            }
            case 'binop':
            case 'unary': {
                const result = evalExpr(arg, col, row);
                if (result.error)
                    return { values: [], hasError: true, error: result.error };
                values.push(Number(result.value));
                break;
            }
            case 'call': {
                const result = evalExpr(arg, col, row);
                if (result.error)
                    return { values: [], hasError: true, error: result.error };
                values.push(Number(result.value));
                break;
            }
        }
    }
    return { values, hasError: false };
}
function evalFunction(name, args, col, row) {
    const { values, hasError, error } = resolveArgsToValues(args, col, row);
    if (hasError)
        return { value: error || types_1.ERR_PARSE, kind: 'error', error: error };
    switch (name) {
        case 'SUM':
            return { value: values.reduce((a, b) => a + b, 0), kind: 'number' };
        case 'AVG':
        case 'AVERAGE':
            if (values.length === 0)
                return { value: 0, kind: 'number' };
            return { value: values.reduce((a, b) => a + b, 0) / values.length, kind: 'number' };
        case 'MIN':
            if (values.length === 0)
                return { value: 0, kind: 'number' };
            return { value: Math.min(...values), kind: 'number' };
        case 'MAX':
            if (values.length === 0)
                return { value: 0, kind: 'number' };
            return { value: Math.max(...values), kind: 'number' };
        case 'COUNT':
            return { value: values.length, kind: 'number' };
        default:
            return { value: types_1.ERR_PARSE, kind: 'error', error: types_1.ERR_PARSE };
    }
}
// Extract cell references from an AST node (for dependency graph)
function collectRefs(expr) {
    const refs = new Set();
    collectRefsFrom(expr, refs);
    return refs;
}
function collectRefsFrom(expr, refs) {
    switch (expr.kind) {
        case 'cell':
            refs.add(`${(0, utils_1.colLabel)(expr.col)}${expr.row + 1}`);
            break;
        case 'range': {
            const minC = Math.min(expr.col1, expr.col2);
            const maxC = Math.max(expr.col1, expr.col2);
            const minR = Math.min(expr.row1, expr.row2);
            const maxR = Math.max(expr.row1, expr.row2);
            for (let r = minR; r <= maxR; r++) {
                for (let c = minC; c <= maxC; c++) {
                    refs.add(`${(0, utils_1.colLabel)(c)}${r + 1}`);
                }
            }
            break;
        }
        case 'binop':
            collectRefsFrom(expr.left, refs);
            collectRefsFrom(expr.right, refs);
            break;
        case 'unary':
            collectRefsFrom(expr.operand, refs);
            break;
        case 'call':
            for (const arg of expr.args) {
                collectRefsFrom(arg, refs);
            }
            break;
    }
}
};

// ── module: src/engine.ts ──
__mods["src/engine.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateCell = updateCell;
exports.recalcAll = recalcAll;
const state_1 = require("./state");
const evaluator_1 = require("./evaluator");
const types_1 = require("./types");
const utils_1 = require("./utils");
function buildDepGraph() {
    const dependsOn = new Map();
    const dependents = new Map();
    for (let r = 0; r < (0, state_1.getGridRowCount)(); r++) {
        for (let c = 0; c < (0, state_1.getGridColCount)(); c++) {
            const cell = state_1.cellGrid[r][c];
            if (!cell || !cell.raw.startsWith('='))
                continue;
            const addr = (0, utils_1.cellAddress)(c, r);
            const parsed = (0, evaluator_1.parseRaw)(cell.raw);
            if (parsed.expr) {
                const refs = (0, evaluator_1.collectRefs)(parsed.expr);
                dependsOn.set(addr, refs);
                for (const ref of refs) {
                    if (!dependents.has(ref))
                        dependents.set(ref, new Set());
                    dependents.get(ref).add(addr);
                }
            }
        }
    }
    return { dependsOn, dependents };
}
function collectDownstream(addr, dependents) {
    const result = new Set();
    const queue = [addr];
    const visited = new Set();
    while (queue.length > 0) {
        const current = queue.shift();
        if (visited.has(current))
            continue;
        visited.add(current);
        const deps = dependents.get(current);
        if (deps) {
            for (const dep of deps) {
                if (!result.has(dep)) {
                    result.add(dep);
                    queue.push(dep);
                }
            }
        }
    }
    return result;
}
function topoSort(addresses, dependsOn) {
    const result = [];
    const inDegree = new Map();
    const subDeps = new Map();
    for (const addr of addresses) {
        const deps = dependsOn.get(addr) || new Set();
        const filtered = new Set();
        for (const d of deps) {
            if (addresses.has(d)) {
                filtered.add(d);
            }
        }
        subDeps.set(addr, filtered);
        inDegree.set(addr, filtered.size);
    }
    const queue = [];
    for (const [addr, deg] of inDegree) {
        if (deg === 0)
            queue.push(addr);
    }
    while (queue.length > 0) {
        const current = queue.shift();
        result.push(current);
        for (const addr of addresses) {
            const deps = subDeps.get(addr);
            if (deps && deps.has(current)) {
                deps.delete(current);
                inDegree.set(addr, inDegree.get(addr) - 1);
                if (inDegree.get(addr) === 0) {
                    queue.push(addr);
                }
            }
        }
    }
    for (const addr of addresses) {
        if (!result.includes(addr)) {
            result.push(addr);
        }
    }
    return result;
}
function detectCycles(addresses, dependsOn) {
    const cycleCells = new Set();
    const WHITE = 0, GRAY = 1, BLACK = 2;
    const color = new Map();
    for (const addr of addresses) {
        color.set(addr, WHITE);
    }
    const dfs = (node, path) => {
        color.set(node, GRAY);
        path.push(node);
        const deps = dependsOn.get(node);
        if (deps) {
            for (const dep of deps) {
                if (!addresses.has(dep))
                    continue;
                const c = color.get(dep);
                if (c === GRAY) {
                    const cycleStart = path.indexOf(dep);
                    for (let i = cycleStart; i < path.length; i++) {
                        cycleCells.add(path[i]);
                    }
                    return;
                }
                if (c === WHITE) {
                    dfs(dep, path);
                }
            }
        }
        path.pop();
        color.set(node, BLACK);
    };
    for (const addr of addresses) {
        if (color.get(addr) === WHITE) {
            dfs(addr, []);
        }
    }
    return cycleCells;
}
function recalcCell(r, c) {
    const cell = state_1.cellGrid[r][c];
    if (!cell || !cell.raw.startsWith('='))
        return false;
    // Skip already-marked #REF! errors (from row/col deletion)
    if (cell.kind === 'error' && cell.error === '#REF!')
        return false;
    const parsed = (0, evaluator_1.parseRaw)(cell.raw);
    if (parsed.error) {
        cell.kind = 'error';
        cell.value = parsed.error;
        cell.error = parsed.error;
        return true;
    }
    if (!parsed.expr) {
        cell.kind = 'text';
        cell.value = cell.raw;
        return true;
    }
    const result = (0, evaluator_1.evaluate)(parsed.expr, c, r);
    const changed = cell.value !== result.value || cell.kind !== result.kind;
    cell.kind = result.kind;
    cell.value = result.value;
    cell.error = result.error;
    return changed;
}
// Main recalculation: update a cell and propagate changes to dependents
// Returns list of addresses that were updated (including the source)
function updateCell(r, c, raw) {
    if (!(0, state_1.inBounds)(r, c))
        return { updated: [] };
    const updated = [];
    const addr = (0, utils_1.cellAddress)(c, r);
    // Set the cell
    (0, evaluator_1.setCell)(r, c, raw);
    updated.push(addr);
    // Evaluate this cell if it's a formula
    const cell = state_1.cellGrid[r][c];
    if (cell.kind === 'formula' && cell.raw.startsWith('=')) {
        const parsed = (0, evaluator_1.parseRaw)(cell.raw);
        if (parsed.error) {
            cell.kind = 'error';
            cell.value = parsed.error;
            cell.error = parsed.error;
        }
        else if (parsed.expr) {
            const result = (0, evaluator_1.evaluate)(parsed.expr, c, r);
            cell.kind = result.kind;
            cell.value = result.value;
            cell.error = result.error;
        }
    }
    // Rebuild dependency graph
    const { dependsOn, dependents } = buildDepGraph();
    // Collect all downstream dependents
    const downstream = collectDownstream(addr, dependents);
    if (downstream.size === 0)
        return { updated };
    // Detect cycles among affected cells
    const allAffected = new Set(downstream);
    allAffected.add(addr);
    const cycles = detectCycles(allAffected, dependsOn);
    // Topo sort non-cycle cells
    const toSort = new Set();
    for (const a of allAffected) {
        if (!cycles.has(a) && a !== addr)
            toSort.add(a); // skip source (already evaluated)
    }
    const order = topoSort(toSort, dependsOn);
    // Recalculate in topological order
    for (const a of order) {
        if (typeof a !== 'string' || !a)
            continue;
        const ref = (0, utils_1.parseCellRef)(a);
        if (ref && (0, state_1.inBounds)(ref.row, ref.col)) {
            recalcCell(ref.row, ref.col);
            updated.push(a);
        }
    }
    // Mark cycle cells
    for (const a of cycles) {
        if (typeof a !== 'string' || !a)
            continue;
        const ref = (0, utils_1.parseCellRef)(a);
        if (ref && (0, state_1.inBounds)(ref.row, ref.col)) {
            const cell = state_1.cellGrid[ref.row][ref.col];
            if (cell && cell.raw.startsWith('=')) {
                cell.kind = 'error';
                cell.value = types_1.ERR_CYCLE;
                cell.error = types_1.ERR_CYCLE;
            }
            updated.push(a);
        }
    }
    return { updated };
}
// Full recalculation of all formulas (used on load or full re-render)
function recalcAll() {
    const { dependsOn, dependents } = buildDepGraph();
    const updated = [];
    // Collect all formula cells
    const formulaCells = new Set();
    for (let r = 0; r < (0, state_1.getGridRowCount)(); r++) {
        for (let c = 0; c < (0, state_1.getGridColCount)(); c++) {
            const cell = state_1.cellGrid[r][c];
            if (cell && cell.raw.startsWith('=')) {
                formulaCells.add((0, utils_1.cellAddress)(c, r));
            }
        }
    }
    if (formulaCells.size === 0)
        return updated;
    const cycles = detectCycles(formulaCells, dependsOn);
    const toSort = new Set();
    for (const a of formulaCells) {
        if (!cycles.has(a))
            toSort.add(a);
    }
    const order = topoSort(toSort, dependsOn);
    for (const a of order) {
        const ref = (0, utils_1.parseCellRef)(a);
        if (ref && (0, state_1.inBounds)(ref.row, ref.col)) {
            recalcCell(ref.row, ref.col);
            updated.push(a);
        }
    }
    for (const a of cycles) {
        const ref = (0, utils_1.parseCellRef)(a);
        if (ref && (0, state_1.inBounds)(ref.row, ref.col)) {
            const cell = state_1.cellGrid[ref.row][ref.col];
            if (cell && cell.raw.startsWith('=')) {
                cell.kind = 'error';
                cell.value = types_1.ERR_CYCLE;
                cell.error = types_1.ERR_CYCLE;
            }
            updated.push(a);
        }
    }
    return updated;
}
};

// ── module: src/render.ts ──
__mods["src/render.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initGrid = initGrid;
exports.updateCellAt = updateCellAt;
exports.updateSelection = updateSelection;
exports.setFormulaBarValue = setFormulaBarValue;
exports.getFormulaBarValue = getFormulaBarValue;
exports.rerenderAll = rerenderAll;
exports.rerenderHeaders = rerenderHeaders;
const state_1 = require("./state");
const types_1 = require("./types");
const utils_1 = require("./utils");
let table = null;
let tbody = null;
let thead = null;
let cellMap = new Map();
function initGrid() {
    (0, state_1.initOrder)();
    table = document.getElementById('grid');
    if (!table)
        return;
    table.innerHTML = '';
    thead = document.createElement('thead');
    tbody = document.createElement('tbody');
    table.appendChild(thead);
    table.appendChild(tbody);
    cellMap.clear();
    renderHeaders();
    renderRows();
    updateSelection();
}
function renderHeaders() {
    if (!thead)
        return;
    thead.innerHTML = '';
    const tr = document.createElement('tr');
    // Corner cell
    const cornerTh = document.createElement('th');
    cornerTh.className = 'corner';
    cornerTh.style.width = `${types_1.ROW_HEADER_WIDTH}px`;
    cornerTh.style.height = `${types_1.COL_HEADER_HEIGHT}px`;
    tr.appendChild(cornerTh);
    // Column headers
    const cCount = (0, state_1.getGridColCount)();
    for (let c = 0; c < cCount; c++) {
        const th = document.createElement('th');
        th.textContent = (0, utils_1.colLabel)(c);
        th.style.width = `${types_1.COL_WIDTH}px`;
        th.style.height = `${types_1.COL_HEADER_HEIGHT}px`;
        th.dataset.col = c.toString();
        th.addEventListener('contextmenu', (e) => {
            e.preventDefault();
            window.__spreadshowContextMenu(e.clientX, e.clientY, 'col', c);
        });
        tr.appendChild(th);
    }
    thead.appendChild(tr);
}
function renderRows() {
    if (!tbody)
        return;
    tbody.innerHTML = '';
    cellMap.clear();
    const rCount = (0, state_1.getGridRowCount)();
    const cCount = (0, state_1.getGridColCount)();
    for (let r = 0; r < rCount; r++) {
        const tr = document.createElement('tr');
        // Row header
        const th = document.createElement('th');
        th.className = 'row-header';
        th.textContent = (r + 1).toString();
        th.style.width = `${types_1.ROW_HEADER_WIDTH}px`;
        th.style.height = `${types_1.ROW_HEIGHT}px`;
        th.style.minWidth = `${types_1.ROW_HEADER_WIDTH}px`;
        th.dataset.row = r.toString();
        th.addEventListener('contextmenu', (e) => {
            e.preventDefault();
            window.__spreadshowContextMenu(e.clientX, e.clientY, 'row', r);
        });
        tr.appendChild(th);
        // Data cells
        for (let c = 0; c < cCount; c++) {
            const td = document.createElement('td');
            td.className = 'cell';
            td.dataset.row = r.toString();
            td.dataset.col = c.toString();
            td.style.width = `${types_1.COL_WIDTH}px`;
            td.style.height = `${types_1.ROW_HEIGHT}px`;
            updateCellDisplay(td, r, c);
            tr.appendChild(td);
            cellMap.set(`${c},${r}`, td);
        }
        tbody.appendChild(tr);
    }
}
function updateCellDisplay(td, r, c) {
    const cell = state_1.cellGrid[r]?.[c];
    if (!cell) {
        td.textContent = '';
        td.className = 'cell';
        return;
    }
    td.className = 'cell';
    if (cell.raw === '') {
        td.textContent = '';
        return;
    }
    if (cell.kind === 'error') {
        td.textContent = cell.value;
        td.classList.add('error-val');
        return;
    }
    if (cell.kind === 'number') {
        td.textContent = (0, utils_1.formatNumber)(cell.value);
        td.classList.add('number-val');
        return;
    }
    // text
    td.textContent = cell.value;
    td.classList.add('text-val');
}
function updateCellAt(r, c) {
    const td = cellMap.get(`${c},${r}`);
    if (td) {
        updateCellDisplay(td, r, c);
    }
}
function updateSelection() {
    // Remove old selection
    const prev = table?.querySelector('.selected');
    if (prev)
        prev.classList.remove('selected');
    // Add new selection
    const td = cellMap.get(`${state_1.selCol},${state_1.selRow}`);
    if (td) {
        td.classList.add('selected');
        // Scroll into view
        td.scrollIntoView({ block: 'nearest', inline: 'nearest' });
    }
    // Update formula bar cell ref
    const refEl = document.getElementById('cell-ref');
    if (refEl) {
        refEl.textContent = (0, utils_1.colLabel)(state_1.selCol) + (state_1.selRow + 1);
    }
    // Update formula bar content
    updateFormulaBar();
}
function updateFormulaBar() {
    const input = document.getElementById('formula-input');
    if (!input)
        return;
    const cell = state_1.cellGrid[state_1.selRow]?.[state_1.selCol];
    if (!cell) {
        input.value = '';
        return;
    }
    // Formula bar always shows raw content
    input.value = cell.raw;
}
function setFormulaBarValue(val) {
    const input = document.getElementById('formula-input');
    if (input)
        input.value = val;
}
function getFormulaBarValue() {
    const input = document.getElementById('formula-input');
    return input ? input.value : '';
}
function rerenderAll() {
    renderRows();
    updateSelection();
}
function rerenderHeaders() {
    renderHeaders();
    renderRows();
    updateSelection();
}
};

// ── module: src/editing.ts ──
__mods["src/editing.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isEditing = isEditing;
exports.getEditState = getEditState;
exports.startEdit = startEdit;
exports.commitEdit = commitEdit;
exports.commitFromFormulaBar = commitFromFormulaBar;
exports.cancelEdit = cancelEdit;
exports.moveSelection = moveSelection;
exports.selectCell = selectCell;
exports.clearCell = clearCell;
const state_1 = require("./state");
const engine_1 = require("./engine");
const render_1 = require("./render");
let editingCell = false;
let editStartValue = '';
let _selCol = 0;
let _selRow = 0;
// Mirror selection from state
function isEditing() {
    return editingCell;
}
function getEditState() {
    return { col: _selCol, row: _selRow, startValue: editStartValue };
}
// Start editing the current cell (either in-cell or formula bar)
function startEdit() {
    if (editingCell)
        return;
    editingCell = true;
    const s = (0, state_1.getSelection)();
    _selCol = s.col;
    _selRow = s.row;
    editStartValue = (0, state_1.getCellRaw)(_selRow, _selCol);
    const input = document.getElementById('formula-input');
    if (input) {
        input.value = editStartValue;
        input.select();
        input.focus();
    }
}
// Commit the current edit; returns updated addresses
function commitEdit() {
    if (!editingCell)
        return [];
    editingCell = false;
    const value = (0, render_1.getFormulaBarValue)();
    (0, state_1.setCellRaw)(_selRow, _selCol, value);
    const result = (0, engine_1.updateCell)(_selRow, _selCol, value);
    (0, render_1.updateSelection)();
    return result.updated;
}
// Commit edit from formula bar (even if not in explicit edit mode); returns updated addresses
function commitFromFormulaBar() {
    const value = (0, render_1.getFormulaBarValue)();
    const s = (0, state_1.getSelection)();
    const col = s.col;
    const row = s.row;
    (0, state_1.setCellRaw)(row, col, value);
    const result = (0, engine_1.updateCell)(row, col, value);
    (0, render_1.updateSelection)();
    return result.updated;
}
// Cancel the current edit (restore previous value)
function cancelEdit() {
    if (!editingCell)
        return;
    editingCell = false;
    const input = document.getElementById('formula-input');
    if (input) {
        input.value = editStartValue;
    }
}
// Move selection
function moveSelection(dcol, drow) {
    const { col, row } = (0, state_1.getSelection)();
    const newCol = Math.max(0, Math.min((0, state_1.getGridColCount)() - 1, col + dcol));
    const newRow = Math.max(0, Math.min((0, state_1.getGridRowCount)() - 1, row + drow));
    (0, state_1.setSelection)(newCol, newRow);
    (0, render_1.updateSelection)();
}
function selectCell(col, row) {
    if (col >= 0 && col < (0, state_1.getGridColCount)() && row >= 0 && row < (0, state_1.getGridRowCount)()) {
        (0, state_1.setSelection)(col, row);
        (0, render_1.updateSelection)();
    }
}
// Clear the current cell
function clearCell() {
    const s = (0, state_1.getSelection)();
    (0, state_1.setCellRaw)(s.row, s.col, '');
    (0, engine_1.updateCell)(s.row, s.col, '');
    (0, render_1.updateCellAt)(s.row, s.col);
    (0, render_1.updateSelection)();
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"unsloth/Qwen3.6-27B-MTP-GGUF-UD-Q5_K_XL","provider":"brigid","persona":"typescript","sessionId":"ed3f868c-ab17-421e-b89d-f44458df4295","tokensIn":8575979,"tokensOut":96487,"tokensTotal":8672466,"cost":0,"turns":155,"toolCalls":154,"failedToolCalls":6,"timestamp":"2026-08-04T09:59:24.958Z"} -->