← Spreadsheet results

Spreadsheet

Qwen 3.6 27b · 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 62,908 bytes · SHA-256 eb530af6034a
<!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: layout ── */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  height: 100%;
  overflow: hidden;
  font-family: 'Google Sans', 'Roboto', 'Arial', sans-serif;
  font-size: 13px;
  color: #202124;
  background: #fff;
}

#app {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}

.toolbar {
  display: flex;
  gap: 4px;
  padding: 6px 8px;
  background: #edf2fa;
  border-bottom: 1px solid #dadce0;
  flex-shrink: 0;
}

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

.toolbar button:hover {
  background: #f1f3f4;
  border-color: #c0c4c8;
}

.formula-bar-container {
  display: flex;
  align-items: center;
  padding: 4px 8px;
  background: #f8f9fa;
  border-bottom: 1px solid #dadce0;
  flex-shrink: 0;
  gap: 6px;
}

.cell-ref {
  width: 50px;
  text-align: center;
  font-weight: 600;
  color: #5f6368;
  font-size: 12px;
  flex-shrink: 0;
  user-select: none;
}

.formula-bar {
  flex: 1;
  padding: 4px 8px;
  border: 1px solid #dadce0;
  border-radius: 4px;
  font-size: 13px;
  font-family: inherit;
  outline: none;
  background: #fff;
}

.formula-bar:focus {
  border-color: #1a73e8;
  box-shadow: 0 0 0 1px #1a73e8;
}

.grid-container {
  flex: 1;
  overflow: auto;
  position: relative;
}
/* ── css: grid ── */
.spreadsheet-table {
  border-collapse: collapse;
  table-layout: fixed;
  width: max-content;
}

.spreadsheet-table th,
.spreadsheet-table td {
  border: 1px solid #e0e0e0;
  padding: 0;
  font-size: 13px;
  line-height: 24px;
  height: 24px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.corner-header {
  background: #f8f9fa;
  position: sticky;
  top: 0;
  z-index: 3;
  border-color: #dadce0;
}

.col-header {
  background: #f8f9fa;
  position: sticky;
  top: 0;
  z-index: 3;
  text-align: center;
  font-weight: 500;
  color: #5f6368;
  user-select: none;
  border-color: #dadce0;
}

.row-header {
  background: #f8f9fa;
  position: sticky;
  left: 0;
  z-index: 2;
  text-align: center;
  font-weight: 500;
  color: #5f6368;
  user-select: none;
  border-color: #dadce0;
}

.cell {
  background: #fff;
  cursor: cell;
  padding: 0 4px;
  overflow: hidden;
  text-overflow: ellipsis;
}

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

.cell-input {
  width: 100%;
  height: 100%;
  border: none;
  outline: none;
  padding: 0;
  font: inherit;
  background: transparent;
}

/* Header hover effects */
.col-header:hover,
.row-header:hover {
  background: #e8eaed;
}
</style>
</head>
<body>
<div id="app">
  <div id="toolbar" class="toolbar">
    <button id="btn-insert-row" title="Insert row above">Insert Row</button>
    <button id="btn-delete-row" title="Delete selected row">Delete Row</button>
    <button id="btn-insert-col" title="Insert column left">Insert Col</button>
    <button id="btn-delete-col" title="Delete selected column">Delete Col</button>
  </div>
  <div id="formula-bar-container" class="formula-bar-container">
    <span id="cell-ref" class="cell-ref">A1</span>
    <input id="formula-bar" class="formula-bar" type="text" placeholder="Formula bar">
  </div>
  <div id="grid-container" class="grid-container"></div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./config":"src/config.ts","./types":"src/types.ts","./state":"src/state.ts","./grid":"src/grid.ts","./dependency":"src/dependency.ts","./operations":"src/operations.ts","./persistence":"src/persistence.ts","./input":"src/input.ts"},"src/types.ts":{"./config":"src/config.ts"},"src/state.ts":{"./config":"src/config.ts","./types":"src/types.ts"},"src/tokenizer.ts":{"./types":"src/types.ts"},"src/parser.ts":{"./types":"src/types.ts","./tokenizer":"src/tokenizer.ts"},"src/evaluator.ts":{"./config":"src/config.ts","./types":"src/types.ts","./state":"src/state.ts","./parser":"src/parser.ts"},"src/dependency.ts":{"./config":"src/config.ts","./types":"src/types.ts","./state":"src/state.ts","./parser":"src/parser.ts","./evaluator":"src/evaluator.ts"},"src/grid.ts":{"./config":"src/config.ts","./types":"src/types.ts","./state":"src/state.ts"},"src/operations.ts":{"./config":"src/config.ts","./types":"src/types.ts","./state":"src/state.ts"},"src/persistence.ts":{"./types":"src/types.ts"},"src/input.ts":{"./config":"src/config.ts","./types":"src/types.ts","./state":"src/state.ts","./grid":"src/grid.ts","./dependency":"src/dependency.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 });
