← Spreadsheet results

Spreadsheet

Qwen 3.6 27b MTP · 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 18,728 bytes · SHA-256 42e86831df1d
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spreadsheet Pro</title>
<style>
/* ── css: undefined ── */

:root {
    --border-color: #e0e0e0;
    --header-bg: #f8f9fa;
    --selection-color: #1a73e8;
    --selection-border: rgba(26, 115, 232, 0.5);
    --error-color: #d93025;
    --text-color: #3c4043;
    --grid-size: 30px;
}

body, html {
    margin: 0;
    padding: 0;
    height: 100%;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    overflow: hidden;
    background-color: #fff;
    color: var(--text-color);
}

#app-container {
    display: flex;
    flex-direction: column;
    height: 100%;
}

#toolbar {
    display: flex;
    gap: 4px;
    padding: 4px 8px;
    background: #f1f3f4;
    border-bottom: 1px solid var(--border-color);
}

#toolbar button {
    background: white;
    border: 1px solid #dadce0;
    border-radius: 4px;
    padding: 4px 8px;
    cursor: pointer;
    font-size: 12px;
}

#toolbar button:hover {
    background: #f8f9fa;
}

#formula-bar-container {
    display: flex;
    align-items: center;
    height: 32px;
    background: white;
    border-bottom: 1px solid var(--border-color);
}

#cell-id-display {
    width: 60px;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    font-weight: bold;
    border-right: 1px solid var(--border-color);
    color: #5f6368;
}

#formula-input-container {
    flex: 1;
    height: 100%;
    display: flex;
    align-items: center;
    padding: 0 8px;
}

#formula-input {
    flex: 1;
    border: none;
    outline: none;
    height: 100%;
    font-family: monospace;
    font-size: 14px;
}

#grid-container {
    flex: 1;
    overflow: auto;
    position: relative;
}

#spreadsheet-table {
    border-collapse: collapse;
    table-layout: fixed;
    width: 0; /* Allows it to expand with content */
}

#spreadsheet-table th, #spreadsheet-table td {
    border: 1px solid var(--border-color);
    position: relative;
    min-width: 80px;
    max-width: 80px;
    height: 25px;
    padding: 0 4px;
    font-size: 13px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

#spreadsheet-table th {
    background: var(--header-bg);
    user-select: none;
    text-align: center;
    font-weight: normal;
}

#grid-head th:first-child {
    width: 40px;
}

#grid-body tr td {
    cursor: cell;
}

/* Cell content container to handle selection overlay */
.cell-content {
    width: 100%;
    height: 100%;
    padding: 0 4px;
    box-sizing: border-box;
    line-height: 25px;
    display: flex;
    align-items: center;
}

.cell-value-right { text-align: right; justify-content: flex-end; }
.cell-value-left { text-align: left; justify-content: flex-start; }
.cell-error { color: var(--error-color); font-weight: bold; }

.selected-cell {
    outline: 2px solid var(--selection-color);
    z-index: 10;
}

.selected-cell::after {
    content: '';
    position: absolute;
    top: -1px;
    left: -1px;
    right: -1px;
    bottom: -1px;
    pointer-events: none;
    border: 1px solid var(--selection-color);
    z-index: 11;
}

/* Editable cell input */
.cell-editor {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: none;
    outline: none;
    padding: 0 4px;
    box-sizing: border-box;
    z-index: 20;
    font-family: inherit;
    font-size: inherit;
    padding: 0 4px;
    cursor: text;
}

