← Spreadsheet results

Spreadsheet

North Mini Code 1.0 GGUF · 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 32,569 bytes · SHA-256 02f243756986
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spreadsheet</title>
<style>
</style>
</head>
<body>
<div id="app"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/parser.ts":{"./types":"src/types.ts"},"src/spreadsheet.ts":{"./types":"src/types.ts","./parser":"src/parser.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";
// Entry point. Runs after the DOM is parsed and after any classic
// <script> imports, so document queries and CDN globals are available.
// Import your other modules from here.
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = start;
function start() {
    // Replace this with the real startup sequence.
}
start();
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ErrorType = void 0;
var ErrorType;
(function (ErrorType) {
    ErrorType["REF"] = "#REF!";
    ErrorType["DIV0"] = "#DIV/0!";
    ErrorType["ERR"] = "#ERR!";
    ErrorType["CYCLE"] = "#CYCLE!";
})(ErrorType || (exports.ErrorType = ErrorType = {}));
};

// ── module: src/parser.ts ──
__mods["src/parser.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.tokenize = tokenize;
exports.parse = parse;
exports.evaluateAST = evaluateAST;
exports.parseFormula = parseFormula;
// Tokenizes a formula string into tokens.
// Handles numbers, identifiers (references, functions, range), operators, parentheses, etc.
const types_1 = require("./types");
function tokenize(input) {
    const tokens = [];
    let pos = 0;
    const length = input.length;
    const isAlpha = (c) => /[A-Za-z]/.test(c);
    const isDigit = (c) => /[0-9]/.test(c);
    while (pos < length) {
        const c = input[pos];
        if (c === ' ') {
            pos++;
            continue;
        }
        if (isDigit(c)) {
            let num = '';
            while (pos < length && isDigit(input[pos])) {
                num += input[pos];
                pos++;
            }
            if (pos < length && input[pos] === '.') {
                num += '.';
                pos++;
                while (pos < length && isDigit(input[pos])) {
                    num += input[pos];
                    pos++;
                }
            }
            tokens.push({ type: 'NUMBER', value: parseFloat(num) });
            continue;
        }
        if (isAlpha(c)) {
            let name = '';
            while (pos < length && (isAlpha(input[pos]) || isDigit(input[pos]))) {
                name += input[pos];
                pos++;
            }
            // Distinguish references (single letter + number) from functions (all caps)
            const isRef = name.length > 1 && isNaN(parseInt(name[name.length - 1])) && /^[A-Z][0-9]+$/.test(name);
            const isFunc = /^[A-Z]{3,}$/.test(name.toUpperCase());
            if (isRef) {
                tokens.push({ type: 'REF', value: name });
            }
            else if (isFunc) {
                tokens.push({ type: 'FUNC', value: name.toUpperCase() });
            }
            else {
                // fallback identifier (treated as literal text)
                tokens.push({ type: 'TEXT', value: name });
            }
            continue;
        }
        if (c === ',') {
            tokens.push({ type: 'COMMA' });
            pos++;
            continue;
        }
        if (c === '=') {
            tokens.push({ type: 'EQUALS' });
            pos++;
            continue;
        }
        if (c === ':' && tokens.length > 0 && tokens[tokens.length - 1].type === 'REF') {
            // Range token starts with REF; we'll handle range detection later
            // For now, push colon as part of REF token and handle in parser
            // Simpler: treat colon as separate token and parser will combine REF + ':' + REF
            tokens.pop(); // remove last REF token
            tokens.push({ type: 'RANGE', start: tokens[tokens.length - 1].value, end: '' });
            pos++;
            continue;
        }
        if (c === '(') {
            tokens.push({ type: 'PAREN_OPEN' });
            pos++;
            continue;
        }
        if (c === ')') {
            tokens.push({ type: 'PAREN_CLOSE' });
            pos++;
            continue;
        }
        if ('+-*/'.includes(c)) {
            tokens.push({ type: 'OP', value: c });
            pos++;
            continue;
        }
        // If unknown character, treat as TEXT literal
        let text = '';
        while (pos < length && !/[,=():*/+-\s]/.test(input[pos])) {
            text += input[pos];
            pos++;
        }
        if (text.length > 0) {
            tokens.push({ type: 'TEXT', value: text });
        }
    }
    tokens.push({ type: 'EOF' });
    return tokens;
}
function isReferenceToken(token) {
    return token.type === 'REF' || token.type === 'RANGE';
}
function parse(tokens) {
    let current = 0;
    function peek() {
        return tokens[current];
    }
    function consume(type) {
        const token = peek();
        if (type !== undefined && token.type !== type) {
            throw new Error(`Expected ${type} but got ${token.type}`);
        }
        current++;
        return token;
    }
    function parseExpression() {
        return parseAdditive();
    }
    function parseAdditive() {
        let left = parseMultiplicative();
        while (peek().type === 'OP') {
            const op = peek().value;
            if (op !== '+' && op !== '-')
                throw new Error('Unexpected operator');
            consume('OP');
            const right = parseMultiplicative();
            left = { type: 'binary', op, left, right };
        }
        return left;
    }
    function parseMultiplicative() {
        let left = parseUnary();
        while (peek().type === 'OP') {
            const op = peek().value;
            if (op !== '*' && op !== '/')
                throw new Error('Unexpected operator');
            consume('OP');
            const right = parseUnary();
            left = { type: 'binary', op, left, right };
        }
        return left;
    }
    function parseUnary() {
        if (peek().type === 'OP' && (peek().value === '+' || peek().value === '-')) {
            const op = peek().value;
            consume('OP');
            const operand = parseUnary();
            return { type: 'unary', op, operand };
        }
        return parsePrimary();
    }
    function parsePrimary() {
        const token = peek();
        if (token.type === 'NUMBER') {
            consume('NUMBER');
            return { type: 'literal', value: token.value };
        }
        if (token.type === 'TEXT') {
            consume('TEXT');
            return { type: 'literal', value: token.value };
        }
        if (token.type === 'REF') {
            consume('REF');
            return { type: 'reference', address: token.value };
        }
        if (token.type === 'RANGE') {
            // For now, treat RANGE as a single token; need to parse start/end
            // This is a simplification; real parsing should handle A1:A10
            // We'll implement later
            consume('RANGE');
            // For now, just treat as reference to start address
            return { type: 'reference', address: token.value };
        }
        if (token.type === 'FUNC') {
            consume('FUNC');
            consume('PAREN_OPEN');
            const args = [];
            if (peek().type !== 'PAREN_CLOSE') {
                while (true) {
                    const arg = parseExpression();
                    args.push(arg);
                    if (peek().type === 'COMMA') {
                        consume('COMMA');
                        continue;
                    }
                    break;
                }
            }
            consume('PAREN_CLOSE');
            return { type: 'function', name: token.value, args };
        }
        if (token.type === 'PAREN_OPEN') {
            consume('PAREN_OPEN');
            const expr = parseExpression();
            consume('PAREN_CLOSE');
            return expr;
        }
        throw new Error(`Unexpected token: ${token.type}`);
    }
    const ast = parseExpression();
    if (peek().type !== 'EOF') {
        throw new Error('Unexpected extra tokens');
    }
    return ast;
}
function evaluateAST(node, sheet, address) {
    try {
        if (node.type === 'binary') {
            const left = evaluateAST(node.left, sheet, address);
            if (left.error)
                return left;
            const right = evaluateAST(node.right, sheet, address);
            if (right.error)
                return right;
            if (typeof left.value === 'number' && typeof right.value === 'number') {
                switch (node.op) {
                    case '+': return { value: left.value + right.value };
                    case '-': return { value: left.value - right.value };
                    case '*': return { value: left.value * right.value };
                    case '/': {
                        if (right.value === 0)
                            return { value: 0, error: types_1.ErrorType.DIV0 };
                        return { value: left.value / right.value };
                    }
                }
            }
            return { value: null, error: types_1.ErrorType.ERR };
        }
        if (node.type === 'unary') {
            const operand = evaluateAST(node.operand, sheet, address);
            if (operand.error)
                return operand;
            if (typeof operand.value === 'number') {
                if (node.op === '-')
                    return { value: -operand.value };
                return { value: operand.value };
            }
            return { value: null, error: types_1.ErrorType.ERR };
        }
        if (node.type === 'literal') {
            return { value: node.value };
        }
        if (node.type === 'reference') {
            const ref = sheet.cells.get(node.address);
            if (!ref)
                return { value: null, error: types_1.ErrorType.REF };
            return { value: ref.value !== null && !ref.error ? ref.value : null, error: ref.error || undefined };
        }
        if (node.type === 'function') {
            const results = [];
            for (const arg of node.args) {
                const res = evaluateAST(arg, sheet, address);
                if (res.error)
                    return res;
                if (typeof res.value === 'number') {
                    results.push(res.value);
                }
                else {
                    results.push(null);
                }
            }
            switch (node.name) {
                case 'SUM':
                    let sum = 0;
                    for (const v of results) {
                        if (v !== null)
                            sum += v;
                    }
                    return { value: sum };
                case 'AVG':
                case 'AVERAGE':
                    const nonNull = results.filter(v => v !== null);
                    const avg = nonNull.reduce((acc, v) => acc + v, 0) / Math.max(nonNull.length, 1);
                    return { value: avg };
                case 'MIN':
                    const min = Math.min(...results.filter(v => v !== null));
                    return { value: min };
                case 'MAX':
                    const max = Math.max(...results.filter(v => v !== null));
                    return { value: max };
                case 'COUNT':
                    return { value: results.filter(v => v !== null).length };
                default:
                    return { value: null, error: types_1.ErrorType.ERR };
            }
        }
        throw new Error('Unknown AST node type');
    }
    catch (e) {
        return { value: null, error: types_1.ErrorType.ERR };
    }
}
function parseFormula(formula) {
    const tokens = tokenize(formula);
    return parse(tokens);
}
};