const config_1 = require("./config");
const state_1 = require("./state");
const grid_1 = require("./grid");
const dependency_1 = require("./dependency");
const operations_1 = require("./operations");
const persistence_1 = require("./persistence");
const input_1 = require("./input");
// AST cache for recalculation
const astCache = new Map();
function start() {
    const app = document.getElementById('app');
    const gridContainer = document.getElementById('grid-container');
    const formulaBar = document.getElementById('formula-bar');
    const cellRef = document.getElementById('cell-ref');
    // Set up formula bar
    (0, input_1.setFormulaBarInput)(formulaBar);
    // Init grid
    (0, grid_1.initGrid)(gridContainer);
    // Load from localStorage
    (0, persistence_1.loadSheet)(state_1.sheet);
    // Full recalculation
    const revDeps = (0, dependency_1.buildReverseDeps)(state_1.sheet);
    (0, dependency_1.fullRecalc)(state_1.sheet, revDeps, astCache);
    // Update all cell displays
    (0, grid_1.updateCellDisplayFromSheet)(state_1.sheet);
    // Select initial cell
    (0, grid_1.selectCell)(state_1.selection.col, state_1.selection.row);
    (0, input_1.updateFormulaBar)();
    updateCellRef();
    // Selection change handler
    function onSelectionChange(col, row) {
        updateCellRef();
        (0, input_1.updateFormulaBar)();
    }
    // Wire up selection change callback
    (0, grid_1.setSelectionChange)(onSelectionChange);
    // Cell reference display
    function updateCellRef() {
        cellRef.textContent = (0, state_1.addr)(state_1.selection.col, state_1.selection.row);
    }
    // Formula bar keydown
    formulaBar.addEventListener('keydown', (e) => {
        (0, input_1.handleFormulaBarKeydown)(e);
    });
    // Formula bar input — sync to cell on blur or enter
    formulaBar.addEventListener('input', () => {
        // If editing in-cell, sync the cell input
        const cellEl = document.querySelector('.cell.selected .cell-input');
        if (cellEl) {
            cellEl.value = formulaBar.value;
        }
    });
    // Grid keydown (delegate from grid container)
    gridContainer.addEventListener('keydown', (e) => {
        (0, input_1.handleGridKeydown)(e, state_1.sheet);
    });
    // Make grid container focusable for keyboard events
    gridContainer.setAttribute('tabindex', '0');
    gridContainer.style.outline = 'none';
    // Double-click to edit
    gridContainer.addEventListener('dblclick', (e) => {
        const td = e.target.closest('td.cell');
        if (td) {
            (0, input_1.startEdit)();
        }
    });
    // Commit handler
    function onCommit(raw) {
        // Determine if Enter was pressed (move down) or Tab (move right)
        (0, input_1.commitCell)(state_1.sheet, raw);
        (0, persistence_1.saveSheet)(state_1.sheet);
    }
    // Override commitEdit to handle Enter key behavior
    // We need to intercept the Enter key in the edit input
    const origGridContainer = gridContainer;
    origGridContainer.addEventListener('keydown', (e) => {
        if ((0, input_1.isEditing)() && e.key === 'Enter') {
            // Move down after commit
            const cell = (0, state_1.getCell)(state_1.selection.col, state_1.selection.row);
            const raw = cell ? cell.raw : '';
            // This will be handled by the input's keydown handler
        }
    });
    // Toolbar buttons
    const btnInsertRow = document.getElementById('btn-insert-row');
    const btnDeleteRow = document.getElementById('btn-delete-row');
    const btnInsertCol = document.getElementById('btn-insert-col');
    const btnDeleteCol = document.getElementById('btn-delete-col');
    if (btnInsertRow) {
        btnInsertRow.addEventListener('click', () => {
            (0, operations_1.insertRow)(state_1.sheet, state_1.selection.row);
            const revDeps = (0, dependency_1.buildReverseDeps)(state_1.sheet);
            (0, dependency_1.fullRecalc)(state_1.sheet, revDeps, astCache);
            (0, grid_1.updateCellDisplayFromSheet)(state_1.sheet);
            updateSelectionAfterGridChange();
            (0, persistence_1.saveSheet)(state_1.sheet);
        });
    }
    if (btnDeleteRow) {
        btnDeleteRow.addEventListener('click', () => {
            (0, operations_1.deleteRow)(state_1.sheet, state_1.selection.row);
            const revDeps = (0, dependency_1.buildReverseDeps)(state_1.sheet);
            (0, dependency_1.fullRecalc)(state_1.sheet, revDeps, astCache);
            (0, grid_1.updateCellDisplayFromSheet)(state_1.sheet);
            updateSelectionAfterGridChange();
            (0, persistence_1.saveSheet)(state_1.sheet);
        });
    }
    if (btnInsertCol) {
        btnInsertCol.addEventListener('click', () => {
            (0, operations_1.insertCol)(state_1.sheet, state_1.selection.col);
            const revDeps = (0, dependency_1.buildReverseDeps)(state_1.sheet);
            (0, dependency_1.fullRecalc)(state_1.sheet, revDeps, astCache);
            (0, grid_1.updateCellDisplayFromSheet)(state_1.sheet);
            updateSelectionAfterGridChange();
            (0, persistence_1.saveSheet)(state_1.sheet);
        });
    }
    if (btnDeleteCol) {
        btnDeleteCol.addEventListener('click', () => {
            (0, operations_1.deleteCol)(state_1.sheet, state_1.selection.col);
            const revDeps = (0, dependency_1.buildReverseDeps)(state_1.sheet);
            (0, dependency_1.fullRecalc)(state_1.sheet, revDeps, astCache);
            (0, grid_1.updateCellDisplayFromSheet)(state_1.sheet);
            updateSelectionAfterGridChange();
            (0, persistence_1.saveSheet)(state_1.sheet);
        });
    }
    // Right-click context menu on headers
    gridContainer.addEventListener('contextmenu', (e) => {
        const colHeader = e.target.closest('.col-header');
        const rowHeader = e.target.closest('.row-header');
        if (colHeader) {
            e.preventDefault();
            const col = parseInt(colHeader.dataset.col);
            showColContextMenu(col, e.clientX, e.clientY);
        }
        else if (rowHeader) {
            e.preventDefault();
            const row = parseInt(rowHeader.dataset.row);
            showRowContextMenu(row, e.clientX, e.clientY);
        }
    });
    // Focus the grid container so keyboard events work
    gridContainer.focus();
}
function updateSelectionAfterGridChange() {
    state_1.selection.col = Math.min(state_1.selection.col, config_1.COLS - 1);
    state_1.selection.row = Math.min(state_1.selection.row, config_1.ROWS - 1);
    (0, grid_1.selectCell)(state_1.selection.col, state_1.selection.row);
    (0, input_1.updateFormulaBar)();
}
function showColContextMenu(col, x, y) {
    // Simple context menu
    const menu = document.createElement('div');
    menu.style.cssText = `
    position: fixed; left: ${x}px; top: ${y}px;
    background: #fff; border: 1px solid #dadce0; border-radius: 4px;
    box-shadow: 0 2px 6px rgba(0,0,0,0.2); padding: 4px 0; z-index: 1000;
    min-width: 150px;
  `;
    const insertItem = createMenuItem('Insert column left', () => {
        (0, operations_1.insertCol)(state_1.sheet, col);
        recalcAndRender();
    });
    const deleteItem = createMenuItem('Delete column', () => {
        (0, operations_1.deleteCol)(state_1.sheet, col);
        recalcAndRender();
    });
    menu.appendChild(insertItem);
    menu.appendChild(deleteItem);
    document.body.appendChild(menu);
    const close = () => {
        document.body.removeChild(menu);
        document.removeEventListener('mousedown', close);
    };
    document.addEventListener('mousedown', close);
}
function showRowContextMenu(row, x, y) {
    const menu = document.createElement('div');
    menu.style.cssText = `
    position: fixed; left: ${x}px; top: ${y}px;
    background: #fff; border: 1px solid #dadce0; border-radius: 4px;
    box-shadow: 0 2px 6px rgba(0,0,0,0.2); padding: 4px 0; z-index: 1000;
    min-width: 150px;
  `;
    const insertItem = createMenuItem('Insert row above', () => {
        (0, operations_1.insertRow)(state_1.sheet, row);
        recalcAndRender();
    });
    const deleteItem = createMenuItem('Delete row', () => {
        (0, operations_1.deleteRow)(state_1.sheet, row);
        recalcAndRender();
    });
    menu.appendChild(insertItem);
    menu.appendChild(deleteItem);
    document.body.appendChild(menu);
    const close = () => {
        document.body.removeChild(menu);
        document.removeEventListener('mousedown', close);
    };
    document.addEventListener('mousedown', close);
}
function createMenuItem(label, onClick) {
    const item = document.createElement('div');
    item.textContent = label;
    item.style.cssText = `
    padding: 6px 16px; cursor: pointer; font-size: 13px; color: #202124;
  `;
    item.addEventListener('mouseenter', () => {
        item.style.background = '#f1f3f4';
    });
    item.addEventListener('mouseleave', () => {
        item.style.background = '';
    });
    item.addEventListener('click', () => {
        onClick();
        (0, persistence_1.saveSheet)(state_1.sheet);
    });
    return item;
}
function recalcAndRender() {
    const revDeps = (0, dependency_1.buildReverseDeps)(state_1.sheet);
    (0, dependency_1.fullRecalc)(state_1.sheet, revDeps, astCache);
    (0, grid_1.updateCellDisplayFromSheet)(state_1.sheet);
    updateSelectionAfterGridChange();
}
// Wire up the commit handler
(0, input_1.setOnCommit)((raw) => {
    (0, input_1.commitCell)(state_1.sheet, raw);
    (0, persistence_1.saveSheet)(state_1.sheet);
});
start();
};

