Spreadsheet
Qwen 3.6 27b MTP · typescript

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>