// ── module: src/spreadsheet.ts ──
__mods["src/spreadsheet.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Spreadsheet = void 0;
const types_1 = require("./types");
const parser_1 = require("./parser");
function addressToString(addr) {
    const col = String.fromCharCode(65 + addr.col);
    return col + addr.row;
}
function parseAddress(str) {
    const match = /^([A-Z])(\d+)$/.exec(str);
    if (!match)
        throw new Error('Invalid address format');
    return { col: match[1].charCodeAt(0) - 65, row: parseInt(match[2], 10) };
}
function addressFromString(str) {
    const match = /^([A-Z])(\d+)$/.exec(str);
    if (!match)
        throw new Error('Invalid address format');
    return { col: match[1].charCodeAt(0) - 65, row: parseInt(match[2], 10) };
}
function isValidAddress(str) {
    return /^[A-Z]\d+$/.test(str);
}
function getColumnLetter(col) {
    return String.fromCharCode(65 + col);
}
function resolveRange(start, end, sheet) {
    const startAddr = parseAddress(start);
    const endAddr = parseAddress(end);
    const cells = [];
    for (let c = startAddr.col; c <= endAddr.col; c++) {
        for (let r = startAddr.row; r <= endAddr.row; r++) {
            const addr = addressToString({ col: c, row: r });
            const cell = sheet.cells.get(addr);
            if (cell)
                cells.push(cell);
        }
    }
    return cells;
}
function extractReferences(node, sheet) {
    if (!node)
        return new Set();
    if (node.type === 'reference') {
        return new Set([node.address]);
    }
    if (node.type === 'range') {
        const cells = resolveRange(node.start, node.end, sheet);
        const refs = new Set();
        for (const cell of cells) {
            refs.add(cell.address);
        }
        return refs;
    }
    const refs = new Set();
    if (node.type === 'binary' || node.type === 'unary') {
        const leftRefs = extractReferences(node.left, sheet);
        for (const ref of leftRefs)
            refs.add(ref);
        const rightRefs = extractReferences(node.right, sheet);
        for (const ref of rightRefs)
            refs.add(ref);
    }
    if (node.type === 'function') {
        for (const arg of node.args) {
            const argRefs = extractReferences(arg, sheet);
            for (const ref of argRefs)
                refs.add(ref);
        }
    }
    return refs;
}
function topologicalSort(sheet) {
    const visited = new Set();
    const temp = new Set();
    const order = [];
    const cycle = [];
    function visit(addr) {
        if (temp.has(addr)) {
            cycle.push(addr);
            return;
        }
        if (visited.has(addr))
            return;
        temp.add(addr);
        const cell = sheet.cells.get(addr);
        if (!cell) {
            temp.delete(addr);
            return;
        }
        for (const dep of cell.dependencies) {
            visit(dep);
        }
        temp.delete(addr);
        visited.add(addr);
        order.push(addr);
    }
    for (const addr of sheet.cells.keys()) {
        if (!visited.has(addr)) {
            visit(addr);
        }
    }
    return cycle.length > 0 ? cycle.map(() => types_1.ErrorType.CYCLE) : order;
}
function computeCell(cell, sheet) {
    if (!cell.formula) {
        return { value: cell.raw === '' ? null : cell.raw };
    }
    if (cell.error) {
        return { value: null, error: cell.error };
    }
    try {
        const ast = (0, parser_1.parseFormula)(cell.raw.slice(1)); // strip '='
        const result = (0, parser_1.evaluateAST)(ast, sheet, cell.address);
        return result;
    }
    catch (e) {
        return { value: null, error: types_1.ErrorType.ERR };
    }
}
class Spreadsheet {
    constructor(container, config = {}) {
        this.selected = null;
        this.editing = false;
        this.formulaBar = null;
        this.grid = null;
        this.cellsCache = new Map();
        this.config = {
            cols: 26,
            rows: 100,
            cellWidth: 120,
            cellHeight: 30,
            headerHeight: 40,
            ...config,
        };
        this.sheet = { cells: new Map(), size: { cols: this.config.cols, rows: this.config.rows } };
        this.initGrid(container);
        this.initFormulaBar(container);
        this.initEventListeners();
        this.renderGrid();
    }
    initGrid(container) {
        this.grid = document.createElement('div');
        this.grid.style.position = 'absolute';
        this.grid.style.top = `${this.config.headerHeight}px`;
        this.grid.style.left = '0';
        this.grid.style.width = `${this.config.cols * this.config.cellWidth}px`;
        this.grid.style.height = `${this.config.rows * this.config.cellHeight}px`;
        this.grid.style.border = '1px solid #ccc';
        this.grid.style.display = 'grid';
        this.grid.style.gridTemplateColumns = `repeat(${this.config.cols}, ${this.config.cellWidth}px)`;
        this.grid.style.gridTemplateRows = `repeat(${this.config.rows}, ${this.config.cellHeight}px)`;
        container.appendChild(this.grid);
    }
    initFormulaBar(container) {
        this.formulaBar = document.createElement('div');
        this.formulaBar.style.height = `${this.config.headerHeight}px`;
        this.formulaBar.style.borderBottom = '1px solid #ccc';
        this.formulaBar.style.display = 'flex';
        this.formulaBar.style.alignItems = 'center';
        this.formulaBar.style.padding = '5px';
        this.formulaBar.style.backgroundColor = '#f8f9fa';
        container.prepend(this.formulaBar);
    }
    initEventListeners() {
        document.addEventListener('keydown', (e) => this.handleKeyDown(e));
        window.addEventListener('beforeunload', () => this.save());
    }
    handleKeyDown(e) {
        if (!this.selected)
            return;
        if (e.key === 'Enter' || e.key === 'Tab') {
            e.preventDefault();
            this.moveSelection(e.key === 'Enter' ? 0 : 1, 0);
            return;
        }
        if (e.key === 'ArrowUp') {
            e.preventDefault();
            this.moveSelection(0, -1);
        }
        else if (e.key === 'ArrowDown') {
            e.preventDefault();
            this.moveSelection(0, 1);
        }
        else if (e.key === 'ArrowLeft') {
            e.preventDefault();
            this.moveSelection(-1, 0);
        }
        else if (e.key === 'ArrowRight') {
            e.preventDefault();
            this.moveSelection(1, 0);
        }
        else if (e.key === 'Delete') {
            e.preventDefault();
            this.clearCell();
        }
        else if (e.key.length === 1 || e.key === 'Backspace') {
            this.enterEditMode();
        }
    }
    moveSelection(dCol, dRow) {
        if (!this.selected)
            return;
        let newCol = this.selected.col + dCol;
        let newRow = this.selected.row + dRow;
        newCol = Math.max(0, Math.min(newCol, this.config.cols - 1));
        newRow = Math.max(1, Math.min(newRow, this.config.rows));
        this.setSelected({ col: newCol, row: newRow });
    }
    setSelected(addr) {
        this.selected = addr;
        this.renderFormulaBar();
        this.updateGridSelection();
    }
    renderFormulaBar() {
        if (!this.formulaBar)
            return;
        this.formulaBar.innerHTML = '';
        if (this.selected) {
            const ref = addressToString(this.selected);
            const refEl = document.createElement('div');
            refEl.textContent = ref;
            refEl.style.fontWeight = 'bold';
            refEl.style.marginRight = '10px';
            this.formulaBar.appendChild(refEl);
            const input = document.createElement('input');
            input.type = 'text';
            input.value = this.sheet.cells.get(addressToString(this.selected))?.raw || '';
            input.style.flex = '1';
            input.addEventListener('change', (e) => this.setCellRaw(addressToString(this.selected), e.target.value));
            this.formulaBar.appendChild(input);
        }
    }
    updateGridSelection() {
        this.cellsCache.forEach((el, addr) => {
            const selected = this.selected && addr === addressToString(this.selected);
            el.style.backgroundColor = selected ? '#e3f2fd' : '';
            el.style.border = selected ? '2px solid #2196f3' : '1px solid #ddd';
        });
    }
    renderGrid() {
        if (!this.grid)
            return;
        this.grid.innerHTML = '';
        this.cellsCache.clear();
        for (let r = 1; r <= this.config.rows; r++) {
            for (let c = 0; c < this.config.cols; c++) {
                const addr = addressToString({ col: c, row: r });
                const cell = this.sheet.cells.get(addr);
                const cellEl = document.createElement('div');
                cellEl.style.border = '1px solid #ddd';
                cellEl.style.display = 'flex';
                cellEl.style.alignItems = 'center';
                cellEl.style.justifyContent = 'center';
                cellEl.style.fontSize = '12px';
                cellEl.style.cursor = 'pointer';
                if (cell) {
                    if (cell.value !== null) {
                        cellEl.textContent = String(cell.value);
                        cellEl.style.textAlign = 'right';
                    }
                    else if (cell.error) {
                        cellEl.textContent = cell.error;
                        cellEl.style.color = '#d32f2f';
                    }
                    else {
                        cellEl.textContent = cell.raw || '';
                        cellEl.style.textAlign = 'left';
                    }
                }
                else {
                    cellEl.textContent = '';
                }
                cellEl.addEventListener('click', () => this.setSelected({ col: c, row: r }));
                cellEl.addEventListener('dblclick', () => this.enterEditMode());
                this.cellsCache.set(addr, cellEl);
                this.grid.appendChild(cellEl);
            }
        }
    }
    enterEditMode() {
        if (!this.selected)
            return;
        this.editing = true;
        const cellEl = this.cellsCache.get(addressToString(this.selected));
        if (cellEl)
            cellEl.contentEditable = 'true';
        cellEl?.focus();
    }
    setCellRaw(address, raw) {
        let cell = this.sheet.cells.get(address);
        const wasFormula = cell?.formula;
        const oldDeps = new Set(cell?.dependencies || []);
        if (!cell) {
            const addr = parseAddress(address);
            cell = {
                address,
                raw,
                value: null,
                formula: raw.startsWith('='),
                error: null,
                dependencies: new Set(),
                dependents: new Set(),
            };
            this.sheet.cells.set(address, cell);
        }
        else {
            cell.raw = raw;
            cell.formula = raw.startsWith('=');
            cell.error = null;
        }
        const newDeps = new Set();
        if (cell.formula) {
            try {
                const ast = (0, parser_1.parseFormula)(raw.slice(1));
                newDeps.add(...extractReferences(ast, this.sheet));
            }
            catch (e) {
                cell.error = types_1.ErrorType.ERR;
            }
        }
        cell.dependencies = newDeps;
        for (const oldDep of oldDeps) {
            if (!newDeps.has(oldDep)) {
                const oldCell = this.sheet.cells.get(oldDep);
                if (oldCell)
                    oldCell.dependents.delete(address);
            }
        }
        for (const newDep of newDeps) {
            const depCell = this.sheet.cells.get(newDep);
            if (depCell)
                depCell.dependents.add(address);
        }
        this.recompute(address);
        this.renderGrid();
        this.renderFormulaBar();
        this.editing = false;
    }
    recompute(address) {
        const cell = this.sheet.cells.get(address);
        if (!cell)
            return;
        const result = computeCell(cell, this.sheet);
        cell.value = result.value;
        cell.error = result.error || null;
        for (const dep of cell.dependents) {
            this.recompute(dep);
        }
    }
    insertRow(row) {
        if (row < 1 || row > this.config.rows)
            return;
        for (let r = this.config.rows; r >= row; r--) {
            for (let c = 0; c < this.config.cols; c++) {
                const addr = addressToString({ col: c, row: r });
                const oldCell = this.sheet.cells.get(addr);
                if (oldCell) {
                    const newAddr = addressToString({ col: c, row: r + 1 });
                    oldCell.address = newAddr;
                    this.sheet.cells.delete(addr);
                    this.sheet.cells.set(newAddr, oldCell);
                    oldCell.dependencies.forEach(dep => {
                        const depCell = this.sheet.cells.get(dep);
                        if (depCell) {
                            depCell.dependents.delete(addr);
                            depCell.dependents.add(newAddr);
                        }
                    });
                    oldCell.dependents.forEach(dep => {
                        const depCell = this.sheet.cells.get(dep);
                        if (depCell) {
                            depCell.dependencies.delete(addr);
                            depCell.dependencies.add(newAddr);
                        }
                    });
                }
            }
        }
        this.config.rows++;
        this.renderGrid();
        this.save();
    }
    deleteRow(row) {
        if (row < 1 || row > this.config.rows)
            return;
        for (let r = row; r < this.config.rows; r++) {
            for (let c = 0; c < this.config.cols; c++) {
                const addr = addressToString({ col: c, row: r });
                const cell = this.sheet.cells.get(addr);
                if (cell) {
                    const newAddr = addressToString({ col: c, row: r + 1 });
                    cell.address = newAddr;
                    this.sheet.cells.delete(addr);
                    this.sheet.cells.set(newAddr, cell);
                    cell.dependencies.forEach(dep => {
                        const depCell = this.sheet.cells.get(dep);
                        if (depCell) {
                            depCell.dependents.delete(addr);
                            depCell.dependents.add(newAddr);
                        }
                    });
                    cell.dependents.forEach(dep => {
                        const depCell = this.sheet.cells.get(dep);
                        if (depCell) {
                            depCell.dependencies.delete(addr);
                            depCell.dependencies.add(newAddr);
                        }
                    });
                }
            }
        }
        this.config.rows--;
        this.renderGrid();
        this.save();
    }
    insertColumn(col) {
        if (col < 0 || col >= this.config.cols)
            return;
        for (let c = this.config.cols - 1; c >= col; c--) {
            for (let r = 1; r <= this.config.rows; r++) {
                const addr = addressToString({ col: c, row: r });
                const oldCell = this.sheet.cells.get(addr);
                if (oldCell) {
                    const newAddr = addressToString({ col: c + 1, row: r });
                    oldCell.address = newAddr;
                    this.sheet.cells.delete(addr);
                    this.sheet.cells.set(newAddr, oldCell);
                    oldCell.dependencies.forEach(dep => {
                        const depCell = this.sheet.cells.get(dep);
                        if (depCell) {
                            depCell.dependents.delete(addr);
                            depCell.dependents.add(newAddr);
                        }
                    });
                    oldCell.dependents.forEach(dep => {
                        const depCell = this.sheet.cells.get(dep);
                        if (depCell) {
                            depCell.dependencies.delete(addr);
                            depCell.dependencies.add(newAddr);
                        }
                    });
                }
            }
        }
        this.config.cols++;
        this.renderGrid();
        this.save();
    }
    deleteColumn(col) {
        if (col < 0 || col >= this.config.cols)
            return;
        for (let c = col; c < this.config.cols; c++) {
            for (let r = 1; r <= this.config.rows; r++) {
                const addr = addressToString({ col: c, row: r });
                const cell = this.sheet.cells.get(addr);
                if (cell) {
                    const newAddr = addressToString({ col: c, row: r });
                    cell.address = newAddr;
                    this.sheet.cells.delete(addr);
                    this.sheet.cells.set(newAddr, cell);
                    cell.dependencies.forEach(dep => {
                        const depCell = this.sheet.cells.get(dep);
                        if (depCell) {
                            depCell.dependents.delete(addr);
                            depCell.dependents.add(newAddr);
                        }
                    });
                    cell.dependents.forEach(dep => {
                        const depCell = this.sheet.cells.get(dep);
                        if (depCell) {
                            depCell.dependencies.delete(addr);
                            depCell.dependencies.add(newAddr);
                        }
                    });
                }
            }
        }
        this.config.cols--;
        this.renderGrid();
        this.save();
    }
    clearCell() {
        if (!this.selected)
            return;
        const addr = addressToString(this.selected);
        const cell = this.sheet.cells.get(addr);
        if (!cell)
            return;
        const oldDeps = new Set(cell.dependencies);
        cell.raw = '';
        cell.formula = false;
        cell.value = null;
        cell.error = null;
        cell.dependencies.clear();
        oldDeps.forEach(dep => {
            const depCell = this.sheet.cells.get(dep);
            if (depCell)
                depCell.dependents.delete(addr);
        });
        this.recompute(addr);
        this.renderGrid();
        this.renderFormulaBar();
    }
    save() {
        const data = {
            cells: Array.from(this.sheet.cells.entries()).map(([addr, cell]) => ({ addr, ...cell, dependencies: Array.from(cell.dependencies), dependents: Array.from(cell.dependents) })),
            config: this.config,
        };
        localStorage.setItem('spreadsheet', JSON.stringify(data));
    }
    load() {
        const data = localStorage.getItem('spreadsheet');
        if (!data)
            return;
        const { cells, config } = JSON.parse(data);
        this.config = { ...this.config, ...config };
        this.sheet.cells.clear();
        cells.forEach((c) => {
            const cell = { ...c, dependencies: new Set(c.dependencies), dependents: new Set(c.dependents) };
            this.sheet.cells.set(c.addr, cell);
        });
        this.renderGrid();
        this.renderFormulaBar();
    }
    getSelected() {
        return this.selected;
    }
    setSelectedFromAddress(addr) {
        try {
            const parsed = parseAddress(addr);
            this.setSelected(parsed);
        }
        catch (e) { }
    }
    getCell(address) {
        return this.sheet.cells.get(address);
    }
}
exports.Spreadsheet = Spreadsheet;
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>