// ── module: src/config.ts ──
__mods["src/config.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ERR_GENERAL = exports.ERR_DIV0 = exports.ERR_REF = exports.ERR_CYCLE = exports.COLORS = exports.HEADER_HEIGHT = exports.HEADER_WIDTH = exports.CELL_HEIGHT = exports.CELL_WIDTH = exports.ROWS = exports.COLS = void 0;
// Tunable constants
exports.COLS = 26;
exports.ROWS = 100;
exports.CELL_WIDTH = 100;
exports.CELL_HEIGHT = 24;
exports.HEADER_WIDTH = 40;
exports.HEADER_HEIGHT = 24;
// Colors
exports.COLORS = {
    bg: '#ffffff',
    gridLine: '#e0e0e0',
    headerBg: '#f8f9fa',
    headerText: '#333333',
    selectionBorder: '#1a73e8',
    selectionBg: '#e8f0fe',
    errorColor: '#d32f2f',
    formulaBarBg: '#f1f3f4',
    formulaBarBorder: '#dadce0',
    textLeft: '#000000',
    textRight: '#000000',
};
// Error strings
exports.ERR_CYCLE = '#CYCLE!';
exports.ERR_REF = '#REF!';
exports.ERR_DIV0 = '#DIV/0!';
exports.ERR_GENERAL = '#ERR!';
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.selection = exports.sheet = void 0;
exports.colName = colName;
exports.addr = addr;
exports.parseAddr = parseAddr;
exports.getCell = getCell;
exports.setCell = setCell;
exports.removeCell = removeCell;
exports.hasCell = hasCell;
exports.getSelectionAddr = getSelectionAddr;
const config_1 = require("./config");
exports.sheet = new Map();
exports.selection = { col: 0, row: 0 };
function colName(col) {
    return String.fromCharCode(65 + col);
}
function addr(col, row) {
    return colName(col) + (row + 1);
}
function parseAddr(s) {
    const m = s.match(/^([A-Z])(\d+)$/);
    if (!m)
        return null;
    const col = m[1].charCodeAt(0) - 65;
    const row = parseInt(m[2], 10) - 1;
    if (col < 0 || col >= config_1.COLS || row < 0 || row >= config_1.ROWS)
        return null;
    return { col, row };
}
function getCell(col, row) {
    return exports.sheet.get(addr(col, row));
}
function setCell(col, row, cell) {
    exports.sheet.set(addr(col, row), cell);
}
function removeCell(col, row) {
    exports.sheet.delete(addr(col, row));
}
function hasCell(col, row) {
    return exports.sheet.has(addr(col, row));
}
function getSelectionAddr() {
    return addr(exports.selection.col, exports.selection.row);
}
};

// ── 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(input) {
    const tokens = [];
    let i = 0;
    const s = input.trim();
    while (i < s.length) {
        // Skip whitespace
        if (s[i] === ' ' || s[i] === '\t') {
            i++;
            continue;
        }
        // Numbers (including decimals)
        if ((s[i] >= '0' && s[i] <= '9') || s[i] === '.') {
            let num = '';
            const hasDot = s[i] === '.';
            while (i < s.length && ((s[i] >= '0' && s[i] <= '9') || (s[i] === '.' && !hasDot))) {
                num += s[i];
                if (s[i] === '.')
                    break;
                i++;
            }
            tokens.push({ kind: 'NUMBER', value: num, pos: i - num.length });
            continue;
        }
        // Identifiers (cell refs like A1, B7) and function names (SUM, AVG, etc.)
        if (s[i] >= 'A' && s[i] <= 'Z') {
            let ident = '';
            while (i < s.length && (s[i] >= 'A' && s[i] <= 'Z')) {
                ident += s[i];
                i++;
            }
            // Check if followed by digits → cell reference
            if (i < s.length && s[i] >= '0' && s[i] <= '9') {
                let num = '';
                while (i < s.length && s[i] >= '0' && s[i] <= '9') {
                    num += s[i];
                    i++;
                }
                tokens.push({ kind: 'IDENT', value: ident + num, pos: i - (ident + num).length });
            }
            else {
                // Function name
                tokens.push({ kind: 'IDENT', value: ident, pos: i - ident.length });
            }
            continue;
        }
        // Operators and punctuation
        switch (s[i]) {
            case ':':
                tokens.push({ kind: 'COLON', value: ':', pos: i });
                break;
            case ',':
                tokens.push({ kind: 'COMMA', value: ',', pos: i });
                break;
            case '(':
                tokens.push({ kind: 'LPAREN', value: '(', pos: i });
                break;
            case ')':
                tokens.push({ kind: 'RPAREN', value: ')', pos: i });
                break;
            case '+':
                tokens.push({ kind: 'PLUS', value: '+', pos: i });
                break;
            case '-':
                tokens.push({ kind: 'MINUS', value: '-', pos: i });
                break;
            case '*':
                tokens.push({ kind: 'STAR', value: '*', pos: i });
                break;
            case '/':
                tokens.push({ kind: 'SLASH', value: '/', pos: i });
                break;
            default:
                // Unknown character — skip
                break;
        }
        i++;
    }
    tokens.push({ kind: 'EOF', value: '', pos: i });
    return tokens;
}
};

// ── module: src/parser.ts ──
__mods["src/parser.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseFormula = parseFormula;
const tokenizer_1 = require("./tokenizer");
class Parser {
    constructor(tokens) {
        this.error = null;
        this.tokens = tokens;
        this.pos = 0;
    }
    peek() {
        return this.tokens[this.pos];
    }
    consume() {
        return this.tokens[this.pos++];
    }
    expect(kind) {
        const t = this.peek();
        if (t.kind !== kind) {
            this.error = `Expected ${kind}, got ${t.kind}`;
            return t;
        }
        return this.consume();
    }
    // expr -> addExpr
    parse() {
        const node = this.parseAdd();
        if (this.error)
            return { kind: 'error', msg: this.error };
        if (this.peek().kind !== 'EOF') {
            return { kind: 'error', msg: 'Unexpected token' };
        }
        return node;
    }
    // addExpr -> mulExpr (('+' | '-') mulExpr)*
    parseAdd() {
        let left = this.parseMul();
        while (this.peek().kind === 'PLUS' || this.peek().kind === 'MINUS') {
            const op = this.consume().value;
            const right = this.parseMul();
            left = { kind: 'binary', op, left, right };
        }
        return left;
    }
    // mulExpr -> unary (('*' | '/') unary)*
    parseMul() {
        let left = this.parseUnary();
        while (this.peek().kind === 'STAR' || this.peek().kind === 'SLASH') {
            const op = this.consume().value;
            const right = this.parseUnary();
            left = { kind: 'binary', op, left, right };
        }
        return left;
    }
    // unary -> '-' unary | primary
    parseUnary() {
        if (this.peek().kind === 'MINUS') {
            this.consume();
            const operand = this.parseUnary();
            return { kind: 'unaryMinus', operand };
        }
        return this.parsePrimary();
    }
    // primary -> NUMBER | IDENT | '(' expr ')' | functionCall
    parsePrimary() {
        const t = this.peek();
        // Number literal
        if (t.kind === 'NUMBER') {
            this.consume();
            return { kind: 'number', value: parseFloat(t.value) };
        }
        // Parenthesized expression
        if (t.kind === 'LPAREN') {
            this.consume();
            const expr = this.parseAdd();
            this.expect('RPAREN');
            return expr;
        }
        // Cell reference, range, or function call
        if (t.kind === 'IDENT') {
            this.consume();
            const name = t.value;
            // Check if next is a colon -> range
            if (this.peek().kind === 'COLON') {
                this.consume();
                const endToken = this.peek();
                if (endToken.kind !== 'IDENT') {
                    this.error = 'Expected cell reference after colon';
                    return { kind: 'error', msg: this.error };
                }
                this.consume();
                const start = parseCellRef(name);
                const end = parseCellRef(endToken.value);
                if (!start || !end) {
                    return { kind: 'error', msg: 'Invalid cell reference in range' };
                }
                return {
                    kind: 'range',
                    startCol: start.col,
                    startRow: start.row,
                    endCol: end.col,
                    endRow: end.row,
                };
            }
            // Check if next is '(' -> function call
            if (this.peek().kind === 'LPAREN') {
                this.consume();
                const args = [];
                if (this.peek().kind !== 'RPAREN') {
                    args.push(this.parseAdd());
                    while (this.peek().kind === 'COMMA') {
                        this.consume();
                        args.push(this.parseAdd());
                    }
                }
                this.expect('RPAREN');
                return { kind: 'function', name: name.toUpperCase(), args };
            }
            // Plain cell reference
            const ref = parseCellRef(name);
            if (!ref) {
                return { kind: 'error', msg: 'Invalid cell reference: ' + name };
            }
            return { kind: 'cellRef', col: ref.col, row: ref.row };
        }
        this.error = 'Unexpected token: ' + t.kind;
        return { kind: 'error', msg: this.error };
    }
}
function parseCellRef(s) {
    const m = s.match(/^([A-Z])(\d+)$/);
    if (!m)
        return null;
    return {
        col: m[1].charCodeAt(0) - 65,
        row: parseInt(m[2], 10) - 1,
    };
}
function parseFormula(formula) {
    try {
        const input = formula.startsWith('=') ? formula.slice(1) : formula;
        const tokens = (0, tokenizer_1.tokenize)(input);
        const parser = new Parser(tokens);
        return parser.parse();
    }
    catch (e) {
        return { kind: 'error', msg: String(e) };
    }
}
};

