← Spreadsheet results

Spreadsheet

ProCreations grug 27b GGUF IQ4 · typescript

Initial view of Spreadsheet

Browser check: Artifact runtime error. This is a technical check, not a quality review.

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 44,457 bytes · SHA-256 b40907eedc53
<!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="toolbar"></div>
<div id="formula-bar"></div>
<div id="grid-container"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./spreadsheet":"src/spreadsheet.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 spreadsheet_1 = require("./spreadsheet");
const spreadsheet = new spreadsheet_1.Spreadsheet(26, 100);
spreadsheet.render();
spreadsheet.loadFromLocalStorage();
spreadsheet.enableEditing();
};

// ── module: src/spreadsheet.ts ──
__mods["src/spreadsheet.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Spreadsheet = void 0;
/* Constants */
const COLUMNS = 26;
const ROWS = 100;
const CELL_WIDTH = 100;
const CELL_HEIGHT = 24;
const HEADER_HEIGHT = 24;
const HEADER_WIDTH = 40;
const COLORS = {
    selection: '#2196F3',
    gridline: '#e0e0e0',
    header: '#f5f5f5',
    error: '#d32f2f',
    text: '#000',
    background: '#fff',
};
/* Types */
var TokenType;
(function (TokenType) {
    TokenType[TokenType["NUMBER"] = 0] = "NUMBER";
    TokenType[TokenType["IDENT"] = 1] = "IDENT";
    TokenType[TokenType["FUNC"] = 2] = "FUNC";
    TokenType[TokenType["OPERATOR"] = 3] = "OPERATOR";
    TokenType[TokenType["LPAREN"] = 4] = "LPAREN";
    TokenType[TokenType["RPAREN"] = 5] = "RPAREN";
    TokenType[TokenType["COMMA"] = 6] = "COMMA";
    TokenType[TokenType["COLON"] = 7] = "COLON";
})(TokenType || (TokenType = {}));
var ExprType;
(function (ExprType) {
    ExprType[ExprType["NUMBER"] = 0] = "NUMBER";
    ExprType[ExprType["REFERENCE"] = 1] = "REFERENCE";
    ExprType[ExprType["RANGE"] = 2] = "RANGE";
    ExprType[ExprType["FUNCTION"] = 3] = "FUNCTION";
    ExprType[ExprType["BINARY"] = 4] = "BINARY";
    ExprType[ExprType["UNARY"] = 5] = "UNARY";
    ExprType[ExprType["ERROR"] = 6] = "ERROR";
})(ExprType || (ExprType = {}));
class Spreadsheet {
    constructor(cols, rows) {
        this.cols = cols;
        this.rows = rows;
        this.selectedCell = null;
        this.editingCell = null;
        this.formulaBar = null;
        this.refDisplay = null;
        this.container = null;
        this.table = null;
        this.recalculating = false;
        this.grid = [];
        for (let row = 0; row < rows; row++) {
            const rowCells = [];
            for (let col = 0; col < cols; col++) {
                rowCells.push({ raw: '', display: '', value: null, formula: null, isEditing: false });
            }
            this.grid.push(rowCells);
        }
        this.dependencies = new Map();
        this.dependents = new Map();
    }
    colToLetter(col) {
        let result = '';
        let n = col;
        while (n >= 0) {
            result = String.fromCharCode(65 + (n % 26)) + result;
            n = Math.floor(n / 26) - 1;
        }
        return result;
    }
    refToString(col, row) {
        return this.colToLetter(col) + (row + 1);
    }
    /* Rendering */
    render() {
        this.container = document.getElementById('grid-container');
        this.container.innerHTML = '';
        this.table = document.createElement('table');
        this.table.style.borderCollapse = 'collapse';
        this.table.style.width = `${HEADER_WIDTH + this.cols * CELL_WIDTH}px`;
        // Header row
        const headerRow = document.createElement('tr');
        const cornerHeader = document.createElement('th');
        cornerHeader.style.width = `${HEADER_WIDTH}px`;
        cornerHeader.style.minWidth = `${HEADER_WIDTH}px`;
        cornerHeader.style.position = 'sticky';
        cornerHeader.style.left = '0';
        cornerHeader.style.top = '0';
        cornerHeader.style.zIndex = '3';
        cornerHeader.style.background = COLORS.header;
        cornerHeader.style.borderRight = `1px solid ${COLORS.gridline}`;
        cornerHeader.style.borderBottom = `1px solid ${COLORS.gridline}`;
        headerRow.appendChild(cornerHeader);
        for (let col = 0; col < this.cols; col++) {
            const header = document.createElement('th');
            header.textContent = this.colToLetter(col);
            header.style.width = `${CELL_WIDTH}px`;
            header.style.minWidth = `${CELL_WIDTH}px`;
            header.style.position = 'sticky';
            header.style.top = '0';
            header.style.zIndex = '2';
            header.style.background = COLORS.header;
            header.style.textAlign = 'center';
            header.style.borderRight = `1px solid ${COLORS.gridline}`;
            header.style.borderBottom = `1px solid ${COLORS.gridline}`;
            header.addEventListener('contextmenu', (event) => {
                event.preventDefault();
                this.showColumnMenu(event, col);
            });
            headerRow.appendChild(header);
        }
        this.table.appendChild(headerRow);
        for (let row = 0; row < this.rows; row++) {
            const tableRow = document.createElement('tr');
            const rowHeader = document.createElement('th');
            rowHeader.textContent = String(row + 1);
            rowHeader.style.width = `${HEADER_WIDTH}px`;
            rowHeader.style.minWidth = `${HEADER_WIDTH}px`;
            rowHeader.style.position = 'sticky';
            rowHeader.style.left = '0';
            rowHeader.style.zIndex = '1';
            rowHeader.style.background = COLORS.header;
            rowHeader.style.textAlign = 'right';
            rowHeader.style.paddingRight = '4px';
            rowHeader.style.borderRight = `1px solid ${COLORS.gridline}`;
            rowHeader.style.borderBottom = `1px solid ${COLORS.gridline}`;
            rowHeader.addEventListener('contextmenu', (event) => {
                event.preventDefault();
                this.showRowMenu(event, row);
            });
            tableRow.appendChild(rowHeader);
            for (let col = 0; col < this.cols; col++) {
                const cell = document.createElement('td');
                cell.id = `cell-${col}-${row}`;
                cell.style.width = `${CELL_WIDTH}px`;
                cell.style.height = `${CELL_HEIGHT}px`;
                cell.style.borderRight = `1px solid ${COLORS.gridline}`;
                cell.style.borderBottom = `1px solid ${COLORS.gridline}`;
                cell.style.padding = '0 4px';
                cell.style.cursor = 'pointer';
                cell.style.whiteSpace = 'nowrap';
                cell.style.overflow = 'hidden';
                cell.style.textAlign = 'right';
                const cellRef = this.refToString(col, row);
                cell.dataset.ref = cellRef;
                cell.addEventListener('click', () => {
                    this.selectCell({ col, row });
                });
                cell.addEventListener('dblclick', () => {
                    this.editCell({ col, row });
                });
                tableRow.appendChild(cell);
            }
            this.table.appendChild(tableRow);
        }
        this.container.appendChild(this.table);
        this.updateFormulaBar();
    }
    /* Cell selection */
    selectCell(ref) {
        if (this.editingCell) {
            this.commitEdit();
        }
        if (this.selectedCell) {
            const oldCell = this.getDOMCell(this.selectedCell.col, this.selectedCell.row);
            if (oldCell) {
                oldCell.style.outline = 'none';
            }
        }
        this.selectedCell = ref;
        const newCell = this.getDOMCell(ref.col, ref.row);
        if (newCell) {
            newCell.style.outline = `2px solid ${COLORS.selection}`;
            newCell.style.outlineOffset = '-2px';
        }
        this.updateFormulaBar();
    }
    /* Editing */
    editCell(ref) {
        this.selectCell(ref);
        const cell = this.grid[ref.row][ref.col];
        const domCell = this.getDOMCell(ref.col, ref.row);
        if (!domCell)
            return;
        cell.isEditing = true;
        this.editingCell = ref;
        domCell.innerHTML = '';
        const input = document.createElement('input');
        input.value = cell.raw;
        input.style.width = '100%';
        input.style.height = '100%';
        input.style.border = 'none';
        input.style.outline = 'none';
        input.style.padding = '0 4px';
        input.style.fontFamily = 'inherit';
        input.style.fontSize = 'inherit';
        domCell.appendChild(input);
        input.focus();
        input.addEventListener('keydown', (event) => {
            if (event.key === 'Enter') {
                event.preventDefault();
                this.commitEdit(true);
                this.selectCell({ col: ref.col, row: ref.row + 1 });
            }
            else if (event.key === 'Tab') {
                event.preventDefault();
                this.commitEdit(true);
                this.selectCell({ col: ref.col + 1, row: ref.row });
            }
            else if (event.key === 'Escape') {
                this.cancelEdit();
            }
            else {
                this.formulaBar.value = input.value;
                this.formulaBar.dispatchEvent(new Event('input', { bubbles: true }));
            }
        });
    }
    commitEdit(shouldRecalc = true) {
        if (!this.editingCell)
            return;
        const { col, row } = this.editingCell;
        const cell = this.grid[row][col];
        const domCell = this.getDOMCell(col, row);
        if (!domCell)
            return;
        const input = domCell.querySelector('input');
        cell.raw = input.value;
        cell.isEditing = false;
        this.editingCell = null;
        if (shouldRecalc) {
            this.calculateCell({ col, row });
        }
        this.renderCell(col, row);
        this.updateFormulaBar();
    }
    cancelEdit() {
        if (!this.editingCell)
            return;
        const { col, row } = this.editingCell;
        const cell = this.grid[row][col];
        const domCell = this.getDOMCell(col, row);
        if (!domCell)
            return;
        cell.isEditing = false;
        this.editingCell = null;
        this.renderCell(col, row);
        this.updateFormulaBar();
    }
    /* Formula bar */
    updateFormulaBar() {
        if (this.selectedCell) {
            const { col, row } = this.selectedCell;
            const cell = this.grid[row][col];
            if (!this.formulaBar) {
                this.setupFormulaBar();
            }
            if (this.refDisplay) {
                this.refDisplay.textContent = this.refToString(col, row);
            }
            this.formulaBar.value = cell.raw;
        }
    }
    setupFormulaBar() {
        const container = document.getElementById('formula-bar');
        container.innerHTML = '';
        container.style.padding = '4px';
        container.style.background = '#f5f5f5';
        container.style.borderBottom = `1px solid ${COLORS.gridline}`;
        container.style.display = 'flex';
        container.style.alignItems = 'center';
        container.style.gap = '8px';
        this.refDisplay = document.createElement('span');
        this.refDisplay.style.fontWeight = 'bold';
        container.appendChild(this.refDisplay);
        this.formulaBar = document.createElement('input');
        this.formulaBar.style.flex = '1';
        this.formulaBar.style.padding = '4px';
        this.formulaBar.style.fontFamily = 'monospace';
        this.formulaBar.addEventListener('keydown', (event) => {
            if (event.key === 'Enter') {
                event.preventDefault();
                this.setCellRaw(this.formulaBar.value);
                this.updateFormulaBar();
            }
        });
        this.formulaBar.addEventListener('input', () => {
            if (this.editingCell) {
                const domCell = this.getDOMCell(this.editingCell.col, this.editingCell.row);
                if (domCell) {
                    const input = domCell.querySelector('input');
                    if (input) {
                        input.value = this.formulaBar.value;
                    }
                }
            }
        });
        container.appendChild(this.formulaBar);
    }
    /* Toolbar */
    setupToolbar() {
        const container = document.getElementById('toolbar');
        container.innerHTML = '';
        container.style.padding = '8px';
        container.style.background = '#fafafa';
        container.style.borderBottom = `1px solid ${COLORS.gridline}`;
        container.style.display = 'flex';
        container.style.gap = '8px';
        const insertRowBtn = document.createElement('button');
        insertRowBtn.textContent = 'Insert Row';
        insertRowBtn.addEventListener('click', () => {
            if (this.selectedCell) {
                this.insertRow(this.selectedCell.row);
            }
        });
        container.appendChild(insertRowBtn);
        const insertColBtn = document.createElement('button');
        insertColBtn.textContent = 'Insert Column';
        insertColBtn.addEventListener('click', () => {
            if (this.selectedCell) {
                this.insertColumn(this.selectedCell.col);
            }
        });
        container.appendChild(insertColBtn);
        const deleteRowBtn = document.createElement('button');
        deleteRowBtn.textContent = 'Delete Row';
        deleteRowBtn.addEventListener('click', () => {
            if (this.selectedCell) {
                this.deleteRow(this.selectedCell.row);
            }
        });
        container.appendChild(deleteRowBtn);
        const deleteColBtn = document.createElement('button');
        deleteColBtn.textContent = 'Delete Column';
        deleteColBtn.addEventListener('click', () => {
            if (this.selectedCell) {
                this.deleteColumn(this.selectedCell.col);
            }
        });
        container.appendChild(deleteColBtn);
        const clearCellBtn = document.createElement('button');
        clearCellBtn.textContent = 'Clear Cell';
        clearCellBtn.addEventListener('click', () => {
            if (this.selectedCell) {
                this.clearCell(this.selectedCell);
            }
        });
        container.appendChild(clearCellBtn);
        const saveBtn = document.createElement('button');
        saveBtn.textContent = 'Save';
        saveBtn.addEventListener('click', () => {
            this.saveToLocalStorage();
        });
        container.appendChild(saveBtn);
    }
    /* Menu helpers */
    showRowMenu(event, row) {
        const menu = document.createElement('div');
        menu.style.position = 'fixed';
        menu.style.left = `${event.clientX}px`;
        menu.style.top = `${event.clientY}px`;
        menu.style.background = 'white';
        menu.style.border = `1px solid ${COLORS.gridline}`;
        menu.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
        menu.style.padding = '4px 0';
        menu.style.zIndex = '1000';
        const insertOption = document.createElement('div');
        insertOption.textContent = 'Insert Row Above';
        insertOption.style.padding = '8px 16px';
        insertOption.style.cursor = 'pointer';
        insertOption.addEventListener('click', () => {
            this.insertRow(row);
            menu.remove();
        });
        menu.appendChild(insertOption);
        const deleteOption = document.createElement('div');
        deleteOption.textContent = 'Delete Row';
        deleteOption.style.padding = '8px 16px';
        deleteOption.style.cursor = 'pointer';
        deleteOption.addEventListener('click', () => {
            this.deleteRow(row);
            menu.remove();
        });
        menu.appendChild(deleteOption);
        document.body.appendChild(menu);
        setTimeout(() => {
            document.addEventListener('click', () => menu.remove(), { once: true });
        }, 10);
    }
    showColumnMenu(event, col) {
        const menu = document.createElement('div');
        menu.style.position = 'fixed';
        menu.style.left = `${event.clientX}px`;
        menu.style.top = `${event.clientY}px`;
        menu.style.background = 'white';
        menu.style.border = `1px solid ${COLORS.gridline}`;
        menu.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
        menu.style.padding = '4px 0';
        menu.style.zIndex = '1000';
        const insertOption = document.createElement('div');
        insertOption.textContent = 'Insert Column Left';
        insertOption.style.padding = '8px 16px';
        insertOption.style.cursor = 'pointer';
        insertOption.addEventListener('click', () => {
            this.insertColumn(col);
            menu.remove();
        });
        menu.appendChild(insertOption);
        const deleteOption = document.createElement('div');
        deleteOption.textContent = 'Delete Column';
        deleteOption.style.padding = '8px 16px';
        deleteOption.style.cursor = 'pointer';
        deleteOption.addEventListener('click', () => {
            this.deleteColumn(col);
            menu.remove();
        });
        menu.appendChild(deleteOption);
        document.body.appendChild(menu);
        setTimeout(() => {
            document.addEventListener('click', () => menu.remove(), { once: true });
        }, 10);
    }
    /* DOM helpers */
    getDOMCell(col, row) {
        return document.getElementById(`cell-${col}-${row}`);
    }
    setCellRaw(raw) {
        if (!this.selectedCell)
            return;
        const { col, row } = this.selectedCell;
        const cell = this.grid[row][col];
        cell.raw = raw;
        this.calculateCell({ col, row });
        this.renderCell(col, row);
    }
    clearCell(ref) {
        const cell = this.grid[ref.row][ref.col];
        cell.raw = '';
        cell.display = '';
        cell.value = null;
        cell.formula = null;
        cell.error = undefined;
        this.renderCell(ref.col, ref.row);
        this.recalcDependents(ref);
    }
    /* Formula engine: tokenizer */
    tokenize(text) {
        const tokens = [];
        let i = 0;
        while (i < text.length) {
            const ch = text[i];
            if (ch === ' ' || ch === '\t') {
                i++;
                continue;
            }
            if (ch >= '0' && ch <= '9') {
                let num = '';
                while (i < text.length && (text[i] >= '0' && text[i] <= '9' || text[i] === '.')) {
                    num += text[i];
                    i++;
                }
                tokens.push({ type: TokenType.NUMBER, value: num });
                continue;
            }
            if (ch >= 'A' && ch <= 'Z') {
                let ident = '';
                while (i < text.length && (text[i] >= 'A' && text[i] <= 'Z' || text[i] >= '0' && text[i] <= '9')) {
                    ident += text[i];
                    i++;
                }
                if (['SUM', 'AVG', 'AVERAGE', 'MIN', 'MAX', 'COUNT'].includes(ident.toUpperCase())) {
                    tokens.push({ type: TokenType.FUNC, value: ident.toUpperCase() });
                }
                else {
                    tokens.push({ type: TokenType.IDENT, value: ident });
                }
                continue;
            }
            if (ch === '(') {
                tokens.push({ type: TokenType.LPAREN, value: '(' });
                i++;
                continue;
            }
            if (ch === ')') {
                tokens.push({ type: TokenType.RPAREN, value: ')' });
                i++;
                continue;
            }
            if (ch === ',') {
                tokens.push({ type: TokenType.COMMA, value: ',' });
                i++;
                continue;
            }
            if (ch === ':') {
                tokens.push({ type: TokenType.COLON, value: ':' });
                i++;
                continue;
            }
            if (ch === '+' || ch === '-' || ch === '*' || ch === '/') {
                tokens.push({ type: TokenType.OPERATOR, value: ch });
                i++;
                continue;
            }
            throw new Error(`Unexpected character: ${ch}`);
        }
        return tokens;
    }
    /* Formula engine: recursive-descent parser */
    parse(expr) {
        try {
            const tokens = this.tokenize(expr);
            const context = { tokens, pos: 0 };
            const result = this.parseExpression(context);
            if (context.pos < tokens.length) {
                return { type: ExprType.ERROR, error: '#ERR!' };
            }
            return result;
        }
        catch (err) {
            return { type: ExprType.ERROR, error: '#ERR!' };
        }
    }
    parseExpression(ctx) {
        let left = this.parseTerm(ctx);
        while (ctx.pos < ctx.tokens.length &&
            ctx.tokens[ctx.pos].type === TokenType.OPERATOR &&
            (ctx.tokens[ctx.pos].value === '+' || ctx.tokens[ctx.pos].value === '-')) {
            const op = ctx.tokens[ctx.pos].value;
            ctx.pos++;
            const right = this.parseTerm(ctx);
            left = { type: ExprType.BINARY, op, left, right };
        }
        return left;
    }
    parseTerm(ctx) {
        let left = this.parseUnary(ctx);
        while (ctx.pos < ctx.tokens.length &&
            ctx.tokens[ctx.pos].type === TokenType.OPERATOR &&
            (ctx.tokens[ctx.pos].value === '*' || ctx.tokens[ctx.pos].value === '/')) {
            const op = ctx.tokens[ctx.pos].value;
            ctx.pos++;
            const right = this.parseUnary(ctx);
            left = { type: ExprType.BINARY, op, left, right };
        }
        return left;
    }
    parseUnary(ctx) {
        if (ctx.pos < ctx.tokens.length &&
            ctx.tokens[ctx.pos].type === TokenType.OPERATOR &&
            ctx.tokens[ctx.pos].value === '-') {
            ctx.pos++;
            const operand = this.parseUnary(ctx);
            return { type: ExprType.UNARY, op: '-', left: operand };
        }
        return this.parsePrimary(ctx);
    }
    parsePrimary(ctx) {
        if (ctx.pos < ctx.tokens.length &&
            ctx.tokens[ctx.pos].type === TokenType.NUMBER) {
            const token = ctx.tokens[ctx.pos];
            ctx.pos++;
            return { type: ExprType.NUMBER, value: parseFloat(token.value) };
        }
        if (ctx.pos < ctx.tokens.length &&
            ctx.tokens[ctx.pos].type === TokenType.IDENT) {
            const ref = this.parseCellRef(ctx.tokens[ctx.pos].value);
            if (!ref) {
                return { type: ExprType.ERROR, error: '#REF!' };
            }
            ctx.pos++;
            if (ctx.pos < ctx.tokens.length &&
                ctx.tokens[ctx.pos].type === TokenType.COLON) {
                ctx.pos++;
                if (ctx.pos < ctx.tokens.length &&
                    ctx.tokens[ctx.pos].type === TokenType.IDENT) {
                    const endRef = this.parseCellRef(ctx.tokens[ctx.pos].value);
                    ctx.pos++;
                    if (!endRef) {
                        return { type: ExprType.ERROR, error: '#REF!' };
                    }
                    return { type: ExprType.RANGE, col: ref.col, row: ref.row, endCol: endRef.col, endRow: endRef.row };
                }
                return { type: ExprType.ERROR, error: '#REF!' };
            }
            return { type: ExprType.REFERENCE, col: ref.col, row: ref.row };
        }
        if (ctx.pos < ctx.tokens.length &&
            ctx.tokens[ctx.pos].type === TokenType.FUNC) {
            const func = ctx.tokens[ctx.pos].value;
            ctx.pos++;
            if (ctx.pos < ctx.tokens.length &&
                ctx.tokens[ctx.pos].type === TokenType.LPAREN) {
                ctx.pos++;
                const args = [];
                while (ctx.pos < ctx.tokens.length &&
                    ctx.tokens[ctx.pos].type !== TokenType.RPAREN) {
                    if (ctx.pos < ctx.tokens.length &&
                        ctx.tokens[ctx.pos].type === TokenType.COMMA) {
                        ctx.pos++;
                        continue;
                    }
                    args.push(this.parseExpression(ctx));
                }
                if (ctx.pos < ctx.tokens.length &&
                    ctx.tokens[ctx.pos].type === TokenType.RPAREN) {
                    ctx.pos++;
                    return { type: ExprType.FUNCTION, func, args };
                }
            }
            return { type: ExprType.ERROR, error: '#ERR!' };
        }
        if (ctx.pos < ctx.tokens.length &&
            ctx.tokens[ctx.pos].type === TokenType.LPAREN) {
            ctx.pos++;
            const expr = this.parseExpression(ctx);
            if (ctx.pos < ctx.tokens.length &&
                ctx.tokens[ctx.pos].type === TokenType.RPAREN) {
                ctx.pos++;
                return expr;
            }
            return { type: ExprType.ERROR, error: '#ERR!' };
        }
        return { type: ExprType.ERROR, error: '#ERR!' };
    }
    parseCellRef(ref) {
        const match = ref.match(/^([A-Z]+)(\d+)$/);
        if (!match)
            return null;
        const col = this.parseColumnName(match[1]);
        const row = parseInt(match[2]) - 1;
        if (col < 0 || col >= this.cols || row < 0 || row >= this.rows) {
            return null;
        }
        return { col, row };
    }
    parseColumnName(name) {
        let col = 0;
        for (const ch of name) {
            col = col * 26 + (ch.charCodeAt(0) - 65);
        }
        return col;
    }
    /* Formula engine: evaluator */
    evaluateExpression(expr) {
        switch (expr.type) {
            case ExprType.NUMBER:
                return expr.value;
            case ExprType.REFERENCE: {
                const cell = this.grid[expr.row][expr.col];
                if (cell.error)
                    return null;
                return cell.value;
            }
            case ExprType.RANGE: {
                const values = [];
                for (let row = Math.min(expr.row, expr.endRow); row <= Math.max(expr.row, expr.endRow); row++) {
                    for (let col = Math.min(expr.col, expr.endCol); col <= Math.max(expr.col, expr.endCol); col++) {
                        const cell = this.grid[row][col];
                        if (cell.value !== null && !cell.error) {
                            values.push(cell.value);
                        }
                    }
                }
                return values.length > 0 ? values[0] : null;
            }
            case ExprType.FUNCTION: {
                const func = expr.func.toUpperCase();
                const args = expr.args;
                const values = [];
                for (const arg of args) {
                    const argValues = this.collectValues(arg);
                    values.push(...argValues);
                }
                switch (func) {
                    case 'SUM':
                        return values.reduce((sum, val) => sum + val, 0);
                    case 'AVG':
                    case 'AVERAGE':
                        return values.length > 0 ? values.reduce((sum, val) => sum + val, 0) / values.length : 0;
                    case 'MIN':
                        return values.length > 0 ? Math.min(...values) : 0;
                    case 'MAX':
                        return values.length > 0 ? Math.max(...values) : 0;
                    case 'COUNT':
                        return values.length;
                    default:
                        return null;
                }
            }
            case ExprType.BINARY: {
                const left = this.evaluateExpression(expr.left);
                if (left === null)
                    return null;
                const right = this.evaluateExpression(expr.right);
                if (right === null)
                    return null;
                switch (expr.op) {
                    case '+':
                        return left + right;
                    case '-':
                        return left - right;
                    case '*':
                        return left * right;
                    case '/':
                        if (right === 0)
                            return null;
                        return left / right;
                }
            }
            case ExprType.UNARY: {
                const operand = this.evaluateExpression(expr.left);
                if (operand === null)
                    return null;
                return -operand;
            }
            case ExprType.ERROR:
                return null;
        }
        return null;
    }
    collectValues(expr) {
        const values = [];
        switch (expr.type) {
            case ExprType.NUMBER:
                values.push(expr.value);
                break;
            case ExprType.REFERENCE: {
                const cell = this.grid[expr.row][expr.col];
                if (cell.value !== null && !cell.error) {
                    values.push(cell.value);
                }
                break;
            }
            case ExprType.RANGE: {
                for (let row = Math.min(expr.row, expr.endRow); row <= Math.max(expr.row, expr.endRow); row++) {
                    for (let col = Math.min(expr.col, expr.endCol); col <= Math.max(expr.col, expr.endCol); col++) {
                        const cell = this.grid[row][col];
                        if (cell.value !== null && !cell.error) {
                            values.push(cell.value);
                        }
                    }
                }
                break;
            }
            case ExprType.FUNCTION:
                for (const arg of expr.args) {
                    values.push(...this.collectValues(arg));
                }
                break;
        }
        return values;
    }
    /* Dependency graph */
    getCellRefs(expr) {
        const refs = [];
        switch (expr.type) {
            case ExprType.REFERENCE:
                refs.push({ col: expr.col, row: expr.row });
                break;
            case ExprType.RANGE:
                for (let row = Math.min(expr.row, expr.endRow); row <= Math.max(expr.row, expr.endRow); row++) {
                    for (let col = Math.min(expr.col, expr.endCol); col <= Math.max(expr.col, expr.endCol); col++) {
                        refs.push({ col, row });
                    }
                }
                break;
            case ExprType.FUNCTION:
                for (const arg of expr.args) {
                    refs.push(...this.getCellRefs(arg));
                }
                break;
            case ExprType.BINARY:
                refs.push(...this.getCellRefs(expr.left));
                refs.push(...this.getCellRefs(expr.right));
                break;
            case ExprType.UNARY:
                refs.push(...this.getCellRefs(expr));
                break;
        }
        return refs;
    }
    addDependency(from, to) {
        const fromRef = this.refToString(from.col, from.row);
        const toRef = this.refToString(to.col, to.row);
        if (!this.dependencies.has(fromRef)) {
            this.dependencies.set(fromRef, new Set());
        }
        this.dependencies.get(fromRef).add(toRef);
        if (!this.dependents.has(toRef)) {
            this.dependents.set(toRef, new Set());
        }
        this.dependents.get(toRef).add(fromRef);
    }
    removeDependencies(ref) {
        const refStr = this.refToString(ref.col, ref.row);
        // Remove from dependents
        for (const [key, dependents] of this.dependents) {
            dependents.delete(refStr);
        }
        // Remove from dependencies
        const dependencies = this.dependencies.get(refStr);
        if (dependencies) {
            for (const dep of dependencies) {
                const dependents = this.dependents.get(dep);
                if (dependents) {
                    dependents.delete(refStr);
                }
            }
            this.dependencies.delete(refStr);
        }
    }
    detectCycle(ref, visited, path) {
        const refStr = this.refToString(ref.col, ref.row);
        if (visited.has(refStr)) {
            path.push(refStr);
            return true;
        }
        visited.add(refStr);
        path.push(refStr);
        const deps = this.dependencies.get(refStr);
        if (deps) {
            for (const dep of deps) {
                if (this.detectCycle(this.parseRef(dep), visited, path)) {
                    return true;
                }
            }
        }
        visited.delete(refStr);
        path.pop();
        return false;
    }
    parseRef(ref) {
        const match = ref.match(/^([A-Z]+)(\d+)$/);
        if (!match)
            throw new Error(`Invalid cell reference: ${ref}`);
        return {
            col: this.parseColumnName(match[1]),
            row: parseInt(match[2]) - 1,
        };
    }
    /* Calculation */
    calculateCell(ref) {
        const cell = this.grid[ref.row][ref.col];
        const raw = cell.raw.trim();
        this.removeDependencies(ref);
        if (raw === '') {
            cell.display = '';
            cell.value = null;
            cell.formula = null;
            cell.error = undefined;
            this.recalcDependents(ref);
            this.renderCell(ref.col, ref.row);
            return;
        }
        if (raw.startsWith('=')) {
            const formulaText = raw.substring(1);
            const formula = this.parse(formulaText);
            cell.formula = formula;
            if (formula.type === ExprType.ERROR) {
                cell.error = formula.error;
                cell.display = formula.error;
                cell.value = null;
                this.recalcDependents(ref);
                this.renderCell(ref.col, ref.row);
                return;
            }
            // Build dependencies
            const refs = this.getCellRefs(formula);
            for (const r of refs) {
                this.addDependency(ref, r);
            }
            // Check for cycles
            if (this.detectCycle(ref, new Set(), [])) {
                cell.error = '#CYCLE!';
                cell.display = '#CYCLE!';
                cell.value = null;
                this.recalcDependents(ref);
                this.renderCell(ref.col, ref.row);
                return;
            }
            // Evaluate
            const result = this.evaluateExpression(formula);
            if (result === null) {
                cell.error = '#REF!';
                cell.display = '#REF!';
                cell.value = null;
            }
            else if (isNaN(result)) {
                cell.error = '#ERR!';
                cell.display = '#ERR!';
                cell.value = null;
            }
            else {
                cell.error = '';
                cell.display = result.toString();
                cell.value = !!result ? result : null;
            }
            this.recalcDependents(ref);
            this.renderCell(ref.col, ref.row);
        }
        else {
            cell.display = raw;
            cell.value = null;
            cell.formula = null;
            cell.error = undefined;
            this.recalcDependents(ref);
            this.renderCell(ref.col, ref.row);
        }
    }
    recalcDependents(ref) {
        const refStr = this.refToString(ref.col, ref.row);
        const dependents = this.dependents.get(refStr);
        if (!dependents)
            return;
        // Topological sort dependents
        const sorted = [];
        const visited = new Set();
        const visit = (cellRef) => {
            const refStr = this.refToString(cellRef.col, cellRef.row);
            if (visited.has(refStr))
                return;
            visited.add(refStr);
            const cellDeps = this.dependencies.get(refStr);
            if (cellDeps) {
                for (const dep of cellDeps) {
                    visit(this.parseRef(dep));
                }
            }
            sorted.push(cellRef);
        };
        for (const dep of dependents) {
            visit(this.parseRef(dep));
        }
        for (const cellRef of sorted) {
            this.calculateCell(cellRef);
        }
    }
    /* Rendering */
    renderCell(col, row) {
        const domCell = this.getDOMCell(col, row);
        if (!domCell)
            return;
        const cell = this.grid[row][col];
        if (cell.isEditing)
            return;
        domCell.textContent = cell.display;
        domCell.style.textAlign = cell.value !== null ? 'right' : 'left';
        if (cell.error) {
            domCell.style.color = COLORS.error;
        }
        else {
            domCell.style.color = COLORS.text;
        }
    }
    /* Row/column operations */
    insertRow(row) {
        const newRow = [];
        for (let col = 0; col < this.cols; col++) {
            newRow.push({ raw: '', display: '', value: null, formula: null, isEditing: false });
        }
        this.grid.splice(row, 0, newRow);
        // Rewrite formulas
        this.rewriteFormulasForRowInsert(row);
        this.render();
    }
    deleteRow(row) {
        this.grid.splice(row, 1);
        // Rewrite formulas
        this.rewriteFormulasForRowDelete(row);
        this.render();
    }
    insertColumn(col) {
        for (let row = 0; row < this.rows; row++) {
            this.grid[row].splice(col, 0, { raw: '', display: '', value: null, formula: null, isEditing: false });
        }
        // Rewrite formulas
        this.rewriteFormulasForColInsert(col);
        this.render();
    }
    deleteColumn(col) {
        for (let row = 0; row < this.rows; row++) {
            this.grid[row].splice(col, 1);
        }
        // Rewrite formulas
        this.rewriteFormulasForColDelete(col);
        this.render();
    }
    rewriteFormulasForRowInsert(row) {
        for (let r = 0; r < this.rows; r++) {
            for (let c = 0; c < this.cols; c++) {
                const cell = this.grid[r][c];
                if (cell.raw.startsWith('=')) {
                    cell.raw = cell.raw.replace(/([A-Z]+)(\d+)/g, (_, colLetter, rowNum) => {
                        const existingRow = parseInt(rowNum) - 1;
                        const newRow = existingRow >= row ? existingRow + 1 : existingRow;
                        return colLetter + (newRow + 1);
                    });
                    this.calculateCell({ col: c, row: r });
                }
            }
        }
    }
    rewriteFormulasForRowDelete(row) {
        for (let r = 0; r < this.rows; r++) {
            for (let c = 0; c < this.cols; c++) {
                const cell = this.grid[r][c];
                if (cell.raw.startsWith('=')) {
                    cell.raw = cell.raw.replace(/([A-Z]+)(\d+)/g, (_, colLetter, rowNum) => {
                        const existingRow = parseInt(rowNum) - 1;
                        if (existingRow === row) {
                            return colLetter + '0';
                        }
                        const newRow = existingRow > row ? existingRow - 1 : existingRow;
                        return colLetter + (newRow + 1);
                    });
                    this.calculateCell({ col: c, row: r });
                }
            }
        }
    }
    rewriteFormulasForColInsert(col) {
        for (let r = 0; r < this.rows; r++) {
            for (let c = 0; c < this.cols; c++) {
                const cell = this.grid[r][c];
                if (cell.raw.startsWith('=')) {
                    cell.raw = cell.raw.replace(/([A-Z]+)(\d+)/g, (_, colLetter, rowNum) => {
                        const existingCol = this.parseColumnName(colLetter);
                        const newCol = existingCol >= col ? existingCol + 1 : existingCol;
                        return this.colToLetter(newCol) + rowNum;
                    });
                    this.calculateCell({ col: c, row: r });
                }
            }
        }
    }
    rewriteFormulasForColDelete(col) {
        for (let r = 0; r < this.rows; r++) {
            for (let c = 0; c < this.cols; c++) {
                const cell = this.grid[r][c];
                if (cell.raw.startsWith('=')) {
                    cell.raw = cell.raw.replace(/([A-Z]+)(\d+)/g, (_, colLetter, rowNum) => {
                        const existingCol = this.parseColumnName(colLetter);
                        if (existingCol === col) {
                            return '??' + rowNum;
                        }
                        const newCol = existingCol > col ? existingCol - 1 : existingCol;
                        return this.colToLetter(newCol) + rowNum;
                    });
                    this.calculateCell({ col: c, row: r });
                }
            }
        }
    }
    /* Editing hooks */
    enableEditing() {
        this.setupToolbar();
        document.addEventListener('keydown', (event) => {
            if (!this.selectedCell)
                return;
            const { col, row } = this.selectedCell;
            if (event.key === 'ArrowUp' && row > 0) {
                event.preventDefault();
                this.selectCell({ col, row: row - 1 });
            }
            else if (event.key === 'ArrowDown' && row < this.rows - 1) {
                event.preventDefault();
                this.selectCell({ col, row: row + 1 });
            }
            else if (event.key === 'ArrowLeft' && col > 0) {
                event.preventDefault();
                this.selectCell({ col: col - 1, row });
            }
            else if (event.key === 'ArrowRight' && col < this.cols - 1) {
                event.preventDefault();
                this.selectCell({ col: col + 1, row });
            }
            else if (event.key === 'Delete') {
                event.preventDefault();
                this.clearCell({ col, row });
            }
            else if (!this.editingCell && event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
                this.editCell({ col, row });
                const domCell = this.getDOMCell(col, row);
                const input = domCell?.querySelector('input');
                if (input) {
                    input.value = event.key;
                }
            }
        });
        document.addEventListener('paste', (event) => {
            if (!this.editingCell) {
                this.editCell(this.selectedCell);
            }
        });
    }
    /* localStorage */
    saveToLocalStorage() {
        const data = [];
        for (let row = 0; row < this.rows; row++) {
            const rowCells = [];
            for (let col = 0; col < this.cols; col++) {
                rowCells.push(this.grid[row][col].raw);
            }
            data.push(rowCells);
        }
        localStorage.setItem('spreadsheet', JSON.stringify(data));
    }
    loadFromLocalStorage() {
        const data = localStorage.getItem('spreadsheet');
        if (!data)
            return;
        const parsed = JSON.parse(data);
        for (let row = 0; row < this.rows; row++) {
            for (let col = 0; col < this.cols; col++) {
                if (parsed[row] && parsed[row][col]) {
                    this.grid[row][col].raw = parsed[row][col];
                    this.calculateCell({ col, row });
                    this.renderCell(col, row);
                }
            }
        }
    }
}
exports.Spreadsheet = Spreadsheet;
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"bartowski/ProCreations_grug-27b-GGUF-IQ4","provider":"brigid","persona":"typescript","sessionId":"d6a0f62f-36a2-484c-be8b-c9e6f2ba58ce","tokensIn":712889,"tokensOut":14159,"tokensTotal":727048,"cost":0,"turns":27,"toolCalls":27,"failedToolCalls":7,"timestamp":"2026-08-05T17:41:35.587Z"} -->