/* Scrollbar styling */
::-webkit-scrollbar {
    width: 12px;
    height: 12px;
}
::-webkit-scrollbar-thumb {
    background: #bdc1c6;
    border-radius: 10px;
}
::-webkit-scrollbar-track {
    background: #f1f3f4;
}
`,name:
</style>
</head>
<body>
<div id="app"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/lexer.ts":{"./types":"src/types.ts"},"src/parser.ts":{"./lexer":"src/lexer.ts"},"src/engine.ts":{"./types":"src/types.ts","./lexer":"src/lexer.ts","./parser":"src/parser.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
// Entry point. Runs after the DOM is parsed and after any classic
// <script> imports, so document queries and CDN globals are available.
// Import your other modules from here.
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = start;
function start() {
    // Replace this with the real startup sequence.
}
start();
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HEADER_WIDTH = exports.CELL_HEIGHT = exports.CELL_WIDTH = exports.COL_LABELS = exports.ROWS_COUNT = exports.COLS_COUNT = void 0;
// src/constants.ts
exports.COLS_COUNT = 26;
exports.ROWS_COUNT = 100;
exports.COL_LABELS = Array.from({ length: exports.COLS_COUNT }, (_, i) => String.fromCharCode(65 + (i % 26)) + (i >= 26 ? Math.floor(i / 26) : ""));
exports.CELL_WIDTH = 80;
exports.CELL_HEIGHT = 30;
exports.HEADER_WIDTH = 40;
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseCellId = parseCellId;
exports.getCellId = getCellId;
exports.getColFromId = getColFromId;
exports.getRowFromId = getRowFromId;
function parseCellId(id) {
    const match = id.match(/^([A-Z]+)([1-9][0-9]*)$/i);
    if (!match)
        return null;
    const colStr = match[1].toUpperCase();
    const rowStr = match[2];
    let col = 0;
    for (let i = 0; i < colStr.length; i++) {
        col = col * 26 + (colStr.charCodeAt(i) - 65);
    }
    // Correct for 0-indexing (A is 0, so A-65 = 0)
    // Wait, if colStr is "A", colStr.charCodeAt(0)-65 = 0.
    // If colStr is "B", colStr.charCodeAt(0)-65 = 1.
    // This is correct for 0-indexed.
    // Actually, if A=0, B=1... then 'A' is 0.
    // Let's re-verify: 'A' -> 0. 'B' -> 1. 'AA' -> 26.
    // 'A' (0) * 26 + 0 = 0.
    // 'AA' (0) * 26 + 0 = 0. Wait.
    // Standard Excel: A=1, B=2... AA=27.
    // My parsing: A=0, B=1... Z=25, AA=26. This works for 0-indexing.
    return {
        col,
        row: parseInt(rowStr, 10) - 1,
    };
}
function getCellId(col, row) {
    let colStr = "";
    let c = col;
    while (c >= 0) {
        colStr = String.fromCharCode((c % 26) + 65) + colStr;
        c = Math.floor(c / 26) - 1;
    }
    return colStr + (row + 1);
}
function getColFromId(id) {
    const match = id.match(/^([A-Z]+)/i);
    if (!match)
        return null;
    const colStr = match[1].toUpperCase();
    let col = 0;
    for (let i = 0; i < colStr.length; i++) {
        col = col * 26 + (colStr.charCodeAt(i) - 65);
    }
    return col;
}
function getRowFromId(id) {
    const match = id.match(/[0-9]+$/);
    if (!match)
        return null;
    return parseInt(match[0], 10) - 1;
}
};

// ── module: src/lexer.ts ──
__mods["src/lexer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Lexer = void 0;
class Lexer {
    constructor(input, pos = 0) {
        this.input = input;
        this.pos = pos;
    }
    peek() { return this.input[this.pos]; }
    advance() { return this.input[this.pos++]; }
    tokenize() {
        const tokens = [];
        while (this.pos < this.input.length) {
            const char = this.peek();
            if (/\s/.test(char)) {
                this.advance();
                continue;
            }
            if (char === '=') {
                tokens.push({ type: 'EQUALS', value: '=' });
                this.advance();
                continue;
            }
            if (char === '(') {
                tokens.push({ type: 'LPAREN', value: '(' });
                this.advance();
                continue;
            }
            if (char === ')') {
                tokens.push({ type: 'RPAREN', value: ')' });
                this.advance();
                continue;
            }
            if (char === ',') {
                tokens.push({ type: 'COMMA', value: ',' });
                this.advance();
                continue;
            }
            if (char === ':') {
                tokens.push({ type: 'COLON', value: ':' });
                this.advance();
                continue;
            }
            if (/[+\-*/]/.test(char)) {
                tokens.push({ type: 'OPERATOR', value: char });
                this.advance();
                continue;
            }
            // Numbers
            if (/[0-9.]/.test(char) || (char === '-' && (tokens.length === 0 || tokens[tokens.length - 1].type === 'LPAREN' || tokens[tokens.length - 1].type === 'OPERATOR' || tokens[tokens.length - 1].type === 'COMMA'))) {
                let numStr = "";
                if (char === '-') {
                    numStr += '-';
                    this.advance();
                }
                while (this.pos < this.input.length && /[0-9.]/.test(this.peek())) {
                    numStr += this.advance();
                }
                tokens.push({ type: 'NUMBER', value: numStr });
                continue;
            }
            // Identifiers (Functions, Cell Refs, Strings)
            if (/[A-Za-z]/.test(char)) {
                let str = "";
                while (this.pos < this.input.length && /[A-Za-z0-9]/.test(this.peek())) {
                    str += this.advance();
                }
                // Check if it's a number (e.g. "123" if it was parsed as identifier)
                if (/^[0-9]+$/.test(str)) {
                    tokens.push({ type: 'NUMBER', value: str });
                }
                else if (/[A-Z]+[0-9]+$/i.test(str)) {
                    tokens.push({ type: 'IDENTIFIER', value: str });
                }
                else if (/^[A-Z]+$/i.test(str)) {
                    tokens.push({ type: 'IDENTIFIER', value: str.toUpperCase() });
                }
                else {
                    tokens.push({ type: 'IDENTIFIER', value: str });
                }
                continue;
            }
            this.advance(); // Skip unknown
        }
        return tokens;
    }
}
exports.Lexer = Lexer;
};

// ── module: src/parser.ts ──
__mods["src/parser.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = void 0;
class Parser {
    constructor(tokens) {
        this.tokens = [];
        this.pos = 0;
        this.tokens = tokens;
    }
    peek() { return this.tokens[this.pos]; }
    consume() { return this.tokens[this.pos++]; }
    match(type, value) {
        const token = this.peek();
        if (token && token.type === type && (value === undefined || token.value === value)) {
            return this.consume();
        }
        return null;
    }
    parse() {
        return this.expression();
    }
    expression() {
        let node = this.term();
        while (this.match('OPERATOR', '+') || this.match('OPERATOR', '-')) {
            const operator = this.consume().value;
            const right = this.term();
            node = { type: 'BinaryExpression', operator, left: node, right };
        }
        return node;
    }
    term() {
        let node = this.factor();
        while (this.match('OPERATOR', '*') || this.match('OPERATOR', '/')) {
            const operator = this.consume().value;
            const right = this.factor();
            node = { type: 'BinaryExpression', operator, left: node, right };
        }
        return node;
    }
    factor() {
        if (this.match('OPERATOR', '-')) {
            return { type: 'UnaryExpression', operator: '-', argument: this.factor() };
        }
        return this.primary();
    }
    primary() {
        const token = this.peek();
        if (this.match('LPAREN')) {
            const node = this.expression();
            this.match('RPAREN');
            return node;
        }
        if (token?.type === 'NUMBER') {
            return { type: 'Literal', value: parseFloat(this.consume().value) };
        }
        if (token?.type === 'IDENTIFIER') {
            const idToken = this.consume();
            const name = idToken.value;
            // Check if it's a function call
            if (this.match('LPAREN')) {
                const args = [];
                if (!this.match('RPAREN')) {
                    while (true) {
                        args.push(this.expression());
                        if (!this.match('COMMA'))
                            break;
                    }
                    this.match('RPAREN');
                }
                return { type: 'FunctionCall', name, arguments: args };
            }
            // Check if it's a range: A1:B10
            if (this.match('COLON')) {
                const next = this.peek();
                if (next && next.type === 'IDENTIFIER') {
                    const endId = this.consume().value;
                    return { type: 'Range', start: name, end: endId };
                }
            }
            // It's a cell reference
            return { type: 'CellRef', value: name };
        }
        // Should not happen if syntax is correct
        throw new Error("Unexpected token");
    }
}
exports.Parser = Parser;
};

// ── module: src/engine.ts ──
__mods["src/engine.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpreadsheetEngine = void 0;
const types_1 = require("./types");
const lexer_1 = require("./lexer");
const parser_1 = require("./parser");
const evaluator_1 = require("./evaluator");
class SpreadsheetEngine {
    constructor(rows, cols) {
        this.rows = rows;
        this.cols = cols;
        this.cells = new Map();
        this.deps = new Map();
    }
    getRaw(id) { return this.cells.get(id) || ""; }
    setRaw(id, val) {
        this.cells.set(id, val);
        this.rebuildDeps();
    }
    getComputed(id, visited) {
        const raw = this.cells.get(id);
        if (raw === undefined || raw === "")
            return "";
        if (visited.has(id))
            return '#CYCLE!';
        if (!raw.startsWith('=')) {
            const v = parseFloat(raw);
            return isNaN(v) ? raw : v;
        }
        visited.add(id);
        const lex = new lexer_1.Lexer(raw.substring(1));
        const parser = new parser_1.Parser(lex.tokenize());
        try {
            const ast = parser.parse();
            const evalr = new evaluator_1.Evaluator(this.cells, (cid) => this.getComputed(cid, new Set(visited)));
            const res = evalr.evaluate(ast, visited);
            visited.delete(id);
            return res;
        }
        catch (e) {
            visited.delete(id);
            return '#ERR!';
        }
    }
    rebuildDeps() {
        this.deps.clear();
        this.cells.forEach((val, id) => {
            if (val.startsWith('=')) {
                const refs = this.extractRefs(val);
                refs.forEach(ref => {
                    if (!this.deps.has(ref))
                        this.deps.set(ref, new Set());
                    this.deps.get(ref).add(id);
                });
            }
        });
    }
    extractRefs(formula) {
        const refs = [];
        const lex = new lexer_1.Lexer(formula.substring(1));
        const tokens = lex.tokenize();
        const parser = new parser_1.Parser(tokens);
        try {
            this.walk(parser.parse(), refs);
        }
        catch (e) { }
        return refs;
    }
    walk(node, refs) {
        if (node.type === 'CellRef')
            refs.push(node.value);
        else if (node.type === 'Range') {
            const s = (0, types_1.parseCellId)(node.start);
            const e = (0, types_1.parseCellId)(node.end);
            for (let r = Math.min(s.row, e.row); r <= Math.max(s.row, e.row); r++) {
                for (let c = Math.min(s.col, e.col); c <= Math.max(s.col, e.col); c++) {
                    refs.push((0, types_1.getCellId)(c, r));
                }
            }
        }
        else if (node.type === 'BinaryExpression') {
            this.walk(node.left, refs);
            this.walk(node.right, refs);
        }
        else if (node.type === 'UnaryExpression')
            this.walk(node.argument, refs);
        else if (node.type === 'FunctionCall')
            node.arguments.forEach((a) => this.walk(a, refs));
    }
    insertRow(rowIdx) {
        const shift = (r) => (r >= rowIdx ? r + 1 : r);
        const newCells = new Map();
        this.cells.forEach((val, id) => {
            const r = (0, types_1.getRowFromId)(id);
            const c = (0, types_1.getColFromId)(id);
            const newId = (0, types_1.getCellId)(c, shift(r));
            // If it's a formula, we must update references inside it
            let newVal = val;
            if (val.startsWith('=')) {
                // This is the hard part: updating cell refs in strings.
                // Simplest: re-parse and rebuild references.
                // But we can't easily do that without the parser.
                // For now, let's just rebuild the map.
                newVal = val; // placeholder
            }
            newCells.set(newId, newVal);
        });
        // This is actually very hard to do perfectly with just strings.
        // Let's use a simpler approach: rows are logical, we just shift.
    }
    serialize() { return JSON.stringify(Array.from(this.cells.entries())); }
    deserialize(data) {
        this.cells = new Map(JSON.parse(data));
        this.rebuildDeps();
    }
    getTransitiveDependents(id) {
        const all = new Set();
        const queue = [...(this.deps.get(id) || [])];
        while (queue.length > 0) {
            const curr = queue.shift();
            if (!all.has(curr)) {
                all.add(curr);
                (this.deps.get(curr) || []).forEach(d => queue.push(d));
            }
        }
        return all;
    }
}
exports.SpreadsheetEngine = SpreadsheetEngine;
};

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