// ── module: src/evaluator.ts ──
__mods["src/evaluator.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.evaluate = evaluate;
exports.toNumber = toNumber;
const config_1 = require("./config");
const state_1 = require("./state");
const parser_1 = require("./parser");
// Evaluate an AST node, collecting dependencies
function evaluate(node, ctx) {
    if (ctx.depth > 100) {
        return { value: config_1.ERR_CYCLE, error: config_1.ERR_CYCLE };
    }
    switch (node.kind) {
        case 'number':
            return { value: node.value };
        case 'cellRef': {
            const a = (0, state_1.addr)(node.col, node.row);
            ctx.deps.push(a);
            const cell = ctx.sheet.get(a);
            if (!cell) {
                return { value: 0 }; // empty cell = 0
            }
            if (cell.error) {
                return { value: cell.error, error: cell.error };
            }
            // Check for circular dependency
            if (ctx.visiting.has(a)) {
                return { value: config_1.ERR_CYCLE, error: config_1.ERR_CYCLE };
            }
            // Recursively evaluate the referenced cell
            const result = evalCell(cell, { ...ctx, depth: ctx.depth + 1 });
            return result;
        }
        case 'range': {
            const values = [];
            // Collect all cells in the range
            const sc = Math.min(node.startCol, node.endCol);
            const ec = Math.max(node.startCol, node.endCol);
            const sr = Math.min(node.startRow, node.endRow);
            const er = Math.max(node.startRow, node.endRow);
            for (let r = sr; r <= er; r++) {
                for (let c = sc; c <= ec; c++) {
                    const a = (0, state_1.addr)(c, r);
                    ctx.deps.push(a);
                    const cell = ctx.sheet.get(a);
                    if (cell) {
                        if (cell.error) {
                            return { value: cell.error, error: cell.error };
                        }
                        const result = evalCell(cell, { ...ctx, depth: ctx.depth + 1 });
                        if (result.error) {
                            return { value: result.error, error: result.error };
                        }
                        const n = toNumber(result.value);
                        if (n !== null)
                            values.push(n);
                    }
                }
            }
            return { value: values };
        }
        case 'unaryMinus': {
            const result = evaluate(node.operand, ctx);
            if (result.error)
                return result;
            const n = toNumber(result.value);
            if (n === null)
                return { value: config_1.ERR_GENERAL, error: config_1.ERR_GENERAL };
            return { value: -n };
        }
        case 'binary': {
            const left = evaluate(node.left, ctx);
            if (left.error)
                return left;
            const right = evaluate(node.right, ctx);
            if (right.error)
                return right;
            const ln = toNumber(left.value);
            const rn = toNumber(right.value);
            if (ln === null || rn === null)
                return { value: config_1.ERR_GENERAL, error: config_1.ERR_GENERAL };
            switch (node.op) {
                case '+': return { value: ln + rn };
                case '-': return { value: ln - rn };
                case '*': return { value: ln * rn };
                case '/':
                    if (rn === 0)
                        return { value: config_1.ERR_DIV0, error: config_1.ERR_DIV0 };
                    return { value: ln / rn };
            }
        }
        case 'function': {
            const fn = node.name.toUpperCase();
            const values = [];
            for (const arg of node.args) {
                const result = evaluate(arg, ctx);
                if (result.error)
                    return result;
                // Flatten arrays (from ranges) into individual numbers
                if (Array.isArray(result.value)) {
                    for (const v of result.value) {
                        const n = toNumber(v);
                        if (n !== null)
                            values.push(n);
                    }
                }
                else {
                    const n = toNumber(result.value);
                    if (n !== null)
                        values.push(n);
                }
            }
            switch (fn) {
                case 'SUM':
                    return { value: values.reduce((a, b) => a + b, 0) };
                case 'AVG':
                case 'AVERAGE':
                    return values.length > 0
                        ? { value: values.reduce((a, b) => a + b, 0) / values.length }
                        : { value: 0 };
                case 'MIN':
                    return values.length > 0 ? { value: Math.min(...values) } : { value: 0 };
                case 'MAX':
                    return values.length > 0 ? { value: Math.max(...values) } : { value: 0 };
                case 'COUNT':
                    return { value: values.length };
                default:
                    return { value: config_1.ERR_GENERAL, error: config_1.ERR_GENERAL };
            }
        }
        case 'error':
            return { value: config_1.ERR_GENERAL, error: config_1.ERR_GENERAL };
        default:
            return { value: config_1.ERR_GENERAL, error: config_1.ERR_GENERAL };
    }
}
// Evaluate a cell's formula
function evalCell(cell, ctx) {
    if (cell.error) {
        return { value: cell.error, error: cell.error };
    }
    const raw = cell.raw;
    if (typeof raw !== 'string' || !raw.startsWith('=')) {
        // Not a formula
        const n = toNumber(raw);
        if (n !== null)
            return { value: n };
        return { value: String(raw) };
    }
    // Re-evaluate the formula to detect cycles
    const ast = (0, parser_1.parseFormula)(raw);
    if (ast.kind === 'error') {
        return { value: config_1.ERR_GENERAL, error: config_1.ERR_GENERAL };
    }
    const result = evaluate(ast, { ...ctx, depth: ctx.depth + 1 });
    if (result.error)
        return { value: result.error, error: result.error };
    const n = toNumber(result.value);
    if (n !== null)
        return { value: n };
    return { value: String(result.value) };
}
function toNumber(v) {
    if (typeof v === 'number')
        return v;
    if (typeof v === 'string') {
        const n = parseFloat(v);
        if (!isNaN(n) && v.trim() !== '')
            return n;
        return null;
    }
    if (Array.isArray(v))
        return null;
    return null;
}
};

