← Spreadsheet results

Spreadsheet

Deepseek v4 Flash 0731 · 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 48,144 bytes · SHA-256 5a2817169e0e
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sheets — Spreadsheet</title>
<style>
/* ── css: layout ── */
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 13px; color: #202124; background: #fff;
}
#app { display: flex; flex-direction: column; height: 100vh; }

#toolbar {
  display: flex; align-items: center; gap: 14px;
  padding: 6px 10px; background: #f8f9fa; border-bottom: 1px solid #dadce0;
  flex-wrap: wrap; flex: 0 0 auto;
}
.tb-group { display: flex; align-items: center; gap: 4px; }
#toolbar button {
  font-size: 12.5px; padding: 5px 10px; border: 1px solid #dadce0; border-radius: 4px;
  background: #fff; color: #202124; cursor: pointer; line-height: 1;
}
#toolbar button:hover { background: #f1f3f4; }
#toolbar button:active { background: #e8eaed; }
.tb-hint { color: #5f6368; font-size: 12px; }
.tb-hint code { background: #e8eaed; border-radius: 3px; padding: 0 4px; font-size: 11.5px; }

#fbar {
  display: flex; align-items: center; flex: 0 0 auto;
  border-bottom: 1px solid #dadce0; background: #fff;
  height: 30px;
}
#cell-ref {
  min-width: 64px; padding: 0 8px; text-align: center;
  font-size: 13px; color: #202124; border-right: 1px solid #e1e1e1;
  font-weight: 500; user-select: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#fx {
  padding: 0 7px; color: #5f6368; font-style: italic; font-size: 13px;
  border-right: 1px solid #e1e1e1; user-select: none;
}
#formula-input {
  flex: 1; border: none; outline: none; height: 100%; padding: 0 8px;
  font-size: 13px; color: #202124;
}
#formula-input.editing { background: #fff; }

#grid-wrap {
  flex: 1 1 auto; overflow: auto; position: relative;
  scrollbar-width: thin; scrollbar-color: #c1c1c1 transparent;
}
#grid { border-collapse: collapse; border-spacing: 0; table-layout: fixed; }
/* ── css: cells ── */
#grid th, #grid td {
  border-right: 1px solid #e1e1e1; border-bottom: 1px solid #e1e1e1;
  padding: 0; height: 24px; white-space: nowrap; overflow: hidden;
}
#grid tbody td.cell { position: relative; cursor: cell; text-align: left; background: #fff; }
#grid tbody td.cell.num { text-align: right; }
#grid tbody td.cell.err { color: #d93025; }
#grid tbody td.cell.selected { box-shadow: inset 0 0 0 2px #1a73e8; z-index: 1; }
#grid tbody td.cell.editing { box-shadow: inset 0 0 0 2px #1a73e8; background: #fff; }
#grid tbody td.cell.editing input.ov {
  position: absolute; left: 0; top: 0; width: 100%; height: 100%;
  border: none; outline: none; padding: 0 5px; margin: 0;
  font: inherit; background: #fff; color: #202124;
}
#grid tbody td.cell input.ov.err { color: #d93025; }
#grid thead th {
  background: #f8f9fa; color: #5f6368; font-weight: 500; font-size: 11px;
  text-align: center; cursor: pointer; user-select: none; position: sticky; top: 0; z-index: 3;
}
#grid thead th:hover { background: #e8f0fe; color: #1a73e8; }
#grid tbody tr td.rowhead {
  background: #f8f9fa; color: #5f6368; font-size: 11px; text-align: center;
  user-select: none; cursor: pointer; position: sticky; left: 0; z-index: 2; width: 46px;
}
#grid .corner {
  background: #f8f9fa; position: sticky; top: 0; left: 0; z-index: 4; width: 46px; cursor: default;
}
#grid tbody td.cell .txt { padding: 0 5px; line-height: 24px; overflow: hidden; text-overflow: ellipsis; white-space: pre; }
#grid tbody td.rowhead.hl { background: #e8f0fe; color: #1a73e8; }
#grid thead th.hl { background: #e8f0fe; color: #1a73e8; }
#grid-wrap:focus { outline: none; }
.menu {
  position: fixed; background: #fff; border: 1px solid #dadce0; border-radius: 6px;
  box-shadow: 0 4px 16px rgba(0,0,0,.18); z-index: 100; min-width: 180px; padding: 4px 0;
}
.menu .mi { padding: 6px 14px; cursor: pointer; font-size: 13px; }
.menu .mi:hover { background: #f1f3f4; }
</style>
</head>
<body>
<div id="app">
  <div id="toolbar">
    <div class="tb-group" id="tb-file">
      <button id="btn-insert-row" title="Insert row above the selected one">Insert row</button>
      <button id="btn-delete-row" title="Delete the selected row">Delete row</button>
      <button id="btn-insert-col" title="Insert column left of the selected one">Insert column</button>
      <button id="btn-delete-col" title="Delete the selected column">Delete column</button>
    </div>
    <div class="tb-group" id="tb-help">
      <span class="tb-hint">Formulas: <code>=SUM(A1:A5, 10, B2)</code> · functions: SUM, AVG/AVERAGE, MIN, MAX, COUNT · right-click a header for row/column ops</span>
    </div>
  </div>
  <div id="fbar">
    <span id="cell-ref" title="Selected cell">A1</span>
    <span id="fx" title="Formula">fx</span>
    <input id="formula-input" spellcheck="false" autocomplete="off" placeholder="= expression">
  </div>
  <div id="grid-wrap">
    <table id="grid" cellspacing="0" cellpadding="0"></table>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./render":"src/render.ts"},"src/parser.ts":{"./consts":"src/consts.ts"},"src/model.ts":{"./parser":"src/parser.ts","./consts":"src/consts.ts"},"src/render.ts":{"./consts":"src/consts.ts","./parser":"src/parser.ts","./model":"src/model.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";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = start;
// Entry point: boot the spreadsheet.
const render = __importStar(require("./render"));
function start() {
    render.init();
}
start();
};

