@pixerate/editor 0.16.2 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-3A7KVDEA.js → chunk-AIZUKWTL.js} +527 -114
- package/dist/chunk-AIZUKWTL.js.map +1 -0
- package/dist/{chunk-TYG7K6WK.cjs → chunk-YQCI4CCN.cjs} +562 -149
- package/dist/chunk-YQCI4CCN.cjs.map +1 -0
- package/dist/index.cjs +12 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/spreadsheet/index.cjs +12 -2
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +71 -2
- package/dist/spreadsheet/index.d.ts +71 -2
- package/dist/spreadsheet/index.js +11 -1
- package/package.json +1 -1
- package/dist/chunk-3A7KVDEA.js.map +0 -1
- package/dist/chunk-TYG7K6WK.cjs.map +0 -1
|
@@ -41,9 +41,10 @@ function tokenizeFormula(formula) {
|
|
|
41
41
|
str += input[i];
|
|
42
42
|
i++;
|
|
43
43
|
}
|
|
44
|
-
if (i
|
|
45
|
-
|
|
44
|
+
if (i >= input.length) {
|
|
45
|
+
throw new Error(`Unterminated string starting at position ${start + offset}`);
|
|
46
46
|
}
|
|
47
|
+
i++;
|
|
47
48
|
tokens.push({
|
|
48
49
|
type: "STRING",
|
|
49
50
|
value: str,
|
|
@@ -51,6 +52,15 @@ function tokenizeFormula(formula) {
|
|
|
51
52
|
});
|
|
52
53
|
continue;
|
|
53
54
|
}
|
|
55
|
+
if (char === "#") {
|
|
56
|
+
const match = /^#(?:REF!|DIV\/0!|VALUE!|NAME\?|N\/A|NUM!|CYCLE!|ERROR!)/.exec(input.slice(i));
|
|
57
|
+
if (!match) {
|
|
58
|
+
throw new Error(`Unexpected character '#' at position ${i + offset}`);
|
|
59
|
+
}
|
|
60
|
+
tokens.push({ type: "ERROR", value: match[0], position: i + offset });
|
|
61
|
+
i += match[0].length;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
54
64
|
if (char === "[") {
|
|
55
65
|
const start = i;
|
|
56
66
|
i++;
|
|
@@ -148,13 +158,14 @@ function tokenizeFormula(formula) {
|
|
|
148
158
|
});
|
|
149
159
|
continue;
|
|
150
160
|
}
|
|
151
|
-
i
|
|
161
|
+
throw new Error(`Unexpected character '${char}' at position ${i + offset}`);
|
|
152
162
|
}
|
|
153
163
|
tokens.push({ type: "EOF", value: "", position: input.length + offset });
|
|
154
164
|
return tokens;
|
|
155
165
|
}
|
|
156
166
|
|
|
157
167
|
// src/spreadsheet/formula/parser.ts
|
|
168
|
+
var WHOLE_COLUMN_END_ROW = 1048576;
|
|
158
169
|
function parseCellReference(ref) {
|
|
159
170
|
const match = ref.match(/^([A-Za-z]+)(\d+)$/);
|
|
160
171
|
if (!match) {
|
|
@@ -166,12 +177,23 @@ function parseCellReference(ref) {
|
|
|
166
177
|
};
|
|
167
178
|
}
|
|
168
179
|
function parseRange(rangeStr) {
|
|
169
|
-
const [start, end] = rangeStr.split(":");
|
|
180
|
+
const [start, end] = rangeStr.replace(/\$/g, "").split(":");
|
|
181
|
+
const raw = rangeStr.toUpperCase();
|
|
182
|
+
if (/^[A-Za-z]+$/.test(start) && /^[A-Za-z]+$/.test(end)) {
|
|
183
|
+
return {
|
|
184
|
+
type: "Range",
|
|
185
|
+
raw,
|
|
186
|
+
startCol: start.toUpperCase(),
|
|
187
|
+
startRow: 1,
|
|
188
|
+
endCol: end.toUpperCase(),
|
|
189
|
+
endRow: WHOLE_COLUMN_END_ROW
|
|
190
|
+
};
|
|
191
|
+
}
|
|
170
192
|
const startRef = parseCellReference(start);
|
|
171
193
|
const endRef = parseCellReference(end);
|
|
172
194
|
return {
|
|
173
195
|
type: "Range",
|
|
174
|
-
raw
|
|
196
|
+
raw,
|
|
175
197
|
startCol: startRef.col,
|
|
176
198
|
startRow: startRef.row,
|
|
177
199
|
endCol: endRef.col,
|
|
@@ -264,6 +286,10 @@ var FormulaParser = (_class = class {
|
|
|
264
286
|
this.advance();
|
|
265
287
|
return { type: "Literal", value: token.value === "TRUE" };
|
|
266
288
|
}
|
|
289
|
+
if (token.type === "ERROR") {
|
|
290
|
+
this.advance();
|
|
291
|
+
return { type: "Literal", value: token.value };
|
|
292
|
+
}
|
|
267
293
|
if (token.type === "CELL_REF") {
|
|
268
294
|
this.advance();
|
|
269
295
|
const parsed = parseCellReference(token.value);
|
|
@@ -303,7 +329,7 @@ var FormulaParser = (_class = class {
|
|
|
303
329
|
args
|
|
304
330
|
};
|
|
305
331
|
}
|
|
306
|
-
return { type: "Literal", value:
|
|
332
|
+
return { type: "Literal", value: "#NAME?" };
|
|
307
333
|
}
|
|
308
334
|
if (this.match("LPAREN")) {
|
|
309
335
|
const expr = this.expression();
|
|
@@ -357,6 +383,57 @@ function parseFormula(formula) {
|
|
|
357
383
|
}
|
|
358
384
|
|
|
359
385
|
// src/spreadsheet/formula/evaluator.ts
|
|
386
|
+
var FORMULA_ERRORS = /* @__PURE__ */ new Set([
|
|
387
|
+
"#DIV/0!",
|
|
388
|
+
"#VALUE!",
|
|
389
|
+
"#REF!",
|
|
390
|
+
"#NAME?",
|
|
391
|
+
"#N/A",
|
|
392
|
+
"#NUM!",
|
|
393
|
+
"#CYCLE!",
|
|
394
|
+
"#ERROR!"
|
|
395
|
+
]);
|
|
396
|
+
function isFormulaError(value) {
|
|
397
|
+
return typeof value === "string" && FORMULA_ERRORS.has(value);
|
|
398
|
+
}
|
|
399
|
+
var isBlank = (value) => value === "" || value === null || value === void 0;
|
|
400
|
+
function toNumber(value) {
|
|
401
|
+
if (isFormulaError(value)) return value;
|
|
402
|
+
if (isBlank(value)) return 0;
|
|
403
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
404
|
+
if (typeof value === "number") return value;
|
|
405
|
+
const trimmed = String(value).trim();
|
|
406
|
+
const num = trimmed === "" ? NaN : Number(trimmed);
|
|
407
|
+
return isNaN(num) ? "#VALUE!" : num;
|
|
408
|
+
}
|
|
409
|
+
function toBoolean(value) {
|
|
410
|
+
if (isFormulaError(value)) return value;
|
|
411
|
+
if (isBlank(value)) return false;
|
|
412
|
+
if (typeof value === "boolean") return value;
|
|
413
|
+
if (typeof value === "number") return value !== 0;
|
|
414
|
+
const upper = String(value).trim().toUpperCase();
|
|
415
|
+
if (upper === "TRUE") return true;
|
|
416
|
+
if (upper === "FALSE") return false;
|
|
417
|
+
return "#VALUE!";
|
|
418
|
+
}
|
|
419
|
+
var toText = (value) => isBlank(value) ? "" : typeof value === "boolean" ? value ? "TRUE" : "FALSE" : String(value);
|
|
420
|
+
function compareValues(left, right) {
|
|
421
|
+
const rank = (v) => typeof v === "boolean" ? 2 : typeof v === "string" ? 1 : 0;
|
|
422
|
+
const l = isBlank(left) ? typeof right === "string" ? "" : 0 : left;
|
|
423
|
+
const r = isBlank(right) ? typeof l === "string" ? "" : 0 : right;
|
|
424
|
+
if (rank(l) !== rank(r)) return rank(l) - rank(r);
|
|
425
|
+
if (typeof l === "string") {
|
|
426
|
+
const a = l.toLowerCase();
|
|
427
|
+
const b = r.toLowerCase();
|
|
428
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
429
|
+
}
|
|
430
|
+
return Number(l) - Number(r);
|
|
431
|
+
}
|
|
432
|
+
function roundHalfAwayFromZero(value, decimals) {
|
|
433
|
+
const factor = Math.pow(10, decimals);
|
|
434
|
+
const scaled = Number((Math.abs(value) * factor).toPrecision(15));
|
|
435
|
+
return Math.sign(value) * Math.round(scaled) / factor;
|
|
436
|
+
}
|
|
360
437
|
function colNameToIndex(col) {
|
|
361
438
|
let index = 0;
|
|
362
439
|
const upper = col.toUpperCase();
|
|
@@ -407,42 +484,59 @@ var FormulaEvaluator = class {
|
|
|
407
484
|
return this.context.resolveProperty(node.property);
|
|
408
485
|
}
|
|
409
486
|
case "UnaryOp": {
|
|
410
|
-
const val = this.evaluateNode(node.argument);
|
|
411
|
-
if (
|
|
412
|
-
if (node.op === "
|
|
487
|
+
const val = toNumber(this.evaluateNode(node.argument));
|
|
488
|
+
if (isFormulaError(val)) return val;
|
|
489
|
+
if (node.op === "-") return -val;
|
|
490
|
+
if (node.op === "+") return val;
|
|
413
491
|
throw new Error(`Unsupported unary operator: ${node.op}`);
|
|
414
492
|
}
|
|
415
493
|
case "BinaryOp": {
|
|
416
494
|
const left = this.evaluateNode(node.left);
|
|
495
|
+
if (isFormulaError(left)) return left;
|
|
417
496
|
const right = this.evaluateNode(node.right);
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
497
|
+
if (isFormulaError(right)) return right;
|
|
498
|
+
if (["+", "-", "*", "/", "^"].includes(node.op)) {
|
|
499
|
+
const a = toNumber(left);
|
|
500
|
+
if (isFormulaError(a)) return a;
|
|
501
|
+
const b = toNumber(right);
|
|
502
|
+
if (isFormulaError(b)) return b;
|
|
503
|
+
const x = a;
|
|
504
|
+
const y = b;
|
|
505
|
+
let result;
|
|
506
|
+
switch (node.op) {
|
|
507
|
+
case "+":
|
|
508
|
+
result = x + y;
|
|
509
|
+
break;
|
|
510
|
+
case "-":
|
|
511
|
+
result = x - y;
|
|
512
|
+
break;
|
|
513
|
+
case "*":
|
|
514
|
+
result = x * y;
|
|
515
|
+
break;
|
|
516
|
+
case "/":
|
|
517
|
+
if (y === 0) return "#DIV/0!";
|
|
518
|
+
result = x / y;
|
|
519
|
+
break;
|
|
520
|
+
default:
|
|
521
|
+
result = Math.pow(x, y);
|
|
429
522
|
}
|
|
430
|
-
|
|
431
|
-
|
|
523
|
+
return Number.isFinite(result) ? result : "#NUM!";
|
|
524
|
+
}
|
|
525
|
+
switch (node.op) {
|
|
432
526
|
case "&":
|
|
433
|
-
return
|
|
527
|
+
return toText(left) + toText(right);
|
|
434
528
|
case "=":
|
|
435
|
-
return
|
|
529
|
+
return compareValues(left, right) === 0;
|
|
436
530
|
case "<>":
|
|
437
|
-
return
|
|
531
|
+
return compareValues(left, right) !== 0;
|
|
438
532
|
case "<":
|
|
439
|
-
return
|
|
533
|
+
return compareValues(left, right) < 0;
|
|
440
534
|
case "<=":
|
|
441
|
-
return
|
|
535
|
+
return compareValues(left, right) <= 0;
|
|
442
536
|
case ">":
|
|
443
|
-
return
|
|
537
|
+
return compareValues(left, right) > 0;
|
|
444
538
|
case ">=":
|
|
445
|
-
return
|
|
539
|
+
return compareValues(left, right) >= 0;
|
|
446
540
|
default:
|
|
447
541
|
throw new Error(`Unsupported binary operator: ${node.op}`);
|
|
448
542
|
}
|
|
@@ -461,25 +555,36 @@ var FormulaEvaluator = class {
|
|
|
461
555
|
for (const arg of args) {
|
|
462
556
|
const val = this.evaluateNode(arg);
|
|
463
557
|
if (Array.isArray(val)) {
|
|
464
|
-
items.push(
|
|
558
|
+
for (const item of val) items.push(item);
|
|
465
559
|
} else {
|
|
466
560
|
items.push(val);
|
|
467
561
|
}
|
|
468
562
|
}
|
|
469
563
|
return items;
|
|
470
564
|
};
|
|
565
|
+
const numericArgs = () => {
|
|
566
|
+
const nums = [];
|
|
567
|
+
for (const item of evaluateAndFlatten()) {
|
|
568
|
+
if (isFormulaError(item)) return item;
|
|
569
|
+
if (isBlank(item) || typeof item === "boolean") continue;
|
|
570
|
+
const num = typeof item === "number" ? item : Number(String(item).trim());
|
|
571
|
+
if (String(item).trim() !== "" && !isNaN(num)) nums.push(num);
|
|
572
|
+
}
|
|
573
|
+
return nums;
|
|
574
|
+
};
|
|
575
|
+
const evaluateArg = (index) => this.evaluateNode(args[index]);
|
|
471
576
|
switch (fnName) {
|
|
472
577
|
case "SUM": {
|
|
473
|
-
const
|
|
578
|
+
const nums = numericArgs();
|
|
579
|
+
if (typeof nums === "string") return nums;
|
|
474
580
|
let total = 0;
|
|
475
|
-
for (const
|
|
476
|
-
const num = Number(item);
|
|
477
|
-
if (!isNaN(num)) total += num;
|
|
478
|
-
}
|
|
581
|
+
for (const num of nums) total += num;
|
|
479
582
|
return total;
|
|
480
583
|
}
|
|
481
584
|
case "AVERAGE": {
|
|
482
585
|
const items = evaluateAndFlatten();
|
|
586
|
+
const error = items.find(isFormulaError);
|
|
587
|
+
if (error) return error;
|
|
483
588
|
let total = 0;
|
|
484
589
|
let count = 0;
|
|
485
590
|
for (const item of items) {
|
|
@@ -512,17 +617,21 @@ var FormulaEvaluator = class {
|
|
|
512
617
|
}
|
|
513
618
|
return count;
|
|
514
619
|
}
|
|
515
|
-
case "MIN":
|
|
516
|
-
const items = evaluateAndFlatten().map(Number).filter((n) => !isNaN(n));
|
|
517
|
-
return items.length === 0 ? 0 : Math.min(...items);
|
|
518
|
-
}
|
|
620
|
+
case "MIN":
|
|
519
621
|
case "MAX": {
|
|
520
|
-
const
|
|
521
|
-
|
|
622
|
+
const nums = numericArgs();
|
|
623
|
+
if (typeof nums === "string") return nums;
|
|
624
|
+
if (nums.length === 0) return 0;
|
|
625
|
+
let result = nums[0];
|
|
626
|
+
for (const num of nums) {
|
|
627
|
+
result = fnName === "MIN" ? Math.min(result, num) : Math.max(result, num);
|
|
628
|
+
}
|
|
629
|
+
return result;
|
|
522
630
|
}
|
|
523
631
|
case "IF": {
|
|
524
632
|
if (args.length < 2) throw new Error("IF requires at least 2 arguments");
|
|
525
|
-
const condition =
|
|
633
|
+
const condition = toBoolean(evaluateArg(0));
|
|
634
|
+
if (isFormulaError(condition)) return condition;
|
|
526
635
|
if (condition) {
|
|
527
636
|
return this.evaluateNode(args[1]);
|
|
528
637
|
} else {
|
|
@@ -531,41 +640,51 @@ var FormulaEvaluator = class {
|
|
|
531
640
|
}
|
|
532
641
|
case "CONCAT": {
|
|
533
642
|
const items = evaluateAndFlatten();
|
|
534
|
-
|
|
643
|
+
const error = items.find(isFormulaError);
|
|
644
|
+
if (error) return error;
|
|
645
|
+
return items.map(toText).join("");
|
|
535
646
|
}
|
|
536
647
|
case "UPPER": {
|
|
537
648
|
if (args.length !== 1) throw new Error("UPPER requires 1 argument");
|
|
538
|
-
const val =
|
|
539
|
-
|
|
649
|
+
const val = evaluateArg(0);
|
|
650
|
+
if (isFormulaError(val)) return val;
|
|
651
|
+
return toText(val).toUpperCase();
|
|
540
652
|
}
|
|
541
653
|
case "LOWER": {
|
|
542
654
|
if (args.length !== 1) throw new Error("LOWER requires 1 argument");
|
|
543
|
-
const val =
|
|
544
|
-
|
|
655
|
+
const val = evaluateArg(0);
|
|
656
|
+
if (isFormulaError(val)) return val;
|
|
657
|
+
return toText(val).toLowerCase();
|
|
545
658
|
}
|
|
546
659
|
case "TRIM": {
|
|
547
660
|
if (args.length !== 1) throw new Error("TRIM requires 1 argument");
|
|
548
|
-
const val =
|
|
549
|
-
|
|
661
|
+
const val = evaluateArg(0);
|
|
662
|
+
if (isFormulaError(val)) return val;
|
|
663
|
+
return toText(val).trim();
|
|
550
664
|
}
|
|
551
665
|
case "LEN": {
|
|
552
666
|
if (args.length !== 1) throw new Error("LEN requires 1 argument");
|
|
553
|
-
const val =
|
|
554
|
-
|
|
667
|
+
const val = evaluateArg(0);
|
|
668
|
+
if (isFormulaError(val)) return val;
|
|
669
|
+
return toText(val).length;
|
|
555
670
|
}
|
|
556
671
|
case "ROUND": {
|
|
557
672
|
if (args.length < 1) throw new Error("ROUND requires at least 1 argument");
|
|
558
|
-
const num =
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
|
|
673
|
+
const num = toNumber(evaluateArg(0));
|
|
674
|
+
if (isFormulaError(num)) return num;
|
|
675
|
+
const decimals = args.length > 1 ? toNumber(evaluateArg(1)) : 0;
|
|
676
|
+
if (isFormulaError(decimals)) return decimals;
|
|
677
|
+
return roundHalfAwayFromZero(num, Math.trunc(decimals));
|
|
562
678
|
}
|
|
563
679
|
case "ABS": {
|
|
564
680
|
if (args.length !== 1) throw new Error("ABS requires 1 argument");
|
|
565
|
-
|
|
681
|
+
const num = toNumber(evaluateArg(0));
|
|
682
|
+
return isFormulaError(num) ? num : Math.abs(num);
|
|
566
683
|
}
|
|
567
684
|
case "TODAY": {
|
|
568
|
-
|
|
685
|
+
const now = /* @__PURE__ */ new Date();
|
|
686
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
687
|
+
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
569
688
|
}
|
|
570
689
|
case "DAYS": {
|
|
571
690
|
if (args.length !== 2) throw new Error("DAYS requires 2 arguments (end_date, start_date)");
|
|
@@ -575,17 +694,24 @@ var FormulaEvaluator = class {
|
|
|
575
694
|
return Math.round(diffTime / (1e3 * 60 * 60 * 24));
|
|
576
695
|
}
|
|
577
696
|
default:
|
|
578
|
-
|
|
697
|
+
return "#NAME?";
|
|
579
698
|
}
|
|
580
699
|
}
|
|
581
700
|
};
|
|
582
701
|
|
|
583
702
|
// src/spreadsheet/formula/dependency-graph.ts
|
|
703
|
+
var columnDependencyKey = (col) => `${col.toUpperCase()}:`;
|
|
584
704
|
function extractCellDependencies(node) {
|
|
585
705
|
const deps = [];
|
|
586
706
|
const visit = (curr) => {
|
|
587
707
|
if (curr.type === "CellRef") {
|
|
588
708
|
deps.push({ col: curr.col, row: curr.row });
|
|
709
|
+
} else if (curr.type === "Range" && curr.endRow >= WHOLE_COLUMN_END_ROW) {
|
|
710
|
+
const startC = colNameToIndex(curr.startCol);
|
|
711
|
+
const endC = colNameToIndex(curr.endCol);
|
|
712
|
+
for (let c = Math.min(startC, endC); c <= Math.max(startC, endC); c++) {
|
|
713
|
+
deps.push({ col: indexToColName(c), row: 0 });
|
|
714
|
+
}
|
|
589
715
|
} else if (curr.type === "Range") {
|
|
590
716
|
const startC = colNameToIndex(curr.startCol);
|
|
591
717
|
const endC = colNameToIndex(curr.endCol);
|
|
@@ -630,7 +756,7 @@ var DependencyGraph = (_class2 = class {constructor() { _class2.prototype.__init
|
|
|
630
756
|
const deps = extractCellDependencies(ast);
|
|
631
757
|
const newDeps = /* @__PURE__ */ new Set();
|
|
632
758
|
for (const d of deps) {
|
|
633
|
-
const depKey = `${d.col}${d.row}`.toUpperCase();
|
|
759
|
+
const depKey = d.row === 0 ? columnDependencyKey(d.col) : `${d.col}${d.row}`.toUpperCase();
|
|
634
760
|
newDeps.add(depKey);
|
|
635
761
|
if (!this.dependents.has(depKey)) {
|
|
636
762
|
this.dependents.set(depKey, /* @__PURE__ */ new Set());
|
|
@@ -641,6 +767,11 @@ var DependencyGraph = (_class2 = class {constructor() { _class2.prototype.__init
|
|
|
641
767
|
} catch (e) {
|
|
642
768
|
}
|
|
643
769
|
}
|
|
770
|
+
/** Removes every registered formula and dependency. */
|
|
771
|
+
clear() {
|
|
772
|
+
this.dependencies.clear();
|
|
773
|
+
this.dependents.clear();
|
|
774
|
+
}
|
|
644
775
|
getDependencies(cellKey) {
|
|
645
776
|
return Array.from(this.dependencies.get(cellKey.toUpperCase()) || []);
|
|
646
777
|
}
|
|
@@ -714,8 +845,191 @@ var DependencyGraph = (_class2 = class {constructor() { _class2.prototype.__init
|
|
|
714
845
|
}
|
|
715
846
|
return { order, hasCycle: cycleDetected };
|
|
716
847
|
}
|
|
848
|
+
/**
|
|
849
|
+
* Plans a recalculation over `cellKeys` (and, with `includeDependents`, every
|
|
850
|
+
* cell that transitively depends on them). Returns the keys in evaluation
|
|
851
|
+
* order (dependencies first) and the set of keys that are part of a cycle,
|
|
852
|
+
* which must be marked `#CYCLE!` instead of evaluated. Every member of a
|
|
853
|
+
* cycle is reported, not just the cell that closed it.
|
|
854
|
+
*/
|
|
855
|
+
getEvaluationPlan(cellKeys, options = {}) {
|
|
856
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
857
|
+
const queue = [];
|
|
858
|
+
for (const key of cellKeys) {
|
|
859
|
+
const upper = key.toUpperCase();
|
|
860
|
+
if (!nodes.has(upper)) {
|
|
861
|
+
nodes.add(upper);
|
|
862
|
+
queue.push(upper);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (options.includeDependents) {
|
|
866
|
+
while (queue.length > 0) {
|
|
867
|
+
const node = queue.pop();
|
|
868
|
+
const direct = [
|
|
869
|
+
..._nullishCoalesce(this.dependents.get(node), () => ( [])),
|
|
870
|
+
..._nullishCoalesce(this.dependents.get(columnDependencyKey(node.replace(/\d+$/, ""))), () => ( []))
|
|
871
|
+
];
|
|
872
|
+
for (const dep of direct) {
|
|
873
|
+
if (!nodes.has(dep)) {
|
|
874
|
+
nodes.add(dep);
|
|
875
|
+
queue.push(dep);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
const neighbors = (node) => {
|
|
881
|
+
const result = [];
|
|
882
|
+
for (const dep of _nullishCoalesce(this.dependencies.get(node), () => ( []))) {
|
|
883
|
+
if (dep.endsWith(":")) {
|
|
884
|
+
const col = dep.slice(0, -1);
|
|
885
|
+
for (const candidate of nodes) {
|
|
886
|
+
if (candidate.replace(/\d+$/, "") === col) result.push(candidate);
|
|
887
|
+
}
|
|
888
|
+
} else if (nodes.has(dep)) {
|
|
889
|
+
result.push(dep);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return result;
|
|
893
|
+
};
|
|
894
|
+
const order = [];
|
|
895
|
+
const cyclic = /* @__PURE__ */ new Set();
|
|
896
|
+
const index = /* @__PURE__ */ new Map();
|
|
897
|
+
const low = /* @__PURE__ */ new Map();
|
|
898
|
+
const stack = [];
|
|
899
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
900
|
+
let counter = 0;
|
|
901
|
+
for (const root of nodes) {
|
|
902
|
+
if (index.has(root)) continue;
|
|
903
|
+
const work = [];
|
|
904
|
+
const enter = (node) => {
|
|
905
|
+
index.set(node, counter);
|
|
906
|
+
low.set(node, counter);
|
|
907
|
+
counter++;
|
|
908
|
+
stack.push(node);
|
|
909
|
+
onStack.add(node);
|
|
910
|
+
work.push({ node, edges: neighbors(node), next: 0 });
|
|
911
|
+
};
|
|
912
|
+
enter(root);
|
|
913
|
+
while (work.length > 0) {
|
|
914
|
+
const frame = work[work.length - 1];
|
|
915
|
+
if (frame.next < frame.edges.length) {
|
|
916
|
+
const target = frame.edges[frame.next++];
|
|
917
|
+
if (!index.has(target)) {
|
|
918
|
+
enter(target);
|
|
919
|
+
} else if (onStack.has(target)) {
|
|
920
|
+
low.set(frame.node, Math.min(low.get(frame.node), index.get(target)));
|
|
921
|
+
}
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
work.pop();
|
|
925
|
+
if (work.length > 0) {
|
|
926
|
+
const parent = work[work.length - 1].node;
|
|
927
|
+
low.set(parent, Math.min(low.get(parent), low.get(frame.node)));
|
|
928
|
+
}
|
|
929
|
+
if (low.get(frame.node) === index.get(frame.node)) {
|
|
930
|
+
const component = [];
|
|
931
|
+
let member;
|
|
932
|
+
do {
|
|
933
|
+
member = stack.pop();
|
|
934
|
+
onStack.delete(member);
|
|
935
|
+
component.push(member);
|
|
936
|
+
} while (member !== frame.node);
|
|
937
|
+
const isCycle = component.length > 1 || frame.edges.includes(frame.node);
|
|
938
|
+
for (const key of component) {
|
|
939
|
+
order.push(key);
|
|
940
|
+
if (isCycle) cyclic.add(key);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return { order, cyclic };
|
|
946
|
+
}
|
|
717
947
|
}, _class2);
|
|
718
948
|
|
|
949
|
+
// src/spreadsheet/formula/references.ts
|
|
950
|
+
var CELL_PART = /^(\$?)([A-Za-z]+)(\$?)(\d+)$/;
|
|
951
|
+
var COLUMN_PART = /^(\$?)([A-Za-z]+)$/;
|
|
952
|
+
var REF_CHAR = /[A-Za-z0-9_$.:]/;
|
|
953
|
+
function parsePart(text) {
|
|
954
|
+
const cell = CELL_PART.exec(text);
|
|
955
|
+
if (cell) {
|
|
956
|
+
return {
|
|
957
|
+
colAbs: cell[1],
|
|
958
|
+
col: colNameToIndex(cell[2]),
|
|
959
|
+
rowAbs: cell[3],
|
|
960
|
+
row: parseInt(cell[4], 10) - 1
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
const column = COLUMN_PART.exec(text);
|
|
964
|
+
if (column) {
|
|
965
|
+
return { colAbs: column[1], col: colNameToIndex(column[2]), rowAbs: "", row: null };
|
|
966
|
+
}
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
function formatPart(part) {
|
|
970
|
+
const col = `${part.colAbs}${indexToColName(part.col)}`;
|
|
971
|
+
return part.row === null ? col : `${col}${part.rowAbs}${part.row + 1}`;
|
|
972
|
+
}
|
|
973
|
+
function shiftCoordinate(value, change) {
|
|
974
|
+
if (change.type === "insert") {
|
|
975
|
+
return value >= change.index ? value + 1 : value;
|
|
976
|
+
}
|
|
977
|
+
if (value === change.index) return null;
|
|
978
|
+
return value > change.index ? value - 1 : value;
|
|
979
|
+
}
|
|
980
|
+
function shiftSpan(lo, hi, change) {
|
|
981
|
+
if (change.type === "insert") {
|
|
982
|
+
return [lo >= change.index ? lo + 1 : lo, hi >= change.index ? hi + 1 : hi];
|
|
983
|
+
}
|
|
984
|
+
if (lo === change.index && hi === change.index) return null;
|
|
985
|
+
return [lo > change.index ? lo - 1 : lo, hi >= change.index ? hi - 1 : hi];
|
|
986
|
+
}
|
|
987
|
+
function rewriteReference(text, change) {
|
|
988
|
+
const [startText, endText] = text.split(":");
|
|
989
|
+
const start = parsePart(startText);
|
|
990
|
+
if (!start) return text;
|
|
991
|
+
const key = change.axis === "row" ? "row" : "col";
|
|
992
|
+
if (endText === void 0) {
|
|
993
|
+
if (start[key] === null) return text;
|
|
994
|
+
const shifted = shiftCoordinate(start[key], change);
|
|
995
|
+
if (shifted === null) return "#REF!";
|
|
996
|
+
return formatPart({ ...start, [key]: shifted });
|
|
997
|
+
}
|
|
998
|
+
const end = parsePart(endText);
|
|
999
|
+
if (!end) return text;
|
|
1000
|
+
if (start[key] === null || end[key] === null) return text;
|
|
1001
|
+
const startValue = start[key];
|
|
1002
|
+
const endValue = end[key];
|
|
1003
|
+
const span = shiftSpan(Math.min(startValue, endValue), Math.max(startValue, endValue), change);
|
|
1004
|
+
if (span === null) return "#REF!";
|
|
1005
|
+
const [lo, hi] = span;
|
|
1006
|
+
const newStart = { ...start, [key]: startValue <= endValue ? lo : hi };
|
|
1007
|
+
const newEnd = { ...end, [key]: startValue <= endValue ? hi : lo };
|
|
1008
|
+
return `${formatPart(newStart)}:${formatPart(newEnd)}`;
|
|
1009
|
+
}
|
|
1010
|
+
function shiftFormulaReferences(formula, change) {
|
|
1011
|
+
if (!formula.startsWith("=")) return formula;
|
|
1012
|
+
let tokens;
|
|
1013
|
+
try {
|
|
1014
|
+
tokens = tokenizeFormula(formula);
|
|
1015
|
+
} catch (e2) {
|
|
1016
|
+
return formula;
|
|
1017
|
+
}
|
|
1018
|
+
let result = "";
|
|
1019
|
+
let cursor = 0;
|
|
1020
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1021
|
+
const token = tokens[i];
|
|
1022
|
+
if (token.type !== "CELL_REF" && token.type !== "RANGE") continue;
|
|
1023
|
+
if (_optionalChain([tokens, 'access', _7 => _7[i + 1], 'optionalAccess', _8 => _8.type]) === "LPAREN") continue;
|
|
1024
|
+
let end = token.position;
|
|
1025
|
+
while (end < formula.length && REF_CHAR.test(formula[end])) end++;
|
|
1026
|
+
const original = formula.slice(token.position, end);
|
|
1027
|
+
result += formula.slice(cursor, token.position) + rewriteReference(original, change);
|
|
1028
|
+
cursor = end;
|
|
1029
|
+
}
|
|
1030
|
+
return result + formula.slice(cursor);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
719
1033
|
// src/spreadsheet/datasource/index.ts
|
|
720
1034
|
var DataSourceBinder = (_class3 = class {constructor() { _class3.prototype.__init5.call(this); }
|
|
721
1035
|
__init5() {this.dataSources = /* @__PURE__ */ new Map()}
|
|
@@ -749,9 +1063,9 @@ var DataSourceBinder = (_class3 = class {constructor() { _class3.prototype.__ini
|
|
|
749
1063
|
for (const record of records) {
|
|
750
1064
|
const existing = existingRecordRowMap.get(record.id);
|
|
751
1065
|
nextRows.push({
|
|
752
|
-
id: _optionalChain([existing, 'optionalAccess',
|
|
1066
|
+
id: _optionalChain([existing, 'optionalAccess', _9 => _9.id]) || `row_${record.id}`,
|
|
753
1067
|
index: rowIndex++,
|
|
754
|
-
height: _optionalChain([existing, 'optionalAccess',
|
|
1068
|
+
height: _optionalChain([existing, 'optionalAccess', _10 => _10.height]) || 32,
|
|
755
1069
|
recordId: record.id,
|
|
756
1070
|
type: "record"
|
|
757
1071
|
});
|
|
@@ -803,7 +1117,12 @@ var SpreadsheetController = (_class4 = class {
|
|
|
803
1117
|
__init12() {this.undoStack = []}
|
|
804
1118
|
__init13() {this.redoStack = []}
|
|
805
1119
|
__init14() {this.isApplyingHistory = false}
|
|
806
|
-
|
|
1120
|
+
// Set while importing TSV: history and recalculation are done once at the end.
|
|
1121
|
+
__init15() {this.batching = false}
|
|
1122
|
+
__init16() {this.pendingCommits = []}
|
|
1123
|
+
// Row being evaluated, so [property] references resolve per row.
|
|
1124
|
+
__init17() {this.evaluatingRowIndex = null}
|
|
1125
|
+
constructor(options = {}) {;_class4.prototype.__init6.call(this);_class4.prototype.__init7.call(this);_class4.prototype.__init8.call(this);_class4.prototype.__init9.call(this);_class4.prototype.__init10.call(this);_class4.prototype.__init11.call(this);_class4.prototype.__init12.call(this);_class4.prototype.__init13.call(this);_class4.prototype.__init14.call(this);_class4.prototype.__init15.call(this);_class4.prototype.__init16.call(this);_class4.prototype.__init17.call(this);
|
|
807
1126
|
const defaultCols = [
|
|
808
1127
|
{ id: "col_a", key: "A", title: "A", width: 120, type: "freeform" },
|
|
809
1128
|
{ id: "col_b", key: "B", title: "B", width: 120, type: "freeform" },
|
|
@@ -817,13 +1136,13 @@ var SpreadsheetController = (_class4 = class {
|
|
|
817
1136
|
type: "freeform"
|
|
818
1137
|
}));
|
|
819
1138
|
this.document = {
|
|
820
|
-
id: _optionalChain([options, 'access',
|
|
821
|
-
name: _optionalChain([options, 'access',
|
|
822
|
-
columns: _optionalChain([options, 'access',
|
|
823
|
-
rows: _optionalChain([options, 'access',
|
|
824
|
-
cells: _optionalChain([options, 'access',
|
|
825
|
-
createdAt: _optionalChain([options, 'access',
|
|
826
|
-
updatedAt: _optionalChain([options, 'access',
|
|
1139
|
+
id: _optionalChain([options, 'access', _11 => _11.document, 'optionalAccess', _12 => _12.id]) || `sheet_${Date.now()}`,
|
|
1140
|
+
name: _optionalChain([options, 'access', _13 => _13.document, 'optionalAccess', _14 => _14.name]) || "Sheet 1",
|
|
1141
|
+
columns: _optionalChain([options, 'access', _15 => _15.document, 'optionalAccess', _16 => _16.columns]) || defaultCols,
|
|
1142
|
+
rows: _optionalChain([options, 'access', _17 => _17.document, 'optionalAccess', _18 => _18.rows]) || defaultRows,
|
|
1143
|
+
cells: _optionalChain([options, 'access', _19 => _19.document, 'optionalAccess', _20 => _20.cells]) || {},
|
|
1144
|
+
createdAt: _optionalChain([options, 'access', _21 => _21.document, 'optionalAccess', _22 => _22.createdAt]) || Date.now(),
|
|
1145
|
+
updatedAt: _optionalChain([options, 'access', _23 => _23.document, 'optionalAccess', _24 => _24.updatedAt]) || Date.now()
|
|
827
1146
|
};
|
|
828
1147
|
this.primaryDataSourceId = options.primaryDataSourceId;
|
|
829
1148
|
this.onDocumentChange = options.onDocumentChange;
|
|
@@ -887,10 +1206,14 @@ var SpreadsheetController = (_class4 = class {
|
|
|
887
1206
|
return this.document.rows.find((r) => r.id === rowIdOrIndex);
|
|
888
1207
|
}
|
|
889
1208
|
getCell(rowId, colId) {
|
|
890
|
-
const existing = _optionalChain([this, 'access',
|
|
1209
|
+
const existing = _optionalChain([this, 'access', _25 => _25.document, 'access', _26 => _26.cells, 'access', _27 => _27[rowId], 'optionalAccess', _28 => _28[colId]]);
|
|
891
1210
|
if (existing) return existing;
|
|
892
1211
|
const col = this.document.columns.find((c) => c.id === colId);
|
|
893
1212
|
const row = this.document.rows.find((r) => r.id === rowId);
|
|
1213
|
+
return this.getDerivedCell(row, col);
|
|
1214
|
+
}
|
|
1215
|
+
/** Value of a cell with no stored data: bound record value, column formula, or blank. */
|
|
1216
|
+
getDerivedCell(row, col) {
|
|
894
1217
|
if (col && row && col.type === "bound" && col.binding && row.recordId) {
|
|
895
1218
|
const boundVal = this.binder.resolveBoundValue(
|
|
896
1219
|
col.binding.dataSource,
|
|
@@ -910,7 +1233,7 @@ var SpreadsheetController = (_class4 = class {
|
|
|
910
1233
|
format: col.format
|
|
911
1234
|
};
|
|
912
1235
|
}
|
|
913
|
-
return { raw: "", value: "", format: _optionalChain([col, 'optionalAccess',
|
|
1236
|
+
return { raw: "", value: "", format: _optionalChain([col, 'optionalAccess', _29 => _29.format]) };
|
|
914
1237
|
}
|
|
915
1238
|
setCellValue(rowId, colId, raw) {
|
|
916
1239
|
this.recordSnapshot();
|
|
@@ -919,7 +1242,7 @@ var SpreadsheetController = (_class4 = class {
|
|
|
919
1242
|
}
|
|
920
1243
|
const col = this.document.columns.find((c) => c.id === colId);
|
|
921
1244
|
const row = this.document.rows.find((r) => r.id === rowId);
|
|
922
|
-
if (_optionalChain([col, 'optionalAccess',
|
|
1245
|
+
if (_optionalChain([col, 'optionalAccess', _30 => _30.type]) === "bound" && col.binding && _optionalChain([row, 'optionalAccess', _31 => _31.recordId])) {
|
|
923
1246
|
this.binder.commitBoundValue(
|
|
924
1247
|
col.binding.dataSource,
|
|
925
1248
|
row.recordId,
|
|
@@ -932,13 +1255,17 @@ var SpreadsheetController = (_class4 = class {
|
|
|
932
1255
|
this.document.cells[rowId][colId] = {
|
|
933
1256
|
raw,
|
|
934
1257
|
value: raw,
|
|
935
|
-
format: _optionalChain([col, 'optionalAccess',
|
|
1258
|
+
format: _optionalChain([col, 'optionalAccess', _32 => _32.format])
|
|
936
1259
|
};
|
|
937
1260
|
this.document.updatedAt = Date.now();
|
|
1261
|
+
if (this.batching) {
|
|
1262
|
+
this.pendingCommits.push([rowId, colId, raw]);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
938
1265
|
this.recalculateCell(rowId, colId);
|
|
939
1266
|
this.notifyChange();
|
|
940
|
-
const evaluatedVal = _optionalChain([this, 'access',
|
|
941
|
-
_optionalChain([this, 'access',
|
|
1267
|
+
const evaluatedVal = _optionalChain([this, 'access', _33 => _33.document, 'access', _34 => _34.cells, 'access', _35 => _35[rowId], 'access', _36 => _36[colId], 'optionalAccess', _37 => _37.value]);
|
|
1268
|
+
_optionalChain([this, 'access', _38 => _38.onCellCommit, 'optionalCall', _39 => _39(rowId, colId, raw, evaluatedVal)]);
|
|
942
1269
|
}
|
|
943
1270
|
getCellKey(rowId, colId) {
|
|
944
1271
|
const row = this.document.rows.find((r) => r.id === rowId);
|
|
@@ -946,37 +1273,58 @@ var SpreadsheetController = (_class4 = class {
|
|
|
946
1273
|
if (!row || !col) return `${colId}_${rowId}`;
|
|
947
1274
|
return `${col.key}${row.index + 1}`.toUpperCase();
|
|
948
1275
|
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Recalculates a cell and everything that depends on it, in dependency
|
|
1278
|
+
* order. Every cell in a reference cycle is marked `#CYCLE!`.
|
|
1279
|
+
*/
|
|
949
1280
|
recalculateCell(rowId, colId) {
|
|
950
1281
|
const cellKey = this.getCellKey(rowId, colId);
|
|
951
|
-
|
|
952
|
-
if (hasCycle) {
|
|
953
|
-
if (_optionalChain([this, 'access', _38 => _38.document, 'access', _39 => _39.cells, 'access', _40 => _40[rowId], 'optionalAccess', _41 => _41[colId]])) {
|
|
954
|
-
this.document.cells[rowId][colId].error = "#CYCLE!";
|
|
955
|
-
this.document.cells[rowId][colId].value = "#CYCLE!";
|
|
956
|
-
}
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
959
|
-
for (const key of order) {
|
|
960
|
-
this.evalSingleCellByKey(key);
|
|
961
|
-
}
|
|
1282
|
+
this.runEvaluationPlan(this.depGraph.getEvaluationPlan([cellKey], { includeDependents: true }));
|
|
962
1283
|
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Rebuilds the dependency graph from scratch and recalculates every formula
|
|
1286
|
+
* in dependency order.
|
|
1287
|
+
*/
|
|
963
1288
|
recalculateAll() {
|
|
1289
|
+
this.depGraph.clear();
|
|
1290
|
+
const formulaKeys = [];
|
|
964
1291
|
for (const row of this.document.rows) {
|
|
965
1292
|
for (const col of this.document.columns) {
|
|
966
|
-
const cell = this.
|
|
967
|
-
const cellKey = `${col.key}${row.index + 1}`.toUpperCase();
|
|
1293
|
+
const cell = _nullishCoalesce(_optionalChain([this, 'access', _40 => _40.document, 'access', _41 => _41.cells, 'access', _42 => _42[row.id], 'optionalAccess', _43 => _43[col.id]]), () => ( this.getDerivedCell(row, col)));
|
|
968
1294
|
if (cell.raw.startsWith("=")) {
|
|
1295
|
+
const cellKey = `${col.key}${row.index + 1}`.toUpperCase();
|
|
969
1296
|
this.depGraph.setCellFormula(cellKey, cell.raw);
|
|
1297
|
+
formulaKeys.push(cellKey);
|
|
970
1298
|
}
|
|
971
1299
|
}
|
|
972
1300
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1301
|
+
this.runEvaluationPlan(this.depGraph.getEvaluationPlan(formulaKeys));
|
|
1302
|
+
}
|
|
1303
|
+
runEvaluationPlan(plan) {
|
|
1304
|
+
for (const key of plan.order) {
|
|
1305
|
+
if (plan.cyclic.has(key)) {
|
|
1306
|
+
this.markCycle(key);
|
|
1307
|
+
} else {
|
|
1308
|
+
this.evalSingleCellByKey(key);
|
|
977
1309
|
}
|
|
978
1310
|
}
|
|
979
1311
|
}
|
|
1312
|
+
locateCellKey(cellKey) {
|
|
1313
|
+
const match = cellKey.match(/^([A-Za-z]+)(\d+)$/);
|
|
1314
|
+
if (!match) return null;
|
|
1315
|
+
const col = this.document.columns.find((c) => c.key.toUpperCase() === match[1].toUpperCase());
|
|
1316
|
+
const row = this.document.rows[parseInt(match[2], 10) - 1];
|
|
1317
|
+
return col && row ? { row, col } : null;
|
|
1318
|
+
}
|
|
1319
|
+
markCycle(cellKey) {
|
|
1320
|
+
const located = this.locateCellKey(cellKey);
|
|
1321
|
+
if (!located) return;
|
|
1322
|
+
const { row, col } = located;
|
|
1323
|
+
const cell = this.getCell(row.id, col.id);
|
|
1324
|
+
if (!cell.raw.startsWith("=")) return;
|
|
1325
|
+
if (!this.document.cells[row.id]) this.document.cells[row.id] = {};
|
|
1326
|
+
this.document.cells[row.id][col.id] = { ...cell, value: "#CYCLE!", error: "#CYCLE!" };
|
|
1327
|
+
}
|
|
980
1328
|
evalSingleCellByKey(cellKey) {
|
|
981
1329
|
const match = cellKey.match(/^([A-Za-z]+)(\d+)$/);
|
|
982
1330
|
if (!match) return;
|
|
@@ -993,6 +1341,7 @@ var SpreadsheetController = (_class4 = class {
|
|
|
993
1341
|
return;
|
|
994
1342
|
}
|
|
995
1343
|
try {
|
|
1344
|
+
this.evaluatingRowIndex = rowNum - 1;
|
|
996
1345
|
const computed = this.evaluator.evaluate(cell.raw);
|
|
997
1346
|
if (!this.document.cells[row.id]) {
|
|
998
1347
|
this.document.cells[row.id] = {};
|
|
@@ -1009,16 +1358,17 @@ var SpreadsheetController = (_class4 = class {
|
|
|
1009
1358
|
this.document.cells[row.id][col.id] = {
|
|
1010
1359
|
...cell,
|
|
1011
1360
|
value: "#ERROR!",
|
|
1012
|
-
error: _optionalChain([err, 'optionalAccess',
|
|
1361
|
+
error: _optionalChain([err, 'optionalAccess', _44 => _44.message]) || "Formula error"
|
|
1013
1362
|
};
|
|
1363
|
+
} finally {
|
|
1364
|
+
this.evaluatingRowIndex = null;
|
|
1014
1365
|
}
|
|
1015
1366
|
}
|
|
1016
1367
|
resolveCellByCoord(colStr, rowNum) {
|
|
1017
1368
|
const col = this.document.columns.find((c) => c.key.toUpperCase() === colStr.toUpperCase());
|
|
1018
1369
|
const row = this.document.rows[rowNum - 1];
|
|
1019
1370
|
if (!col || !row) return 0;
|
|
1020
|
-
|
|
1021
|
-
return cell.value !== void 0 && cell.value !== "" ? cell.value : 0;
|
|
1371
|
+
return _nullishCoalesce(this.getCell(row.id, col.id).value, () => ( ""));
|
|
1022
1372
|
}
|
|
1023
1373
|
resolveRangeByCoords(sCol, sRow, eCol, eRow) {
|
|
1024
1374
|
const startC = colNameToIndex(sCol);
|
|
@@ -1026,7 +1376,7 @@ var SpreadsheetController = (_class4 = class {
|
|
|
1026
1376
|
const minC = Math.min(startC, endC);
|
|
1027
1377
|
const maxC = Math.max(startC, endC);
|
|
1028
1378
|
const minR = Math.min(sRow, eRow);
|
|
1029
|
-
const maxR = Math.max(sRow, eRow);
|
|
1379
|
+
const maxR = Math.min(Math.max(sRow, eRow), WHOLE_COLUMN_END_ROW, this.document.rows.length);
|
|
1030
1380
|
const results = [];
|
|
1031
1381
|
for (let c = minC; c <= maxC; c++) {
|
|
1032
1382
|
const colName = indexToColName(c);
|
|
@@ -1035,15 +1385,15 @@ var SpreadsheetController = (_class4 = class {
|
|
|
1035
1385
|
for (let r = minR; r <= maxR; r++) {
|
|
1036
1386
|
const row = this.document.rows[r - 1];
|
|
1037
1387
|
if (!row) continue;
|
|
1038
|
-
|
|
1039
|
-
results.push(cell.value !== void 0 && cell.value !== "" ? cell.value : 0);
|
|
1388
|
+
results.push(_nullishCoalesce(this.getCell(row.id, col.id).value, () => ( "")));
|
|
1040
1389
|
}
|
|
1041
1390
|
}
|
|
1042
1391
|
return results;
|
|
1043
1392
|
}
|
|
1044
1393
|
resolveCurrentRowProperty(prop) {
|
|
1045
|
-
|
|
1046
|
-
|
|
1394
|
+
const rowIndex = _nullishCoalesce(this.evaluatingRowIndex, () => ( _optionalChain([this, 'access', _45 => _45.activeCell, 'optionalAccess', _46 => _46.row])));
|
|
1395
|
+
if (rowIndex === void 0 || rowIndex === null) return 0;
|
|
1396
|
+
const row = this.document.rows[rowIndex];
|
|
1047
1397
|
if (!row || !row.recordId || !this.primaryDataSourceId) return 0;
|
|
1048
1398
|
return _nullishCoalesce(this.binder.resolveBoundValue(this.primaryDataSourceId, row.recordId, prop), () => ( 0));
|
|
1049
1399
|
}
|
|
@@ -1056,8 +1406,8 @@ var SpreadsheetController = (_class4 = class {
|
|
|
1056
1406
|
this.activeCell = {
|
|
1057
1407
|
row: clampedRow,
|
|
1058
1408
|
col: clampedCol,
|
|
1059
|
-
rowId: _optionalChain([rowObj, 'optionalAccess',
|
|
1060
|
-
colId: _optionalChain([colObj, 'optionalAccess',
|
|
1409
|
+
rowId: _optionalChain([rowObj, 'optionalAccess', _47 => _47.id]),
|
|
1410
|
+
colId: _optionalChain([colObj, 'optionalAccess', _48 => _48.id])
|
|
1061
1411
|
};
|
|
1062
1412
|
if (extendSelection && this.selectedRange) {
|
|
1063
1413
|
this.selectedRange = {
|
|
@@ -1074,15 +1424,15 @@ var SpreadsheetController = (_class4 = class {
|
|
|
1074
1424
|
endCol: clampedCol
|
|
1075
1425
|
};
|
|
1076
1426
|
}
|
|
1077
|
-
_optionalChain([this, 'access',
|
|
1427
|
+
_optionalChain([this, 'access', _49 => _49.onSelectionChange, 'optionalCall', _50 => _50(this.activeCell, this.selectedRange)]);
|
|
1078
1428
|
}
|
|
1079
1429
|
selectRange(range) {
|
|
1080
1430
|
this.selectedRange = range;
|
|
1081
|
-
_optionalChain([this, 'access',
|
|
1431
|
+
_optionalChain([this, 'access', _51 => _51.onSelectionChange, 'optionalCall', _52 => _52(this.activeCell, this.selectedRange)]);
|
|
1082
1432
|
}
|
|
1083
1433
|
startEditing(row, col, initialValue) {
|
|
1084
|
-
const targetRow = _nullishCoalesce(_nullishCoalesce(row, () => ( _optionalChain([this, 'access',
|
|
1085
|
-
const targetCol = _nullishCoalesce(_nullishCoalesce(col, () => ( _optionalChain([this, 'access',
|
|
1434
|
+
const targetRow = _nullishCoalesce(_nullishCoalesce(row, () => ( _optionalChain([this, 'access', _53 => _53.activeCell, 'optionalAccess', _54 => _54.row]))), () => ( 0));
|
|
1435
|
+
const targetCol = _nullishCoalesce(_nullishCoalesce(col, () => ( _optionalChain([this, 'access', _55 => _55.activeCell, 'optionalAccess', _56 => _56.col]))), () => ( 0));
|
|
1086
1436
|
this.selectCell(targetRow, targetCol);
|
|
1087
1437
|
const rowObj = this.document.rows[targetRow];
|
|
1088
1438
|
const colObj = this.document.columns[targetCol];
|
|
@@ -1135,18 +1485,19 @@ var SpreadsheetController = (_class4 = class {
|
|
|
1135
1485
|
// --- Grid Structure Mutations ---
|
|
1136
1486
|
insertColumn(index, config) {
|
|
1137
1487
|
this.recordSnapshot();
|
|
1138
|
-
const newId = _optionalChain([config, 'optionalAccess',
|
|
1488
|
+
const newId = _optionalChain([config, 'optionalAccess', _57 => _57.id]) || this.generateId("col");
|
|
1139
1489
|
const newCol = {
|
|
1140
1490
|
id: newId,
|
|
1141
1491
|
key: indexToColName(index),
|
|
1142
|
-
title: _optionalChain([config, 'optionalAccess',
|
|
1143
|
-
width: _optionalChain([config, 'optionalAccess',
|
|
1144
|
-
type: _optionalChain([config, 'optionalAccess',
|
|
1145
|
-
binding: _optionalChain([config, 'optionalAccess',
|
|
1146
|
-
formula: _optionalChain([config, 'optionalAccess',
|
|
1147
|
-
format: _optionalChain([config, 'optionalAccess',
|
|
1148
|
-
readOnly: _optionalChain([config, 'optionalAccess',
|
|
1492
|
+
title: _optionalChain([config, 'optionalAccess', _58 => _58.title]) || indexToColName(index),
|
|
1493
|
+
width: _optionalChain([config, 'optionalAccess', _59 => _59.width]) || 130,
|
|
1494
|
+
type: _optionalChain([config, 'optionalAccess', _60 => _60.type]) || "freeform",
|
|
1495
|
+
binding: _optionalChain([config, 'optionalAccess', _61 => _61.binding]),
|
|
1496
|
+
formula: _optionalChain([config, 'optionalAccess', _62 => _62.formula]),
|
|
1497
|
+
format: _optionalChain([config, 'optionalAccess', _63 => _63.format]) || "text",
|
|
1498
|
+
readOnly: _optionalChain([config, 'optionalAccess', _64 => _64.readOnly])
|
|
1149
1499
|
};
|
|
1500
|
+
this.shiftReferences({ axis: "col", index, type: "insert" });
|
|
1150
1501
|
this.document.columns.splice(index, 0, newCol);
|
|
1151
1502
|
this.recomputeRowIndices();
|
|
1152
1503
|
this.recalculateAll();
|
|
@@ -1154,6 +1505,9 @@ var SpreadsheetController = (_class4 = class {
|
|
|
1154
1505
|
}
|
|
1155
1506
|
deleteColumn(colId) {
|
|
1156
1507
|
this.recordSnapshot();
|
|
1508
|
+
const colIndex = this.document.columns.findIndex((c) => c.id === colId);
|
|
1509
|
+
if (colIndex === -1) return;
|
|
1510
|
+
this.shiftReferences({ axis: "col", index: colIndex, type: "delete" });
|
|
1157
1511
|
this.document.columns = this.document.columns.filter((c) => c.id !== colId);
|
|
1158
1512
|
for (const rowId of Object.keys(this.document.cells)) {
|
|
1159
1513
|
delete this.document.cells[rowId][colId];
|
|
@@ -1164,14 +1518,15 @@ var SpreadsheetController = (_class4 = class {
|
|
|
1164
1518
|
}
|
|
1165
1519
|
insertRow(index, config) {
|
|
1166
1520
|
this.recordSnapshot();
|
|
1167
|
-
const newId = _optionalChain([config, 'optionalAccess',
|
|
1521
|
+
const newId = _optionalChain([config, 'optionalAccess', _65 => _65.id]) || this.generateId("row");
|
|
1168
1522
|
const newRow = {
|
|
1169
1523
|
id: newId,
|
|
1170
1524
|
index,
|
|
1171
|
-
height: _optionalChain([config, 'optionalAccess',
|
|
1172
|
-
recordId: _optionalChain([config, 'optionalAccess',
|
|
1173
|
-
type: _optionalChain([config, 'optionalAccess',
|
|
1525
|
+
height: _optionalChain([config, 'optionalAccess', _66 => _66.height]) || 32,
|
|
1526
|
+
recordId: _optionalChain([config, 'optionalAccess', _67 => _67.recordId]),
|
|
1527
|
+
type: _optionalChain([config, 'optionalAccess', _68 => _68.type]) || "freeform"
|
|
1174
1528
|
};
|
|
1529
|
+
this.shiftReferences({ axis: "row", index, type: "insert" });
|
|
1175
1530
|
this.document.rows.splice(index, 0, newRow);
|
|
1176
1531
|
this.recomputeRowIndices();
|
|
1177
1532
|
this.recalculateAll();
|
|
@@ -1179,12 +1534,38 @@ var SpreadsheetController = (_class4 = class {
|
|
|
1179
1534
|
}
|
|
1180
1535
|
deleteRow(rowId) {
|
|
1181
1536
|
this.recordSnapshot();
|
|
1537
|
+
const rowIndex = this.document.rows.findIndex((r) => r.id === rowId);
|
|
1538
|
+
if (rowIndex === -1) return;
|
|
1539
|
+
this.shiftReferences({ axis: "row", index: rowIndex, type: "delete" });
|
|
1182
1540
|
this.document.rows = this.document.rows.filter((r) => r.id !== rowId);
|
|
1183
1541
|
delete this.document.cells[rowId];
|
|
1184
1542
|
this.recomputeRowIndices();
|
|
1185
1543
|
this.recalculateAll();
|
|
1186
1544
|
this.notifyChange();
|
|
1187
1545
|
}
|
|
1546
|
+
generateId(prefix) {
|
|
1547
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Rewrites references in every cell formula (and, for column changes, every
|
|
1551
|
+
* column formula) so they keep pointing at the same cells.
|
|
1552
|
+
*/
|
|
1553
|
+
shiftReferences(change) {
|
|
1554
|
+
for (const rowCells of Object.values(this.document.cells)) {
|
|
1555
|
+
for (const cell of Object.values(rowCells)) {
|
|
1556
|
+
if (_optionalChain([cell, 'access', _69 => _69.raw, 'optionalAccess', _70 => _70.startsWith, 'call', _71 => _71("=")])) {
|
|
1557
|
+
cell.raw = shiftFormulaReferences(cell.raw, change);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
if (change.axis === "col") {
|
|
1562
|
+
for (const col of this.document.columns) {
|
|
1563
|
+
if (_optionalChain([col, 'access', _72 => _72.formula, 'optionalAccess', _73 => _73.startsWith, 'call', _74 => _74("=")])) {
|
|
1564
|
+
col.formula = shiftFormulaReferences(col.formula, change);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1188
1569
|
setColumnWidth(colId, width) {
|
|
1189
1570
|
const col = this.document.columns.find((c) => c.id === colId);
|
|
1190
1571
|
if (col) {
|
|
@@ -1231,31 +1612,54 @@ var SpreadsheetController = (_class4 = class {
|
|
|
1231
1612
|
}).join(" ")
|
|
1232
1613
|
).join("\n");
|
|
1233
1614
|
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Pastes tab-separated values starting at (startRow, startCol), adding rows
|
|
1617
|
+
* and columns as needed. The whole paste is a single undo step.
|
|
1618
|
+
*/
|
|
1234
1619
|
importFromTsv(tsvData, startRow = 0, startCol = 0) {
|
|
1620
|
+
const normalized = tsvData.replace(/\r\n?/g, "\n").replace(/\n$/, "");
|
|
1621
|
+
if (normalized === "") return;
|
|
1235
1622
|
this.recordSnapshot();
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
cells.forEach((val, cIdx) => {
|
|
1245
|
-
const targetC = startCol + cIdx;
|
|
1246
|
-
while (targetC >= this.document.columns.length) {
|
|
1247
|
-
this.insertColumn(this.document.columns.length);
|
|
1623
|
+
this.batching = true;
|
|
1624
|
+
this.pendingCommits = [];
|
|
1625
|
+
try {
|
|
1626
|
+
normalized.split("\n").forEach((line, rIdx) => {
|
|
1627
|
+
const targetR = startRow + rIdx;
|
|
1628
|
+
while (targetR >= this.document.rows.length) {
|
|
1629
|
+
const index = this.document.rows.length;
|
|
1630
|
+
this.document.rows.push({ id: this.generateId("row"), index, height: 32, type: "freeform" });
|
|
1248
1631
|
}
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1632
|
+
const row = this.document.rows[targetR];
|
|
1633
|
+
line.split(" ").forEach((val, cIdx) => {
|
|
1634
|
+
const targetC = startCol + cIdx;
|
|
1635
|
+
while (targetC >= this.document.columns.length) {
|
|
1636
|
+
const key = indexToColName(this.document.columns.length);
|
|
1637
|
+
this.document.columns.push({
|
|
1638
|
+
id: this.generateId("col"),
|
|
1639
|
+
key,
|
|
1640
|
+
title: key,
|
|
1641
|
+
width: 130,
|
|
1642
|
+
type: "freeform",
|
|
1643
|
+
format: "text"
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
this.setCellValue(row.id, this.document.columns[targetC].id, val);
|
|
1647
|
+
});
|
|
1251
1648
|
});
|
|
1252
|
-
}
|
|
1649
|
+
} finally {
|
|
1650
|
+
this.batching = false;
|
|
1651
|
+
}
|
|
1253
1652
|
this.recalculateAll();
|
|
1254
1653
|
this.notifyChange();
|
|
1654
|
+
const commits = this.pendingCommits;
|
|
1655
|
+
this.pendingCommits = [];
|
|
1656
|
+
for (const [rowId, colId, raw] of commits) {
|
|
1657
|
+
_optionalChain([this, 'access', _75 => _75.onCellCommit, 'optionalCall', _76 => _76(rowId, colId, raw, _optionalChain([this, 'access', _77 => _77.document, 'access', _78 => _78.cells, 'access', _79 => _79[rowId], 'optionalAccess', _80 => _80[colId], 'optionalAccess', _81 => _81.value]))]);
|
|
1658
|
+
}
|
|
1255
1659
|
}
|
|
1256
1660
|
// --- Undo/Redo Snapshots ---
|
|
1257
1661
|
recordSnapshot() {
|
|
1258
|
-
if (this.isApplyingHistory) return;
|
|
1662
|
+
if (this.isApplyingHistory || this.batching) return;
|
|
1259
1663
|
this.undoStack.push(JSON.stringify(this.document));
|
|
1260
1664
|
if (this.undoStack.length > 50) this.undoStack.shift();
|
|
1261
1665
|
this.redoStack = [];
|
|
@@ -1263,25 +1667,29 @@ var SpreadsheetController = (_class4 = class {
|
|
|
1263
1667
|
undo() {
|
|
1264
1668
|
if (this.undoStack.length === 0) return;
|
|
1265
1669
|
this.isApplyingHistory = true;
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1670
|
+
try {
|
|
1671
|
+
this.redoStack.push(JSON.stringify(this.document));
|
|
1672
|
+
this.document = JSON.parse(this.undoStack.pop());
|
|
1673
|
+
this.recalculateAll();
|
|
1674
|
+
this.notifyChange();
|
|
1675
|
+
} finally {
|
|
1676
|
+
this.isApplyingHistory = false;
|
|
1677
|
+
}
|
|
1272
1678
|
}
|
|
1273
1679
|
redo() {
|
|
1274
1680
|
if (this.redoStack.length === 0) return;
|
|
1275
1681
|
this.isApplyingHistory = true;
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1682
|
+
try {
|
|
1683
|
+
this.undoStack.push(JSON.stringify(this.document));
|
|
1684
|
+
this.document = JSON.parse(this.redoStack.pop());
|
|
1685
|
+
this.recalculateAll();
|
|
1686
|
+
this.notifyChange();
|
|
1687
|
+
} finally {
|
|
1688
|
+
this.isApplyingHistory = false;
|
|
1689
|
+
}
|
|
1282
1690
|
}
|
|
1283
1691
|
notifyChange() {
|
|
1284
|
-
_optionalChain([this, 'access',
|
|
1692
|
+
_optionalChain([this, 'access', _82 => _82.onDocumentChange, 'optionalCall', _83 => _83(this.document)]);
|
|
1285
1693
|
}
|
|
1286
1694
|
}, _class4);
|
|
1287
1695
|
|
|
@@ -1298,5 +1706,10 @@ var SpreadsheetController = (_class4 = class {
|
|
|
1298
1706
|
|
|
1299
1707
|
|
|
1300
1708
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1709
|
+
|
|
1710
|
+
|
|
1711
|
+
|
|
1712
|
+
|
|
1713
|
+
|
|
1714
|
+
exports.tokenizeFormula = tokenizeFormula; exports.WHOLE_COLUMN_END_ROW = WHOLE_COLUMN_END_ROW; exports.parseCellReference = parseCellReference; exports.parseRange = parseRange; exports.FormulaParser = FormulaParser; exports.parseFormula = parseFormula; exports.FORMULA_ERRORS = FORMULA_ERRORS; exports.isFormulaError = isFormulaError; exports.colNameToIndex = colNameToIndex; exports.indexToColName = indexToColName; exports.FormulaEvaluator = FormulaEvaluator; exports.columnDependencyKey = columnDependencyKey; exports.extractCellDependencies = extractCellDependencies; exports.DependencyGraph = DependencyGraph; exports.shiftFormulaReferences = shiftFormulaReferences; exports.DataSourceBinder = DataSourceBinder; exports.SpreadsheetController = SpreadsheetController;
|
|
1715
|
+
//# sourceMappingURL=chunk-YQCI4CCN.cjs.map
|