// ── module: src/dependency.ts ──
__mods["src/dependency.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.collectDeps = collectDeps;
exports.buildReverseDeps = buildReverseDeps;
exports.recomputeCell = recomputeCell;
exports.recalcFrom = recalcFrom;
exports.fullRecalc = fullRecalc;
const config_1 = require("./config");
const state_1 = require("./state");
const parser_1 = require("./parser");
const evaluator_1 = require("./evaluator");
// Parse a formula and collect all cell dependencies
function collectDeps(raw) {
    if (!raw.startsWith('='))
        return [];
    const ast = (0, parser_1.parseFormula)(raw);
    const deps = [];
    collectDepsFromNode(ast, deps);
    return deps;
}
function collectDepsFromNode(node, deps) {
    if (!node || node.kind === 'error')
        return;
    switch (node.kind) {
        case 'cellRef':
            deps.push((0, state_1.addr)(node.col, node.row));
            break;
        case 'range': {
            const sc = Math.min(node.startCol, node.endCol);
            const ec = Math.max(node.startCol, node.endCol);
            const sr = Math.min(node.startRow, node.endRow);
            const er = Math.max(node.startRow, node.endRow);
            for (let r = sr; r <= er; r++) {
                for (let c = sc; c <= ec; c++) {
                    deps.push((0, state_1.addr)(c, r));
                }
            }
            break;
        }
        case 'unaryMinus':
            collectDepsFromNode(node.operand, deps);
            break;
        case 'binary':
            collectDepsFromNode(node.left, deps);
            collectDepsFromNode(node.right, deps);
            break;
        case 'function':
            for (const arg of node.args) {
                collectDepsFromNode(arg, deps);
            }
            break;
    }
}
// Build reverse dependency map: for each cell, which cells depend on it
function buildReverseDeps(sheet) {
    const revDeps = new Map();
    for (const [cellAddr, cell] of sheet) {
        if (cell.raw.startsWith('=')) {
            const deps = collectDeps(cell.raw);
            for (const dep of deps) {
                if (!revDeps.has(dep))
                    revDeps.set(dep, new Set());
                revDeps.get(dep).add(cellAddr);
            }
        }
    }
    return revDeps;
}
// Recompute a single cell
function recomputeCell(cellAddr, sheet, revDeps, astCache) {
    const cell = sheet.get(cellAddr);
    if (!cell)
        return { raw: '', value: '' };
    const raw = cell.raw;
    // Non-formula cells just display their raw value
    if (!raw.startsWith('=')) {
        const n = (0, evaluator_1.toNumber)(raw);
        if (n !== null)
            return { raw, value: n };
        return { raw, value: raw };
    }
    // Parse formula (or get from cache)
    let ast;
    if (astCache.has(cellAddr)) {
        ast = astCache.get(cellAddr);
    }
    else {
        ast = (0, parser_1.parseFormula)(raw);
        astCache.set(cellAddr, ast);
    }
    // Check for parse error
    if (ast.kind === 'error') {
        return { raw, value: config_1.ERR_GENERAL, error: config_1.ERR_GENERAL };
    }
    // Evaluate with cycle detection
    const ctx = {
        sheet,
        deps: [],
        visiting: new Set([cellAddr]),
        depth: 0,
    };
    const result = (0, evaluator_1.evaluate)(ast, ctx);
    if (result.error) {
        return { raw, value: result.error, error: result.error };
    }
    const n = (0, evaluator_1.toNumber)(result.value);
    if (n !== null) {
        return { raw, value: n };
    }
    return { raw, value: String(result.value) };
}
// Recompute a cell and cascade to all dependents
// Returns the list of affected cell addresses
function recalcFrom(cellAddr, sheet, revDeps, astCache) {
    // BFS to find all affected cells in topological order
    const visited = new Set();
    const queue = [cellAddr];
    const order = [];
    while (queue.length > 0) {
        const current = queue.shift();
        if (visited.has(current))
            continue;
        visited.add(current);
        order.push(current);
        // Add dependents
        const deps = revDeps.get(current);
        if (deps) {
            for (const d of deps) {
                if (!visited.has(d)) {
                    queue.push(d);
                }
            }
        }
    }
    // Recompute in order
    for (const ca of order) {
        const cell = recomputeCell(ca, sheet, revDeps, astCache);
        sheet.set(ca, cell);
    }
    return order;
}
// Full recalculation of all formula cells (used on init)
function fullRecalc(sheet, revDeps, astCache) {
    // Topological sort of all cells
    const inDegree = new Map();
    const allCells = new Set(sheet.keys());
    // Initialize in-degrees
    for (const addr of allCells) {
        if (!inDegree.has(addr))
            inDegree.set(addr, 0);
    }
    // Build forward dependency graph
    const fwdDeps = new Map();
    for (const [addr, cell] of sheet) {
        if (cell.raw.startsWith('=')) {
            const deps = collectDeps(cell.raw);
            for (const dep of deps) {
                if (allCells.has(dep)) {
                    if (!fwdDeps.has(dep))
                        fwdDeps.set(dep, new Set());
                    fwdDeps.get(dep).add(addr);
                    inDegree.set(addr, (inDegree.get(addr) || 0) + 1);
                }
            }
        }
    }
    // Kahn's algorithm
    const queue = [];
    for (const [addr, deg] of inDegree) {
        if (deg === 0)
            queue.push(addr);
    }
    const order = [];
    while (queue.length > 0) {
        const current = queue.shift();
        order.push(current);
        const deps = fwdDeps.get(current);
        if (deps) {
            for (const d of deps) {
                inDegree.set(d, (inDegree.get(d) || 0) - 1);
                if (inDegree.get(d) === 0) {
                    queue.push(d);
                }
            }
        }
    }
    // Cells not in order are in cycles
    for (const addr of allCells) {
        if (!order.includes(addr)) {
            const cell = sheet.get(addr);
            if (cell && cell.raw.startsWith('=')) {
                sheet.set(addr, { raw: cell.raw, value: config_1.ERR_CYCLE, error: config_1.ERR_CYCLE });
            }
        }
    }
    // Recompute in topological order
    for (const ca of order) {
        const cell = recomputeCell(ca, sheet, revDeps, astCache);
        sheet.set(ca, cell);
    }
}
};