// ── module: src/consts.ts ──
__mods["src/consts.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ERR = exports.LAST_COL = exports.FIRST_COL = exports.SAVE_KEY = exports.ROW_HEIGHT = exports.COL_WIDTH = exports.GRID_ROWS = exports.GRID_COLS = void 0;
exports.colLetter = colLetter;
// =============================================================
// Tunable constants
// =============================================================
exports.GRID_COLS = 26; // A..Z
exports.GRID_ROWS = 100; // rows 1..100
exports.COL_WIDTH = 90; // px
exports.ROW_HEIGHT = 24; // px
exports.SAVE_KEY = "sheets.data.v1";
exports.FIRST_COL = "A";
exports.LAST_COL = String.fromCharCode(65 + exports.GRID_COLS - 1); // Z
exports.ERR = Object.freeze({
    REF: "#REF!",
    DIV0: "#DIV/0!",
    PARSE: "#ERR!",
    CYCLE: "#CYCLE!",
});
function colLetter(i) {
    return String.fromCharCode(65 + i);
}
};

// ── module: src/parser.ts ──
__mods["src/parser.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isCellRef = isCellRef;
exports.refToRC = refToRC;
exports.rcToRef = rcToRef;
exports.isLiveRef = isLiveRef;
exports.isDeadRef = isDeadRef;
exports.parseFormula = parseFormula;
exports.collectRefs = collectRefs;
exports.astToString = astToString;
// =============================================================
// Formula parser: tokenizer + recursive-descent. No eval().
// =============================================================
const consts_1 = require("./consts");
// ---- cell reference helpers ----
function isCellRef(s) {
    return /^[A-Za-z]\d+$/.test(s);
}
// Return {col,row} or null if invalid. col in 0..25, row in 1..GRID_ROWS.
function refToRC(ref) {
    const m = /^([A-Za-z])(\d+)$/.exec(ref);
    if (!m)
        return null;
    const col = m[1].toUpperCase().charCodeAt(0) - 65;
    const row = parseInt(m[2], 10);
    if (col < 0 || col > 25)
        return null;
    return { col, row };
}
function rcToRef(col, row) {
    if (col < 0 || col > 25)
        return (0, consts_1.colLetter)(0) + "0";
    return (0, consts_1.colLetter)(col) + row;
}
// Is this ref a live, in-bounds cell?
function isLiveRef(ref) {
    const rc = refToRC(ref);
    return rc !== null && rc.row >= 1 && rc.row <= consts_1.GRID_ROWS;
}
// Row 0 is the reserved "dead" ref that evaluates to #REF!.
function isDeadRef(ref) {
    const rc = refToRC(ref);
    return rc !== null && rc.row === 0;
}
function tokenize(src) {
    const toks = [];
    let i = 0;
    while (i < src.length) {
        const c = src[i];
        if (c === " " || c === "\t") {
            i++;
            continue;
        }
        if (c >= "0" && c <= "9") {
            let j = i;
            while (j < src.length && src[j] >= "0" && src[j] <= "9")
                j++;
            if (j < src.length && src[j] === ".") {
                j++;
                while (j < src.length && src[j] >= "0" && src[j] <= "9")
                    j++;
            }
            const num = parseFloat(src.slice(i, j));
            if (isNaN(num))
                return null;
            toks.push({ t: "num", v: num });
            i = j;
            continue;
        }
        if (/[A-Za-z]/.test(c)) {
            let j = i;
            while (j < src.length && /[A-Za-z0-9]/.test(src[j]))
                j++;
            toks.push({ t: "id", v: src.slice(i, j) });
            i = j;
            continue;
        }
        if (c === "(") {
            toks.push({ t: "lp" });
            i++;
            continue;
        }
        if (c === ")") {
            toks.push({ t: "rp" });
            i++;
            continue;
        }
        if (c === ",") {
            toks.push({ t: "comma" });
            i++;
            continue;
        }
        if (c === ":") {
            toks.push({ t: "colon" });
            i++;
            continue;
        }
        if (c === "+" || c === "-" || c === "*" || c === "/") {
            toks.push({ t: "op", v: c });
            i++;
            continue;
        }
        return null; // invalid char
    }
    toks.push({ t: "eof" });
    return toks;
}
// ---- recursive descent ----
class Parser {
    constructor(toks) {
        this.pos = 0;
        this.toks = toks;
    }
    peek() { return this.toks[this.pos]; }
    next() { return this.toks[this.pos++]; }
    atEnd() { return this.peek().t === "eof"; }
    match(t) {
        if (this.peek().t === t) {
            this.pos++;
            return true;
        }
        return false;
    }
    opVal() {
        const t = this.peek();
        return t.t === "op" ? t.v : "";
    }
    consumeOp() {
        const t = this.next();
        return t.v;
    }
    consumeId() {
        const t = this.next();
        return t.v;
    }
    parse() {
        const n = this.expr();
        if (!n)
            return null;
        if (!this.atEnd())
            return null;
        return n;
    }
    expr() {
        let left = this.term();
        if (!left)
            return null;
        while (this.peek().t === "op" && (this.opVal() === "+" || this.opVal() === "-")) {
            const op = this.consumeOp();
            const right = this.term();
            if (!right)
                return null;
            left = { type: "bin", op, left, right };
        }
        return left;
    }
    term() {
        let left = this.factor();
        if (!left)
            return null;
        while (this.peek().t === "op" && (this.opVal() === "*" || this.opVal() === "/")) {
            const op = this.consumeOp();
            const right = this.factor();
            if (!right)
                return null;
            left = { type: "bin", op, left, right };
        }
        return left;
    }
    factor() {
        if (this.peek().t === "op" && (this.opVal() === "-" || this.opVal() === "+")) {
            const op = this.consumeOp();
            const operand = this.factor();
            if (!operand)
                return null;
            return { type: "un", op, operand };
        }
        return this.primary();
    }
    primary() {
        const tk = this.peek();
        if (tk.t === "num") {
            this.next();
            return { type: "num", value: tk.v };
        }
        if (tk.t === "id") {
            this.next(); // consume the id token
            const name = (tk.v);
            if (this.peek().t === "lp") {
                this.next();
                const args = [];
                if (this.peek().t !== "rp") {
                    const first = this.expr();
                    if (!first)
                        return null;
                    args.push(first);
                    while (this.match("comma")) {
                        const a = this.expr();
                        if (!a)
                            return null;
                        args.push(a);
                    }
                }
                if (!this.match("rp"))
                    return null;
                return { type: "func", name: name.toUpperCase(), args };
            }
            else {
                if (!isCellRef(name))
                    return null;
                const from = name.toUpperCase();
                if (this.peek().t === "colon") {
                    this.next();
                    const nxt = this.next();
                    if (nxt.t !== "id")
                        return null;
                    const idTok = nxt;
                    if (!isCellRef(idTok.v))
                        return null;
                    const to = idTok.v.toUpperCase();
                    const a = refToRC(from);
                    const b = refToRC(to);
                    if (!a || !b)
                        return { type: "range", from, to };
                    const c1 = Math.min(a.col, b.col), r1 = Math.min(a.row, b.row);
                    const c2 = Math.max(a.col, b.col), r2 = Math.max(a.row, b.row);
                    return { type: "range", from: rcToRef(c1, r1), to: rcToRef(c2, r2) };
                }
                return { type: "cell", ref: from };
            }
        }
        if (tk.t === "lp") {
            this.next();
            const inner = this.expr();
            if (!inner)
                return null;
            if (!this.match("rp"))
                return null;
            return inner;
        }
        return null;
    }
}
// Parse a full formula body (without the leading '='). Returns AST or null on failure.
function parseFormula(body) {
    if (body.trim() === "")
        return null;
    const toks = tokenize(body);
    if (!toks)
        return null;
    const p = new Parser(toks);
    return p.parse();
}
// ---- collect every cell ref referenced by an AST (for dependency graph) ----
function collectRefs(node, out) {
    switch (node.type) {
        case "cell":
            out.add(node.ref);
            break;
        case "range":
            out.add(node.from);
            out.add(node.to);
            break;
        case "func":
            for (const a of node.args)
                collectRefs(a, out);
            break;
        case "bin":
            collectRefs(node.left, out);
            collectRefs(node.right, out);
            break;
        case "un":
            collectRefs(node.operand, out);
            break;
        case "num":
            break;
    }
}
// ---- print an AST back to a formula string (with safe parens) ----
function astToString(node) {
    switch (node.type) {
        case "num": return String(node.value);
        case "cell": return node.ref;
        case "range": return node.from + ":" + node.to;
        case "func": return node.name + "(" + node.args.map(astToString).join(",") + ")";
        case "bin": return "(" + astToString(node.left) + node.op + astToString(node.right) + ")";
        case "un": return node.op + "(" + astToString(node.operand) + ")";
    }
}
};