// ── module: src/grid.ts ──
__mods["src/grid.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setSelectionChange = setSelectionChange;
exports.initGrid = initGrid;
exports.selectCell = selectCell;
exports.updateSelection = updateSelection;
exports.updateCellDisplay = updateCellDisplay;
exports.updateCellDisplayFromSheet = updateCellDisplayFromSheet;
exports.getCellElement = getCellElement;
const config_1 = require("./config");
const state_1 = require("./state");
let tableEl = null;
let tbodyEl = null;
let onSelectionChange = null;
function setSelectionChange(fn) {
    onSelectionChange = fn;
}
function initGrid(container) {
    // Create the table
    tableEl = document.createElement('table');
    tableEl.className = 'spreadsheet-table';
    tableEl.style.borderCollapse = 'collapse';
    tableEl.style.tableLayout = 'fixed';
    // Create thead with column headers
    const thead = document.createElement('thead');
    const headerRow = document.createElement('tr');
    // Corner cell (top-left)
    const corner = document.createElement('th');
    corner.className = 'corner-header';
    corner.style.width = config_1.HEADER_WIDTH + 'px';
    corner.style.height = config_1.HEADER_HEIGHT + 'px';
    corner.style.minWidth = config_1.HEADER_WIDTH + 'px';
    corner.style.minHeight = config_1.HEADER_HEIGHT + 'px';
    headerRow.appendChild(corner);
    // Column headers
    for (let c = 0; c < config_1.COLS; c++) {
        const th = document.createElement('th');
        th.className = 'col-header';
        th.textContent = (0, state_1.colName)(c);
        th.style.width = config_1.CELL_WIDTH + 'px';
        th.style.minWidth = config_1.CELL_WIDTH + 'px';
        th.style.height = config_1.HEADER_HEIGHT + 'px';
        th.style.minHeight = config_1.HEADER_HEIGHT + 'px';
        th.dataset.col = String(c);
        headerRow.appendChild(th);
    }
    thead.appendChild(headerRow);
    tableEl.appendChild(thead);
    // Create tbody
    tbodyEl = document.createElement('tbody');
    for (let r = 0; r < config_1.ROWS; r++) {
        const tr = document.createElement('tr');
        tr.dataset.row = String(r);
        // Row header
        const th = document.createElement('td');
        th.className = 'row-header';
        th.textContent = String(r + 1);
        th.style.width = config_1.HEADER_WIDTH + 'px';
        th.style.minWidth = config_1.HEADER_WIDTH + 'px';
        th.style.height = config_1.CELL_HEIGHT + 'px';
        th.style.minHeight = config_1.CELL_HEIGHT + 'px';
        tr.appendChild(th);
        // Cells
        for (let c = 0; c < config_1.COLS; c++) {
            const td = document.createElement('td');
            td.className = 'cell';
            td.dataset.col = String(c);
            td.dataset.row = String(r);
            td.style.width = config_1.CELL_WIDTH + 'px';
            td.style.height = config_1.CELL_HEIGHT + 'px';
            tr.appendChild(td);
        }
        tbodyEl.appendChild(tr);
    }
    tableEl.appendChild(tbodyEl);
    container.appendChild(tableEl);
    // Click handler for cell selection
    tbodyEl.addEventListener('click', handleCellClick);
}
function handleCellClick(e) {
    const td = e.target.closest('td.cell');
    if (!td)
        return;
    const col = parseInt(td.dataset.col);
    const row = parseInt(td.dataset.row);
    selectCell(col, row);
}
function selectCell(col, row) {
    state_1.selection.col = col;
    state_1.selection.row = row;
    updateSelection();
    if (onSelectionChange)
        onSelectionChange(col, row);
}
function updateSelection() {
    if (!tbodyEl)
        return;
    // Remove old selection
    const old = tbodyEl.querySelector('.cell.selected');
    if (old)
        old.classList.remove('selected');
    // Add new selection
    const row = tbodyEl.querySelector(`tr[data-row="${state_1.selection.row}"]`);
    if (row) {
        const cell = row.querySelector(`td.cell[data-col="${state_1.selection.col}"]`);
        if (cell)
            cell.classList.add('selected');
    }
}
function updateCellDisplay(col, row) {
    if (!tbodyEl)
        return;
    const rowEl = tbodyEl.querySelector(`tr[data-row="${row}"]`);
    if (!rowEl)
        return;
    const cellEl = rowEl.querySelector(`td.cell[data-col="${col}"]`);
    if (!cellEl)
        return;
    const cell = (0, state_1.getCell)(col, row);
    if (!cell) {
        cellEl.textContent = '';
        cellEl.style.color = '';
        cellEl.style.textAlign = '';
        return;
    }
    const val = cell.value;
    if (typeof val === 'string' && val.startsWith('#')) {
        cellEl.textContent = val;
        cellEl.style.color = config_1.COLORS.errorColor;
        cellEl.style.textAlign = 'right';
    }
    else if (typeof val === 'number') {
        cellEl.textContent = formatNumber(val);
        cellEl.style.color = '';
        cellEl.style.textAlign = 'right';
    }
    else {
        cellEl.textContent = String(val);
        cellEl.style.color = '';
        cellEl.style.textAlign = 'left';
    }
}
function updateCellDisplayFromSheet(sheet) {
    if (!tbodyEl)
        return;
    for (const [cellAddr, cell] of sheet) {
        const m = cellAddr.match(/^([A-Z])(\d+)$/);
        if (!m)
            continue;
        const col = m[1].charCodeAt(0) - 65;
        const row = parseInt(m[2], 10) - 1;
        updateCellDisplay(col, row);
    }
}
function formatNumber(n) {
    if (Number.isInteger(n))
        return String(n);
    // Limit decimal places
    const s = n.toFixed(10);
    // Remove trailing zeros
    return s.replace(/\.?0+$/, '');
}
function getCellElement(col, row) {
    if (!tbodyEl)
        return null;
    const rowEl = tbodyEl.querySelector(`tr[data-row="${row}"]`);
    if (!rowEl)
        return null;
    return rowEl.querySelector(`td.cell[data-col="${col}"]`);
}
};

// ── module: src/operations.ts ──
__mods["src/operations.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.rewriteFormula = rewriteFormula;
exports.insertRow = insertRow;
exports.deleteRow = deleteRow;
exports.insertCol = insertCol;
exports.deleteCol = deleteCol;
const config_1 = require("./config");
const state_1 = require("./state");
// Rewrite a cell address when a row/col is inserted or deleted
function rewriteAddr(addrStr, op, index) {
    const parsed = (0, state_1.parseAddr)(addrStr);
    if (!parsed)
        return null;
    let { col, row } = parsed;
    if (op === 'insertRow' && row >= index)
        row++;
    if (op === 'deleteRow' && row > index)
        row--;
    if (op === 'insertCol' && col >= index)
        col++;
    if (op === 'deleteCol' && col > index)
        col--;
    // Out of bounds
    if (col < 0 || col >= config_1.COLS || row < 0 || row >= config_1.ROWS)
        return null;
    return (0, state_1.addr)(col, row);
}
// Rewrite all cell references in a formula string
function rewriteFormula(raw, op, index) {
    if (!raw.startsWith('='))
        return raw;
    // Find all cell references and ranges in the formula
    // Pattern: letter followed by digits (cell ref), optionally followed by : and another cell ref (range)
    const cellRefPattern = /([A-Z]\d+)(?::([A-Z]\d+))?/g;
    let result = raw;
    let hasRefError = false;
    // We need to process matches carefully since we're modifying the string
    // Use a replacement function
    result = raw.replace(cellRefPattern, (match, startRef, endRef) => {
        const newStart = rewriteAddr(startRef, op, index);
        if (!newStart) {
            hasRefError = true;
            return match;
        }
        if (endRef) {
            const newEnd = rewriteAddr(endRef, op, index);
            if (!newEnd) {
                hasRefError = true;
                return match;
            }
            return newStart + ':' + newEnd;
        }
        return newStart;
    });
    return result;
}
// Insert a row at the given index
function insertRow(sheet, rowIndex) {
    if (rowIndex < 0 || rowIndex >= config_1.ROWS)
        return;
    const newSheet = new Map(sheet);
    // Move all cells below the inserted row down by 1
    const toRemove = [];
    const toAdd = new Map();
    for (const [cellAddr, cell] of sheet) {
        const parsed = (0, state_1.parseAddr)(cellAddr);
        if (!parsed)
            continue;
        if (parsed.row >= rowIndex) {
            // Move this cell down
            const newAddr = (0, state_1.addr)(parsed.col, parsed.row + 1);
            if (parsed.row + 1 < config_1.ROWS) {
                toAdd.set(newAddr, { ...cell });
                toRemove.push(cellAddr);
            }
        }
    }
    // Rewrite formulas to account for the new row
    for (const [newAddr, cell] of toAdd) {
        cell.raw = rewriteFormula(cell.raw, 'insertRow', rowIndex);
    }
    // Also rewrite formulas in cells above the inserted row
    for (const [cellAddr, cell] of sheet) {
        const parsed = (0, state_1.parseAddr)(cellAddr);
        if (!parsed || parsed.row >= rowIndex)
            continue;
        if (cell.raw.startsWith('=')) {
            cell.raw = rewriteFormula(cell.raw, 'insertRow', rowIndex);
        }
    }
    // Apply changes
    for (const addr of toRemove) {
        newSheet.delete(addr);
    }
    for (const [addr, cell] of toAdd) {
        newSheet.set(addr, cell);
    }
    sheet.clear();
    for (const [addr, cell] of newSheet) {
        sheet.set(addr, cell);
    }
}
// Delete a row at the given index
function deleteRow(sheet, rowIndex) {
    if (rowIndex < 0 || rowIndex >= config_1.ROWS)
        return;
    const newSheet = new Map(sheet);
    // Remove all cells in the deleted row
    for (const cellAddr of sheet.keys()) {
        const parsed = (0, state_1.parseAddr)(cellAddr);
        if (parsed && parsed.row === rowIndex) {
            newSheet.delete(cellAddr);
        }
    }
    // Move all cells below the deleted row up by 1
    const toRemove = [];
    const toAdd = new Map();
    for (const [cellAddr, cell] of newSheet) {
        const parsed = (0, state_1.parseAddr)(cellAddr);
        if (!parsed)
            continue;
        if (parsed.row > rowIndex) {
            const newAddr = (0, state_1.addr)(parsed.col, parsed.row - 1);
            toAdd.set(newAddr, { ...cell });
            toRemove.push(cellAddr);
        }
    }
    // Rewrite formulas
    for (const [newAddr, cell] of toAdd) {
        cell.raw = rewriteFormula(cell.raw, 'deleteRow', rowIndex);
    }
    // Also rewrite formulas in cells above the deleted row
    for (const [cellAddr, cell] of newSheet) {
        const parsed = (0, state_1.parseAddr)(cellAddr);
        if (!parsed || parsed.row > rowIndex)
            continue;
        if (cell.raw.startsWith('=')) {
            cell.raw = rewriteFormula(cell.raw, 'deleteRow', rowIndex);
        }
    }
    // Apply changes
    for (const addr of toRemove) {
        newSheet.delete(addr);
    }
    for (const [addr, cell] of toAdd) {
        newSheet.set(addr, cell);
    }
    sheet.clear();
    for (const [addr, cell] of newSheet) {
        sheet.set(addr, cell);
    }
}
// Insert a column at the given index
function insertCol(sheet, colIndex) {
    if (colIndex < 0 || colIndex >= config_1.COLS)
        return;
    const newSheet = new Map(sheet);
    const toRemove = [];
    const toAdd = new Map();
    for (const [cellAddr, cell] of sheet) {
        const parsed = (0, state_1.parseAddr)(cellAddr);
        if (!parsed)
            continue;
        if (parsed.col >= colIndex) {
            const newAddr = (0, state_1.addr)(parsed.col + 1, parsed.row);
            if (parsed.col + 1 < config_1.COLS) {
                toAdd.set(newAddr, { ...cell });
                toRemove.push(cellAddr);
            }
        }
    }
    for (const [newAddr, cell] of toAdd) {
        cell.raw = rewriteFormula(cell.raw, 'insertCol', colIndex);
    }
    for (const [cellAddr, cell] of sheet) {
        const parsed = (0, state_1.parseAddr)(cellAddr);
        if (!parsed || parsed.col >= colIndex)
            continue;
        if (cell.raw.startsWith('=')) {
            cell.raw = rewriteFormula(cell.raw, 'insertCol', colIndex);
        }
    }
    for (const a of toRemove)
        newSheet.delete(a);
    for (const [a, cell] of toAdd)
        newSheet.set(a, cell);
    sheet.clear();
    for (const [a, cell] of newSheet)
        sheet.set(a, cell);
}
// Delete a column at the given index
function deleteCol(sheet, colIndex) {
    if (colIndex < 0 || colIndex >= config_1.COLS)
        return;
    const newSheet = new Map(sheet);
    for (const cellAddr of sheet.keys()) {
        const parsed = (0, state_1.parseAddr)(cellAddr);
        if (parsed && parsed.col === colIndex) {
            newSheet.delete(cellAddr);
        }
    }
    const toRemove = [];
    const toAdd = new Map();
    for (const [cellAddr, cell] of newSheet) {
        const parsed = (0, state_1.parseAddr)(cellAddr);
        if (!parsed)
            continue;
        if (parsed.col > colIndex) {
            const newAddr = (0, state_1.addr)(parsed.col - 1, parsed.row);
            toAdd.set(newAddr, { ...cell });
            toRemove.push(cellAddr);
        }
    }
    for (const [newAddr, cell] of toAdd) {
        cell.raw = rewriteFormula(cell.raw, 'deleteCol', colIndex);
    }
    for (const [cellAddr, cell] of newSheet) {
        const parsed = (0, state_1.parseAddr)(cellAddr);
        if (!parsed || parsed.col > colIndex)
            continue;
        if (cell.raw.startsWith('=')) {
            cell.raw = rewriteFormula(cell.raw, 'deleteCol', colIndex);
        }
    }
    for (const a of toRemove)
        newSheet.delete(a);
    for (const [a, cell] of toAdd)
        newSheet.set(a, cell);
    sheet.clear();
    for (const [a, cell] of newSheet)
        sheet.set(a, cell);
}
};

// ── module: src/persistence.ts ──
__mods["src/persistence.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.saveSheet = saveSheet;
exports.loadSheet = loadSheet;
const STORAGE_KEY = 'spreadsheet_data';
function saveSheet(sheet) {
    try {
        const data = {};
        for (const [addr, cell] of sheet) {
            data[addr] = { raw: cell.raw };
        }
        localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
    }
    catch (e) {
        // Storage might be unavailable
    }
}
function loadSheet(sheet) {
    try {
        const raw = localStorage.getItem(STORAGE_KEY);
        if (!raw)
            return false;
        const data = JSON.parse(raw);
        sheet.clear();
        for (const [addr, cellData] of Object.entries(data)) {
            sheet.set(addr, { raw: cellData.raw, value: '' });
        }
        return true;
    }
    catch (e) {
        return false;
    }
}
};

// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setFormulaBarInput = setFormulaBarInput;
exports.setOnCommit = setOnCommit;
exports.setOnFormulaBarChange = setOnFormulaBarChange;
exports.getFormulaBar = getFormulaBar;
exports.updateFormulaBar = updateFormulaBar;
exports.startEdit = startEdit;
exports.commitEdit = commitEdit;
exports.cancelEdit = cancelEdit;
exports.isEditing = isEditing;
exports.handleFormulaBarKeydown = handleFormulaBarKeydown;
exports.handleGridKeydown = handleGridKeydown;
exports.clearCell = clearCell;
exports.commitCell = commitCell;
exports.moveSelection = moveSelection;
const config_1 = require("./config");
const state_1 = require("./state");
const grid_1 = require("./grid");
const dependency_1 = require("./dependency");
let formulaBarInput = null;
let editingCell = null;
let editInput = null;
let onCommit = null;
let onFormulaBarChange = null;
function setFormulaBarInput(el) {
    formulaBarInput = el;
}
function setOnCommit(fn) {
    onCommit = fn;
}
function setOnFormulaBarChange(fn) {
    onFormulaBarChange = fn;
}
function getFormulaBar() {
    return formulaBarInput;
}
// Update formula bar to show the raw content of the selected cell
function updateFormulaBar() {
    if (!formulaBarInput)
        return;
    const cell = (0, state_1.getCell)(state_1.selection.col, state_1.selection.row);
    formulaBarInput.value = cell ? cell.raw : '';
}
// Enter edit mode for the selected cell
function startEdit() {
    const cellEl = (0, grid_1.getCellElement)(state_1.selection.col, state_1.selection.row);
    if (!cellEl || !formulaBarInput)
        return;
    // Create an input element inside the cell
    cellEl.textContent = '';
    editInput = document.createElement('input');
    editInput.type = 'text';
    editInput.className = 'cell-input';
    editInput.style.width = '100%';
    editInput.style.height = '100%';
    editInput.style.border = 'none';
    editInput.style.outline = 'none';
    editInput.style.padding = '2px 4px';
    editInput.style.font = 'inherit';
    editInput.style.boxSizing = 'border-box';
    const cell = (0, state_1.getCell)(state_1.selection.col, state_1.selection.row);
    editInput.value = cell ? cell.raw : '';
    cellEl.appendChild(editInput);
    editInput.focus();
    editInput.select();
    // Sync formula bar
    formulaBarInput.value = editInput.value;
    // Handle input events
    editInput.addEventListener('input', () => {
        if (formulaBarInput && editInput)
            formulaBarInput.value = editInput.value;
    });
    // Handle Enter/Escape
    editInput.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            if (editInput)
                commitEdit(editInput.value);
        }
        else if (e.key === 'Escape') {
            e.preventDefault();
            cancelEdit();
        }
    });
    editingCell = cellEl;
}
// Commit the current edit
function commitEdit(raw) {
    if (onCommit) {
        onCommit(raw);
    }
    finishEdit();
}
// Cancel the current edit
function cancelEdit() {
    finishEdit();
}
// Finish editing (remove input element, restore display)
function finishEdit() {
    if (editInput && editingCell) {
        editingCell.removeChild(editInput);
        editInput = null;
        editingCell = null;
    }
    (0, grid_1.updateCellDisplay)(state_1.selection.col, state_1.selection.row);
    updateFormulaBar();
}
// Check if we're currently editing
function isEditing() {
    return editInput !== null;
}
// Handle keyboard input on the formula bar
function handleFormulaBarKeydown(e) {
    if (e.key === 'Enter') {
        e.preventDefault();
        if (formulaBarInput) {
            commitEdit(formulaBarInput.value);
        }
    }
    else if (e.key === 'Escape') {
        e.preventDefault();
        if (formulaBarInput) {
            updateFormulaBar();
        }
    }
}
// Handle keyboard input on the grid
function handleGridKeydown(e, sheet) {
    // If editing, let the input handle it
    if (isEditing())
        return;
    const key = e.key;
    // Navigation
    if (key === 'ArrowUp' || key === 'ArrowDown' || key === 'ArrowLeft' || key === 'ArrowRight') {
        e.preventDefault();
        let { col, row } = state_1.selection;
        if (key === 'ArrowUp')
            row = Math.max(0, row - 1);
        if (key === 'ArrowDown')
            row = Math.min(config_1.ROWS - 1, row + 1);
        if (key === 'ArrowLeft')
            col = Math.max(0, col - 1);
        if (key === 'ArrowRight')
            col = Math.min(config_1.COLS - 1, col + 1);
        state_1.selection.col = col;
        state_1.selection.row = row;
        (0, grid_1.selectCell)(col, row);
        updateFormulaBar();
        return;
    }
    // Delete key
    if (key === 'Delete' || key === 'Backspace') {
        e.preventDefault();
        clearCell(sheet);
        return;
    }
    // Tab moves right
    if (key === 'Tab') {
        e.preventDefault();
        let col = Math.min(config_1.COLS - 1, state_1.selection.col + 1);
        state_1.selection.col = col;
        (0, grid_1.selectCell)(col, state_1.selection.row);
        updateFormulaBar();
        return;
    }
    // Any other printable key starts editing
    if (key.length === 1 && !e.ctrlKey && !e.metaKey) {
        e.preventDefault();
        startEditWithInitial(key);
    }
}
// Start editing with an initial character
function startEditWithInitial(ch) {
    const cellEl = (0, grid_1.getCellElement)(state_1.selection.col, state_1.selection.row);
    if (!cellEl || !formulaBarInput)
        return;
    cellEl.textContent = '';
    editInput = document.createElement('input');
    editInput.type = 'text';
    editInput.className = 'cell-input';
    editInput.style.width = '100%';
    editInput.style.height = '100%';
    editInput.style.border = 'none';
    editInput.style.outline = 'none';
    editInput.style.padding = '2px 4px';
    editInput.style.font = 'inherit';
    editInput.style.boxSizing = 'border-box';
    editInput.value = ch;
    cellEl.appendChild(editInput);
    editInput.focus();
    formulaBarInput.value = ch;
    editInput.addEventListener('input', () => {
        if (formulaBarInput && editInput)
            formulaBarInput.value = editInput.value;
    });
    editInput.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            if (editInput)
                commitEdit(editInput.value);
        }
        else if (e.key === 'Escape') {
            e.preventDefault();
            cancelEdit();
        }
    });
    editingCell = cellEl;
}
// Clear the selected cell
function clearCell(sheet) {
    const cellAddr = (0, state_1.getSelectionAddr)();
    const cell = sheet.get(cellAddr);
    if (!cell)
        return;
    // Remove the cell
    sheet.delete(cellAddr);
    (0, grid_1.updateCellDisplay)(state_1.selection.col, state_1.selection.row);
    // Recalculate dependents
    const revDeps = (0, dependency_1.buildReverseDeps)(sheet);
    (0, dependency_1.recalcFrom)(cellAddr, sheet, revDeps, new Map());
    updateFormulaBar();
}
// Commit a cell value
function commitCell(sheet, raw) {
    const cellAddr = (0, state_1.getSelectionAddr)();
    // Build AST cache
    const astCache = new Map();
    // Set the cell
    if (raw.trim() === '') {
        sheet.delete(cellAddr);
    }
    else {
        const cell = { raw, value: '' };
        sheet.set(cellAddr, cell);
    }
    // Recalculate and track which cells were affected
    const revDeps = (0, dependency_1.buildReverseDeps)(sheet);
    const affectedCells = (0, dependency_1.recalcFrom)(cellAddr, sheet, revDeps, astCache);
    // Update display for all affected cells
    for (const addr of affectedCells) {
        const m = addr.match(/^([A-Z])(\d+)$/);
        if (m) {
            const col = m[1].charCodeAt(0) - 65;
            const row = parseInt(m[2], 10) - 1;
            (0, grid_1.updateCellDisplay)(col, row);
        }
    }
    updateFormulaBar();
}
// Move selection after commit
function moveSelection(direction) {
    if (direction === 'down') {
        state_1.selection.row = Math.min(config_1.ROWS - 1, state_1.selection.row + 1);
    }
    else {
        state_1.selection.col = Math.min(config_1.COLS - 1, state_1.selection.col + 1);
    }
    (0, grid_1.selectCell)(state_1.selection.col, state_1.selection.row);
    updateFormulaBar();
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.6-27b","provider":"lmstudio","persona":"typescript","sessionId":"71271cc6-cdee-470c-9f33-830f172461b0","tokensIn":5739720,"tokensOut":64214,"tokensTotal":5803934,"cost":0,"turns":124,"toolCalls":123,"failedToolCalls":3,"timestamp":"2026-08-01T07:33:50.218Z"} -->