// ── module: src/model.ts ──
__mods["src/model.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.inputs = void 0;
exports.setCell = setCell;
exports.clearCell = clearCell;
exports.rawOf = rawOf;
exports.getCellDisplay = getCellDisplay;
exports.deleteRow = deleteRow;
exports.insertRow = insertRow;
exports.deleteCol = deleteCol;
exports.insertCol = insertCol;
exports.serialize = serialize;
exports.load = load;
exports.loadFromStorage = loadFromStorage;
exports.saveToStorage = saveToStorage;
exports.testDisplay = testDisplay;
// =============================================================
// Data model: raw inputs, dependency graph, recalculation,
// cycle detection, structural ops (insert/delete) and
// serialization. No DOM code here.
// =============================================================
const parser_1 = require("./parser");
const consts_1 = require("./consts");
// ---- raw inputs: cellId ("A1") -> raw string the user typed ----
exports.inputs = {};
// formula AST cache
const astCache = new Map();
// refs[c] = cell ids referenced by c's formula
const refs = new Map();
// dependents[c] = set of cells whose formulas reference c
const dependents = new Map();
// computed[c] = cached computed value for formula cells
const computed = new Map();
// ---- literals ----
function literalValue(raw) {
    const t = raw.trim();
    if (/^-?\d+(\.\d+)?$/.test(t))
        return { kind: "num", num: parseFloat(t) };
    return { kind: "text", text: raw };
}
function fmtNum(n) {
    if (!isFinite(n))
        return "∞";
    const r = parseFloat(n.toPrecision(12));
    return String(r);
}
// valueOf: current cell value used during evaluation. null => empty cell.
function valueOf(cell) {
    const raw = exports.inputs[cell];
    if (raw === undefined)
        return null;
    if (raw.startsWith("=")) {
        return computed.get(cell) ?? null;
    }
    return literalValue(raw);
}
// ---- evaluation (walks AST; refs read through valueOf) ----
function numOf(v) {
    return v.kind === "num" ? v.num : NaN;
}
function collect(node) {
    if (node.type === "range") {
        const arr = [];
        const a = (0, parser_1.refToRC)(node.from);
        const b = (0, parser_1.refToRC)(node.to);
        if (a && b && b.row >= 1) {
            const r0 = Math.max(a.row, 1);
            const c0 = Math.max(a.col, 0);
            for (let r = r0; r <= b.row; r++) {
                for (let c = c0; c <= b.col; c++) {
                    const v = valueOf((0, parser_1.rcToRef)(c, r));
                    if (v)
                        arr.push(v);
                }
            }
        }
        return arr;
    }
    const v = evalNode(node);
    return v === null ? [] : [v];
}
function applyFunc(name, args) {
    const flat = [];
    for (const a of args)
        flat.push(...collect(a));
    for (const v of flat)
        if (v.kind === "err")
            return v;
    const nums = flat.filter((v) => v.kind === "num").map(numOf);
    const sum = nums.reduce((x, y) => x + y, 0);
    switch (name) {
        case "SUM": return { kind: "num", num: sum };
        case "AVG":
        case "AVERAGE": return { kind: "num", num: nums.length ? sum / nums.length : 0 };
        case "MIN": return { kind: "num", num: nums.length ? Math.min(...nums) : 0 };
        case "MAX": return { kind: "num", num: nums.length ? Math.max(...nums) : 0 };
        case "COUNT": return { kind: "num", num: nums.length };
        default: return { kind: "err", code: consts_1.ERR.PARSE };
    }
}
function evalNode(node) {
    switch (node.type) {
        case "num":
            return { kind: "num", num: node.value };
        case "cell": {
            if (!(0, parser_1.isLiveRef)(node.ref))
                return { kind: "err", code: consts_1.ERR.REF };
            const v = valueOf(node.ref);
            return v ?? { kind: "num", num: 0 };
        }
        case "range":
            return { kind: "err", code: consts_1.ERR.PARSE };
        case "func":
            return applyFunc(node.name, node.args);
        case "un": {
            const v = evalNode(node.operand);
            if (v === null)
                return { kind: "num", num: 0 };
            if (v.kind === "err")
                return v;
            if (v.kind === "text")
                return { kind: "err", code: consts_1.ERR.PARSE };
            return { kind: "num", num: node.op === "-" ? -v.num : v.num };
        }
        case "bin": {
            const l = evalNode(node.left) ?? { kind: "num", num: 0 };
            const r = evalNode(node.right) ?? { kind: "num", num: 0 };
            if (l.kind === "err")
                return l;
            if (r.kind === "err")
                return r;
            const a = numOf(l), b = numOf(r);
            if (isNaN(a) || isNaN(b))
                return { kind: "err", code: consts_1.ERR.PARSE };
            let res;
            switch (node.op) {
                case "+":
                    res = a + b;
                    break;
                case "-":
                    res = a - b;
                    break;
                case "*":
                    res = a * b;
                    break;
                case "/":
                    if (b === 0)
                        return { kind: "err", code: consts_1.ERR.DIV0 };
                    res = a / b;
                    break;
                default: return { kind: "err", code: consts_1.ERR.PARSE };
            }
            return { kind: "num", num: res };
        }
    }
}
function computeCell(cell) {
    const raw = exports.inputs[cell];
    if (raw === undefined)
        return { kind: "num", num: 0 };
    if (!raw.startsWith("="))
        return literalValue(raw);
    let ast = astCache.get(cell);
    if (!ast) {
        ast = (0, parser_1.parseFormula)(raw.slice(1));
        astCache.set(cell, ast);
    }
    if (!ast)
        return { kind: "err", code: consts_1.ERR.PARSE };
    const v = evalNode(ast);
    return v ?? { kind: "num", num: 0 };
}
// ---- recalculation ----
function recompute(roots) {
    // collect affected cells: roots + all transitive dependents
    const affected = new Set();
    const stack = [];
    for (const r of roots) {
        if (!affected.has(r)) {
            affected.add(r);
            stack.push(r);
        }
    }
    while (stack.length) {
        const c = stack.pop();
        const d = dependents.get(c);
        if (d)
            for (const x of d) {
                if (!affected.has(x)) {
                    affected.add(x);
                    stack.push(x);
                }
            }
    }
    // topological sort over refs (restricted to affected) + cycle detection
    const color = new Map(); // 0 none, 1 visiting, 2 done
    const inCycle = new Set();
    const order = [];
    const pathStack = [];
    const pathIdx = new Map();
    const dfs = (c) => {
        color.set(c, 1);
        pathIdx.set(c, pathStack.length);
        pathStack.push(c);
        const rs = refs.get(c);
        if (rs) {
            for (const r of rs) {
                if (!affected.has(r))
                    continue;
                const rc = color.get(r);
                if (rc === 1) {
                    const start = pathIdx.get(r);
                    for (let i = start; i < pathStack.length; i++)
                        inCycle.add(pathStack[i]);
                }
                else if (rc === undefined) {
                    dfs(r);
                }
            }
        }
        pathStack.pop();
        pathIdx.delete(c);
        color.set(c, 2);
        order.push(c);
    };
    for (const c of affected) {
        if (color.get(c) === undefined)
            dfs(c);
    }
    // evaluate in topological order
    for (const c of order) {
        if (inCycle.has(c)) {
            computed.set(c, { kind: "err", code: consts_1.ERR.CYCLE });
        }
        else {
            computed.set(c, computeCell(c));
        }
    }
    return affected;
}
function recomputeAll() {
    recompute(Object.keys(exports.inputs));
}
// ---- graph rebuild helpers ----
function resetGraph() {
    astCache.clear();
    refs.clear();
    dependents.clear();
    computed.clear();
}
function registerFormula(cell, raw) {
    const ast = (0, parser_1.parseFormula)(raw.slice(1));
    astCache.set(cell, ast);
    const s = new Set();
    if (ast)
        (0, parser_1.collectRefs)(ast, s);
    const list = [...s];
    refs.set(cell, list);
    for (const r of list) {
        if (!dependents.has(r))
            dependents.set(r, new Set());
        dependents.get(r).add(cell);
    }
}
function registerAll() {
    for (const cell in exports.inputs) {
        const raw = exports.inputs[cell];
        if (raw.startsWith("="))
            registerFormula(cell, raw);
        else {
            astCache.set(cell, null);
            refs.set(cell, []);
        }
    }
}
// ---- public API ----
// Set a cell's raw content. Returns affected cell ids (for rendering).
function setCell(cell, raw) {
    // unregister old refs
    const oldRefs = refs.get(cell) || [];
    for (const r of oldRefs)
        dependents.get(r)?.delete(cell);
    if (raw === "") {
        delete exports.inputs[cell];
        astCache.delete(cell);
        refs.delete(cell);
        computed.delete(cell);
        return recompute([cell]);
    }
    exports.inputs[cell] = raw;
    if (raw.startsWith("=")) {
        registerFormula(cell, raw);
    }
    else {
        astCache.set(cell, null);
        refs.set(cell, []);
    }
    return recompute([cell]);
}
function clearCell(cell) {
    return setCell(cell, "");
}
function rawOf(cell) {
    return exports.inputs[cell];
}
function getCellDisplay(cell) {
    const raw = exports.inputs[cell];
    if (raw === undefined)
        return { text: "", align: "left", isError: false };
    if (raw.startsWith("=")) {
        const cv = computed.get(cell);
        if (cv?.kind === "err")
            return { text: cv.code, align: "left", isError: true };
        if (cv?.kind === "num")
            return { text: fmtNum(cv.num), align: "right", isError: false };
        if (cv?.kind === "text")
            return { text: cv.text, align: "left", isError: false };
        return { text: "", align: "left", isError: false };
    }
    const lit = literalValue(raw);
    if (lit.kind === "num")
        return { text: fmtNum(lit.num), align: "right", isError: false };
    return { text: raw, align: "left", isError: false };
}
function transformRef(ref, colMap, rowMap) {
    const rc = (0, parser_1.refToRC)(ref);
    if (!rc)
        return ref;
    const nc = colMap(rc.col);
    const nr = rowMap(rc.row);
    if (nc === null || nr === null || nc < 0 || nc > 25 || nr < 1)
        return (0, parser_1.rcToRef)(0, 0); // dead -> A0 -> #REF!
    return (0, parser_1.rcToRef)(nc, nr);
}
function transformNode(node, colMap, rowMap) {
    switch (node.type) {
        case "cell":
            return { type: "cell", ref: transformRef(node.ref, colMap, rowMap) };
        case "range":
            return {
                type: "range",
                from: transformRef(node.from, colMap, rowMap),
                to: transformRef(node.to, colMap, rowMap),
            };
        case "func":
            return { type: "func", name: node.name, args: node.args.map((a) => transformNode(a, colMap, rowMap)) };
        case "bin":
            return {
                type: "bin", op: node.op,
                left: transformNode(node.left, colMap, rowMap),
                right: transformNode(node.right, colMap, rowMap),
            };
        case "un":
            return { type: "un", op: node.op, operand: transformNode(node.operand, colMap, rowMap) };
        case "num":
            return node;
    }
}
// Build the moved raw-input table after a structural op.
function moveData(colMap, rowMap) {
    const ni = {};
    for (const cell in exports.inputs) {
        const raw = exports.inputs[cell];
        const rc = (0, parser_1.refToRC)(cell);
        if (!rc)
            continue;
        const nc = colMap(rc.col);
        const nr = rowMap(rc.row);
        if (nc === null || nr === null || nc < 0 || nc > 25 || nr < 1)
            continue; // deleted
        const nid = (0, parser_1.rcToRef)(nc, nr);
        let newRaw = raw;
        if (raw.startsWith("=")) {
            const ast = astCache.get(cell);
            if (ast)
                newRaw = "=" + (0, parser_1.astToString)(transformNode(ast, colMap, rowMap));
        }
        ni[nid] = newRaw;
    }
    return ni;
}
function applyStructure(colMap, rowMap) {
    exports.inputs = moveData(colMap, rowMap);
    resetGraph();
    registerAll();
    recomputeAll();
}
const idMap = (i) => i;
function deleteRow(R) {
    const rowMap = (r) => (r < R ? r : r === R ? null : r - 1);
    applyStructure(idMap, rowMap);
}
function insertRow(R) {
    const rowMap = (r) => (r >= R ? r + 1 : r);
    applyStructure(idMap, rowMap);
}
function deleteCol(C) {
    const colMap = (c) => (c < C ? c : c === C ? null : c - 1);
    applyStructure(colMap, idMap);
}
function insertCol(C) {
    const colMap = (c) => (c >= C ? c + 1 : c);
    applyStructure(colMap, idMap);
}
// ---- serialization to localStorage (raw formulas, not values) ----
function serialize() {
    const cells = Object.keys(exports.inputs)
        .sort((a, b) => (0, parser_1.refToRC)(a).row - (0, parser_1.refToRC)(b).row || (0, parser_1.refToRC)(a).col - (0, parser_1.refToRC)(b).col)
        .map((cell) => [cell, exports.inputs[cell]]);
    return JSON.stringify({ v: 1, cells });
}
function load(json) {
    try {
        const data = JSON.parse(json);
        if (!data || data.v !== 1 || !Array.isArray(data.cells))
            return false;
        const ni = {};
        for (const entry of data.cells) {
            if (!Array.isArray(entry) || entry.length !== 2)
                continue;
            const [cell, raw] = entry;
            if (typeof cell !== "string" || typeof raw !== "string")
                continue;
            if (!(0, parser_1.isCellRef)(cell))
                continue;
            ni[cell] = raw;
        }
        exports.inputs = ni;
        resetGraph();
        registerAll();
        recomputeAll();
        return true;
    }
    catch {
        return false;
    }
}
function loadFromStorage() {
    try {
        const s = localStorage.getItem(consts_1.SAVE_KEY);
        if (!s)
            return false;
        return load(s);
    }
    catch {
        return false;
    }
}
function saveToStorage() {
    try {
        localStorage.setItem(consts_1.SAVE_KEY, serialize());
    }
    catch {
        /* storage may be unavailable */
    }
}
// return a human key used by tests: computed value text of a cell
function testDisplay(cell) {
    return getCellDisplay(cell).text;
}
};

// ── module: src/render.ts ──
__mods["src/render.ts"] = function (exports, require, module) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.cellEl = cellEl;
exports.getSelected = getSelected;
exports.buildGrid = buildGrid;
exports.refreshCell = refreshCell;
exports.refreshAll = refreshAll;
exports.refreshCellAndDeps = refreshCellAndDeps;
exports.select = select;
exports.commit = commit;
exports.cancelEdit = cancelEdit;
exports.clearSelected = clearSelected;
exports.init = init;
// =============================================================
// Rendering + interaction: DOM grid, selection, editing,
// formula bar, header context menus.
// =============================================================
const consts_1 = require("./consts");
const parser_1 = require("./parser");
const model = __importStar(require("./model"));
let selected = "A1";
let editing = false;
let editText = "";
let overlayInput = null;
let syncing = false;
let menuEl = null;
const fbar = document.getElementById("formula-input");
const cellRefEl = document.getElementById("cell-ref");
const gridWrap = document.getElementById("grid-wrap");
const grid = document.getElementById("grid");
function cellEl(cell) {
    return document.getElementById("c-" + cell);
}
function getSelected() {
    return selected;
}
// ---- build the grid DOM ---------------------------------------
function buildGrid() {
    const colgroup = document.createElement("colgroup");
    const none = document.createElement("col");
    none.style.width = "46px";
    colgroup.appendChild(none);
    for (let c = 0; c < consts_1.GRID_COLS; c++) {
        const col = document.createElement("col");
        col.style.width = consts_1.COL_WIDTH + "px";
        colgroup.appendChild(col);
    }
    grid.appendChild(colgroup);
    const thead = document.createElement("thead");
    const htr = document.createElement("tr");
    const corner = document.createElement("th");
    corner.className = "corner";
    htr.appendChild(corner);
    for (let c = 0; c < consts_1.GRID_COLS; c++) {
        const th = document.createElement("th");
        th.textContent = (0, consts_1.colLetter)(c);
        th.dataset.col = String(c);
        th.addEventListener("click", () => { if (editing)
            commit(); select("A" + ((0, parser_1.refToRC)(selected).row)); });
        th.addEventListener("contextmenu", (e) => headerMenu(e, "col", c));
        htr.appendChild(th);
    }
    thead.appendChild(htr);
    grid.appendChild(thead);
    const tbody = document.createElement("tbody");
    for (let r = 1; r <= consts_1.GRID_ROWS; r++) {
        const tr = document.createElement("tr");
        const rh = document.createElement("td");
        rh.className = "rowhead";
        rh.textContent = String(r);
        rh.dataset.row = String(r);
        rh.addEventListener("click", () => { if (editing)
            commit(); select((0, consts_1.colLetter)(0) + r); });
        rh.addEventListener("contextmenu", (e) => headerMenu(e, "row", r));
        tr.appendChild(rh);
        for (let c = 0; c < consts_1.GRID_COLS; c++) {
            const id = (0, consts_1.colLetter)(c) + r;
            const td = document.createElement("td");
            td.className = "cell";
            td.id = "c-" + id;
            const txt = document.createElement("div");
            txt.className = "txt";
            td.appendChild(txt);
            td.dataset.cell = id;
            td.addEventListener("mousedown", (e) => {
                if (e.button === 2)
                    return;
                if (editing)
                    commit();
                select(id);
            });
            td.addEventListener("dblclick", () => { beginEdit("overlay", rawForSel()); });
            tr.appendChild(td);
        }
        tbody.appendChild(tr);
    }
    grid.appendChild(tbody);
    gridWrap.tabIndex = 0;
}
function rawForSel() {
    return model.rawOf(selected) ?? "";
}
// ---- display refresh ------------------------------------------
function refreshCell(cell) {
    const el = cellEl(cell);
    if (!el)
        return;
    const d = model.getCellDisplay(cell);
    const txt = el.querySelector(".txt");
    if (txt)
        txt.textContent = d.text;
    el.classList.toggle("num", d.align === "right");
    el.classList.toggle("err", d.isError);
}
function refreshAll() {
    for (let r = 1; r <= consts_1.GRID_ROWS; r++) {
        for (let c = 0; c < consts_1.GRID_COLS; c++) {
            refreshCell((0, consts_1.colLetter)(c) + r);
        }
    }
    updateHeaders();
}
function refreshCellAndDeps(cells) {
    for (const id of cells)
        refreshCell(id);
}
function updateHeaders() {
    const rc = (0, parser_1.refToRC)(selected);
    // clear highlights
    document.querySelectorAll("th.hl").forEach((el) => el.classList.remove("hl"));
    document.querySelectorAll("td.rowhead.hl").forEach((el) => el.classList.remove("hl"));
    const cth = document.querySelector(`th[data-col="${rc.col}"]`);
    const rth = document.querySelector(`td.rowhead[data-row="${rc.row}"]`);
    if (cth)
        cth.classList.add("hl");
    if (rth)
        rth.classList.add("hl");
}
// ---- selection ------------------------------------------------
function select(cell) {
    const old = cellEl(selected);
    if (old)
        old.classList.remove("selected");
    selected = cell;
    const el = cellEl(cell);
    if (el) {
        el.classList.add("selected");
        el.scrollIntoView({ block: "nearest", inline: "nearest" });
    }
    cellRefEl.textContent = cell;
    updateHeaders();
    if (!editing) {
        fbar.value = rawForSel();
    }
    gridWrap.focus();
}
function moveSel(dr, dc) {
    const rc = (0, parser_1.refToRC)(selected);
    const nr = Math.max(1, Math.min(consts_1.GRID_ROWS, rc.row + dr));
    const nc = Math.max(0, Math.min(consts_1.GRID_COLS - 1, rc.col + dc));
    select((0, parser_1.rcToRef)(nc, nr));
}
// ---- editing --------------------------------------------------
function beginEdit(focus, initial) {
    commit(); // commit any prior edit first
    editing = true;
    editText = initial;
    syncing = true;
    fbar.value = editText;
    syncing = false;
    fbar.classList.add("editing");
    const td = cellEl(selected);
    if (td) {
        td.classList.add("editing");
        const txt = td.querySelector(".txt");
        if (txt)
            txt.style.visibility = "hidden";
        overlayInput = document.createElement("input");
        overlayInput.className = "ov";
        overlayInput.spellcheck = false;
        overlayInput.autocomplete = "off";
        overlayInput.value = editText;
        overlayInput.addEventListener("input", () => {
            if (syncing)
                return;
            syncing = true;
            editText = overlayInput.value;
            if (fbar.value !== editText)
                fbar.value = editText;
            syncing = false;
        });
        td.appendChild(overlayInput);
    }
    if (focus === "overlay" && overlayInput) {
        overlayInput.focus();
        try {
            overlayInput.setSelectionRange(editText.length, editText.length);
        }
        catch { /* noop */ }
    }
    else {
        fbar.focus();
    }
}
function endEdit() {
    editing = false;
    fbar.classList.remove("editing");
    const td = cellEl(selected);
    if (td) {
        td.classList.remove("editing");
        const txt = td.querySelector(".txt");
        if (txt)
            txt.style.visibility = "";
        if (overlayInput && overlayInput.parentNode === td)
            td.removeChild(overlayInput);
    }
    overlayInput = null;
}
function commit(moveDr = 0, moveDc = 0) {
    if (!editing) {
        if (moveDr || moveDc)
            moveSel(moveDr, moveDc);
        return false;
    }
    const text = editText;
    endEdit();
    const affected = model.setCell(selected, text);
    refreshCell(selected);
    refreshCellAndDeps(affected);
    if (moveDr || moveDc)
        moveSel(moveDr, moveDc);
    else
        gridWrap.focus();
    return true;
}
function cancelEdit() {
    if (!editing)
        return;
    endEdit();
    refreshCell(selected);
    gridWrap.focus();
}
// ---- clear selected cell --------------------------------------
function clearSelected() {
    if (editing) {
        endEdit();
    }
    const affected = model.clearCell(selected);
    refreshCell(selected);
    refreshCellAndDeps(affected);
    gridWrap.focus();
}
// ---- formula bar wiring ---------------------------------------
fbar.addEventListener("focus", () => {
    if (!editing)
        beginEdit("bar", rawForSel());
});
fbar.addEventListener("input", () => {
    if (syncing)
        return;
    if (!editing)
        return;
    syncing = true;
    editText = fbar.value;
    if (overlayInput && overlayInput.value !== editText)
        overlayInput.value = editText;
    syncing = false;
});
// ---- header context menu --------------------------------------
function headerMenu(e, kind, index) {
    e.preventDefault();
    const items = [];
    if (kind === "row") {
        items.push({
            label: "Insert row above",
            action: () => { endEdit(); model.insertRow(index); afterStructure(); select((0, parser_1.rcToRef)(0, index)); },
        });
        items.push({
            label: "Delete row " + index,
            action: () => { endEdit(); model.deleteRow(index); afterStructure(); },
        });
    }
    else {
        items.push({
            label: "Insert column left",
            action: () => { endEdit(); model.insertCol(index); afterStructure(); select((0, parser_1.rcToRef)(index, 1)); },
        });
        items.push({
            label: "Delete column " + (0, consts_1.colLetter)(index),
            action: () => { endEdit(); model.deleteCol(index); afterStructure(); },
        });
    }
    openMenu(e.clientX, e.clientY, items);
}
function afterStructure() {
    refreshAll();
    updateHeaders();
    gridWrap.focus();
    // selection cell may have moved; re-highlight selected
    select(selected);
}
function openMenu(x, y, items) {
    closeMenu();
    menuEl = document.createElement("div");
    menuEl.className = "menu";
    for (const it of items) {
        const mi = document.createElement("div");
        mi.className = "mi";
        mi.textContent = it.label;
        mi.addEventListener("click", () => { closeMenu(); it.action(); });
        menuEl.appendChild(mi);
    }
    document.body.appendChild(menuEl);
    const r = menuEl.getBoundingClientRect();
    menuEl.style.left = Math.min(x, window.innerWidth - r.width - 8) + "px";
    menuEl.style.top = Math.min(y, window.innerHeight - r.height - 8) + "px";
}
function closeMenu() {
    if (menuEl && menuEl.parentNode)
        menuEl.parentNode.removeChild(menuEl);
    menuEl = null;
}
// close menu & commit when clicking elsewhere
document.addEventListener("mousedown", (e) => {
    if (menuEl && !e.target.closest(".menu"))
        closeMenu();
});
// ---- keyboard ---------------------------------------------
function isPrintable(e) {
    if (e.ctrlKey || e.metaKey || e.altKey)
        return false;
    if (e.key.length !== 1)
        return false;
    return true;
}
document.addEventListener("keydown", (e) => {
    const tgt = e.target;
    const inInput = tgt.tagName === "INPUT";
    if (inInput && tgt !== fbar && tgt !== overlayInput)
        return; // other inputs (none)
    if (e.key === "Escape") {
        e.preventDefault();
        closeMenu();
        if (editing)
            cancelEdit();
        else
            gridWrap.focus();
        return;
    }
    if (e.key === "Enter") {
        e.preventDefault();
        commit(1, 0);
        return;
    }
    if (e.key === "Tab") {
        e.preventDefault();
        commit(0, 1);
        return;
    }
    if (editing || inInput) {
        // while editing, let other keys type normally
        return;
    }
    if (e.key === "ArrowDown") {
        e.preventDefault();
        moveSel(1, 0);
        return;
    }
    if (e.key === "ArrowUp") {
        e.preventDefault();
        moveSel(-1, 0);
        return;
    }
    if (e.key === "ArrowRight") {
        e.preventDefault();
        moveSel(0, 1);
        return;
    }
    if (e.key === "ArrowLeft") {
        e.preventDefault();
        moveSel(0, -1);
        return;
    }
    if (e.key === "Delete") {
        e.preventDefault();
        clearSelected();
        return;
    }
    if (e.key === "Backspace") {
        e.preventDefault();
        beginEdit("overlay", "");
        return;
    }
    if (isPrintable(e)) {
        e.preventDefault();
        beginEdit("overlay", e.key);
        return;
    }
});
// ---- toolbar --------------------------------------------------
function bindToolbar() {
    document.getElementById("btn-insert-row").onclick = () => {
        endEdit();
        const r = (0, parser_1.refToRC)(selected).row;
        model.insertRow(r);
        afterStructure();
        select((0, parser_1.rcToRef)(0, r));
    };
    document.getElementById("btn-delete-row").onclick = () => {
        endEdit();
        const r = (0, parser_1.refToRC)(selected).row;
        model.deleteRow(r);
        afterStructure();
    };
    document.getElementById("btn-insert-col").onclick = () => {
        endEdit();
        const c = (0, parser_1.refToRC)(selected).col;
        model.insertCol(c);
        afterStructure();
        select((0, parser_1.rcToRef)(c, 1));
    };
    document.getElementById("btn-delete-col").onclick = () => {
        endEdit();
        const c = (0, parser_1.refToRC)(selected).col;
        model.deleteCol(c);
        afterStructure();
    };
}
function init() {
    buildGrid();
    bindToolbar();
    // load persisted
    const loaded = model.loadFromStorage();
    refreshAll();
    select("A1");
    // save on unload
    window.addEventListener("beforeunload", () => model.saveToStorage());
    // autosave after edits
    setInterval(() => model.saveToStorage(), 2000);
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"deepseek/deepseek-v4-flash-0731","provider":"openrouter","persona":"typescript","sessionId":"dbc1a959-e2b7-4711-ae6d-60276e04ba69","tokensIn":3410981,"tokensOut":61705,"tokensTotal":3472686,"cost":0.08716394799999998,"turns":66,"toolCalls":69,"failedToolCalls":0,"timestamp":"2026-08-01T16:10:05.284Z"} -->