@pixerate/editor 0.22.0 → 0.24.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/README.md +175 -2
- package/THIRD_PARTY_NOTICES.md +139 -0
- package/dist/agent/index.cjs +87 -111
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +70 -211
- package/dist/agent/index.d.ts +70 -211
- package/dist/agent/index.js +78 -102
- package/dist/agent/index.js.map +1 -1
- package/dist/arrangeShortcuts-B8O3f8-P.d.cts +184 -0
- package/dist/arrangeShortcuts-sLlD2rSX.d.ts +184 -0
- package/dist/canvas/index.cjs +2075 -86
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +608 -186
- package/dist/canvas/index.d.ts +608 -186
- package/dist/canvas/index.js +2060 -71
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-26BVFKHU.cjs +453 -0
- package/dist/chunk-26BVFKHU.cjs.map +1 -0
- package/dist/chunk-2H7ZHPEJ.js +762 -0
- package/dist/chunk-2H7ZHPEJ.js.map +1 -0
- package/dist/{chunk-DENSCKFC.js → chunk-3CN2WD3X.js} +1730 -189
- package/dist/chunk-3CN2WD3X.js.map +1 -0
- package/dist/{chunk-JJDLPEWL.js → chunk-3LV6RRPH.js} +2 -2
- package/dist/{chunk-MJZBNWG7.js → chunk-5S3Z4PPF.js} +78 -557
- package/dist/chunk-5S3Z4PPF.js.map +1 -0
- package/dist/chunk-664LQYFJ.js +115 -0
- package/dist/chunk-664LQYFJ.js.map +1 -0
- package/dist/chunk-7DWMJCAT.js +453 -0
- package/dist/chunk-7DWMJCAT.js.map +1 -0
- package/dist/chunk-ARRTP33I.js +101 -0
- package/dist/chunk-ARRTP33I.js.map +1 -0
- package/dist/chunk-CPOKFNBG.cjs +101 -0
- package/dist/chunk-CPOKFNBG.cjs.map +1 -0
- package/dist/{chunk-AA4ZEVCC.cjs → chunk-D27YFC46.cjs} +101 -580
- package/dist/chunk-D27YFC46.cjs.map +1 -0
- package/dist/chunk-D6FH2YAX.cjs +131 -0
- package/dist/chunk-D6FH2YAX.cjs.map +1 -0
- package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
- package/dist/chunk-FD4LPFAE.cjs.map +1 -0
- package/dist/chunk-FODNLX5N.js +749 -0
- package/dist/chunk-FODNLX5N.js.map +1 -0
- package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
- package/dist/chunk-FPENXAEJ.js.map +1 -0
- package/dist/chunk-G2I4QJOT.cjs +762 -0
- package/dist/chunk-G2I4QJOT.cjs.map +1 -0
- package/dist/chunk-HLIMTC6N.js +1 -0
- package/dist/chunk-HLIMTC6N.js.map +1 -0
- package/dist/{chunk-QGHGJ56O.js → chunk-ITBSKI26.js} +1483 -19
- package/dist/chunk-ITBSKI26.js.map +1 -0
- package/dist/{chunk-6M3BCTBD.cjs → chunk-MA2F5KHF.cjs} +470 -242
- package/dist/chunk-MA2F5KHF.cjs.map +1 -0
- package/dist/{chunk-SAZRENEZ.cjs → chunk-MCSJFWAO.cjs} +1733 -192
- package/dist/chunk-MCSJFWAO.cjs.map +1 -0
- package/dist/chunk-MKA3AHLV.cjs +256 -0
- package/dist/chunk-MKA3AHLV.cjs.map +1 -0
- package/dist/chunk-OLZJXL4R.cjs +749 -0
- package/dist/chunk-OLZJXL4R.cjs.map +1 -0
- package/dist/chunk-OXSV64JX.js +689 -0
- package/dist/chunk-OXSV64JX.js.map +1 -0
- package/dist/chunk-P3RQ5UJE.cjs +689 -0
- package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
- package/dist/chunk-P6ODPZ4W.js +256 -0
- package/dist/chunk-P6ODPZ4W.js.map +1 -0
- package/dist/chunk-PGNZKFQS.js +685 -0
- package/dist/chunk-PGNZKFQS.js.map +1 -0
- package/dist/chunk-PL4JB3BF.cjs +1 -0
- package/dist/chunk-PL4JB3BF.cjs.map +1 -0
- package/dist/{chunk-GVSMRPHB.cjs → chunk-SBCZ6RN5.cjs} +1568 -104
- package/dist/chunk-SBCZ6RN5.cjs.map +1 -0
- package/dist/{chunk-CL37JGFW.cjs → chunk-SML64VJG.cjs} +4 -4
- package/dist/{chunk-CL37JGFW.cjs.map → chunk-SML64VJG.cjs.map} +1 -1
- package/dist/chunk-TNNJXF46.cjs +115 -0
- package/dist/chunk-TNNJXF46.cjs.map +1 -0
- package/dist/chunk-U5HZSFCS.js +131 -0
- package/dist/chunk-U5HZSFCS.js.map +1 -0
- package/dist/{chunk-RU52I7QO.js → chunk-U5KWZU4X.js} +388 -160
- package/dist/chunk-U5KWZU4X.js.map +1 -0
- package/dist/chunk-XNORY7M6.cjs +685 -0
- package/dist/chunk-XNORY7M6.cjs.map +1 -0
- package/dist/commands/index.cjs +860 -0
- package/dist/commands/index.cjs.map +1 -0
- package/dist/commands/index.d.cts +244 -0
- package/dist/commands/index.d.ts +244 -0
- package/dist/commands/index.js +860 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/comment-store-BEMgVlAA.d.cts +135 -0
- package/dist/comment-store-BEMgVlAA.d.ts +135 -0
- package/dist/compare/index.cjs +15 -0
- package/dist/compare/index.cjs.map +1 -0
- package/dist/compare/index.d.cts +58 -0
- package/dist/compare/index.d.ts +58 -0
- package/dist/compare/index.js +15 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/{controller-B7GIybc4.d.cts → controller-BhLLAoi8.d.cts} +119 -5
- package/dist/{controller-B7GIybc4.d.ts → controller-BhLLAoi8.d.ts} +119 -5
- package/dist/documents-BQjzJNe2.d.ts +76 -0
- package/dist/documents-KRTPhvst.d.cts +76 -0
- package/dist/extensions/index.cjs +53 -5
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +7 -3
- package/dist/extensions/index.d.ts +7 -3
- package/dist/extensions/index.js +52 -4
- package/dist/generation/index.cjs +1373 -0
- package/dist/generation/index.cjs.map +1 -0
- package/dist/generation/index.d.cts +554 -0
- package/dist/generation/index.d.ts +554 -0
- package/dist/generation/index.js +1373 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/grammar/index.cjs +48 -3
- package/dist/grammar/index.cjs.map +1 -1
- package/dist/grammar/index.d.cts +322 -100
- package/dist/grammar/index.d.ts +322 -100
- package/dist/grammar/index.js +54 -9
- package/dist/history/index.cjs +12 -2
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +211 -7
- package/dist/history/index.d.ts +211 -7
- package/dist/history/index.js +13 -3
- package/dist/image-editor/index.cjs +2244 -38
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +742 -4
- package/dist/image-editor/index.d.ts +742 -4
- package/dist/image-editor/index.js +2222 -16
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{index-CCr_z9kF.d.cts → index-BhJBSm2d.d.cts} +370 -4
- package/dist/{index-Ck6ajwXh.d.ts → index-fJrlkJCd.d.ts} +370 -4
- package/dist/index.cjs +193 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -48
- package/dist/index.d.ts +17 -48
- package/dist/index.js +198 -103
- package/dist/index.js.map +1 -1
- package/dist/migrate-iTlm3a8h.d.cts +112 -0
- package/dist/migrate-iTlm3a8h.d.ts +112 -0
- package/dist/persistence/index.cjs +676 -0
- package/dist/persistence/index.cjs.map +1 -0
- package/dist/persistence/index.d.cts +347 -0
- package/dist/persistence/index.d.ts +347 -0
- package/dist/persistence/index.js +676 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/registry-BDe-vlv3.d.ts +171 -0
- package/dist/registry-D32LpIc4.d.cts +354 -0
- package/dist/registry-DnzpgY27.d.cts +171 -0
- package/dist/registry-evKTP4ls.d.ts +354 -0
- package/dist/schema-DYDGdYL9.d.cts +41 -0
- package/dist/schema-DYDGdYL9.d.ts +41 -0
- package/dist/serializers/index.cjs +4 -3
- package/dist/serializers/index.cjs.map +1 -1
- package/dist/serializers/index.d.cts +2 -1
- package/dist/serializers/index.d.ts +2 -1
- package/dist/serializers/index.js +3 -2
- package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
- package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
- package/dist/spreadsheet/index.cjs +53 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +247 -18
- package/dist/spreadsheet/index.d.ts +247 -18
- package/dist/spreadsheet/index.js +58 -8
- package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
- package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
- package/dist/{track-changes-BSF58RK0.d.cts → track-changes-BK5sVf4_.d.cts} +18 -136
- package/dist/{track-changes-BSF58RK0.d.ts → track-changes-CDjCrwKe.d.ts} +18 -136
- package/dist/{types-CJ4U-HHI.d.ts → types-DX4Mh4SL.d.cts} +1 -1
- package/dist/{types-CJ4U-HHI.d.cts → types-DX4Mh4SL.d.ts} +1 -1
- package/dist/types-GT5a5YAX.d.cts +86 -0
- package/dist/types-GT5a5YAX.d.ts +86 -0
- package/package.json +23 -2
- package/dist/chunk-2TWGLC5K.cjs +0 -262
- package/dist/chunk-2TWGLC5K.cjs.map +0 -1
- package/dist/chunk-4SUNJZWB.cjs.map +0 -1
- package/dist/chunk-6M3BCTBD.cjs.map +0 -1
- package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
- package/dist/chunk-AXV2ZW34.cjs +0 -118
- package/dist/chunk-AXV2ZW34.cjs.map +0 -1
- package/dist/chunk-B2ZRLN4S.js +0 -118
- package/dist/chunk-B2ZRLN4S.js.map +0 -1
- package/dist/chunk-DENSCKFC.js.map +0 -1
- package/dist/chunk-GVSMRPHB.cjs.map +0 -1
- package/dist/chunk-KAADNAPF.js +0 -262
- package/dist/chunk-KAADNAPF.js.map +0 -1
- package/dist/chunk-MJZBNWG7.js.map +0 -1
- package/dist/chunk-OYXSKQFR.js.map +0 -1
- package/dist/chunk-QGHGJ56O.js.map +0 -1
- package/dist/chunk-RU52I7QO.js.map +0 -1
- package/dist/chunk-SAZRENEZ.cjs.map +0 -1
- package/dist/types-BZgcDsC5.d.cts +0 -42
- package/dist/types-BZgcDsC5.d.ts +0 -42
- /package/dist/{chunk-JJDLPEWL.js.map → chunk-3LV6RRPH.js.map} +0 -0
|
@@ -1,18 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseCsv,
|
|
3
|
+
parseTsv,
|
|
4
|
+
serializeCsv,
|
|
5
|
+
serializeTsv
|
|
6
|
+
} from "./chunk-664LQYFJ.js";
|
|
1
7
|
import {
|
|
2
8
|
FormulaEvaluator,
|
|
9
|
+
NumberFormat,
|
|
3
10
|
WHOLE_COLUMN_END_ROW,
|
|
4
11
|
colNameToIndex,
|
|
12
|
+
compareValues,
|
|
5
13
|
describeFormulaError,
|
|
14
|
+
formatTextFunction,
|
|
6
15
|
getFormulaArgumentSpecIndex,
|
|
7
16
|
getFormulaFunction,
|
|
8
17
|
getFormulaSignature,
|
|
9
18
|
getFormulaSignatureParts,
|
|
10
19
|
indexToColName,
|
|
20
|
+
isBlank,
|
|
11
21
|
isFormulaError,
|
|
22
|
+
numericValue,
|
|
23
|
+
parseCellInput,
|
|
12
24
|
parseFormula,
|
|
25
|
+
parseIsoDate,
|
|
13
26
|
searchFormulaFunctions,
|
|
27
|
+
sortCompareValues,
|
|
28
|
+
toText,
|
|
14
29
|
tokenizeFormula
|
|
15
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-3CN2WD3X.js";
|
|
16
31
|
|
|
17
32
|
// src/spreadsheet/formula/dependency-graph.ts
|
|
18
33
|
var columnDependencyKey = (col) => `${col.toUpperCase()}:`;
|
|
@@ -560,6 +575,68 @@ function applyFormulaSuggestion(input, caret, name) {
|
|
|
560
575
|
return { value, caret: start + insert.length + (hasParen ? 1 : 0) };
|
|
561
576
|
}
|
|
562
577
|
|
|
578
|
+
// src/spreadsheet/numfmt/presets.ts
|
|
579
|
+
var NUMBER_FORMAT_PRESETS = [
|
|
580
|
+
{ id: "general", label: "General", code: "General" },
|
|
581
|
+
{ id: "number", label: "Number (0.00)", code: "0.00" },
|
|
582
|
+
{ id: "currency", label: "Currency ($1,234.50)", code: '"$"#,##0.00' },
|
|
583
|
+
{ id: "percent", label: "Percent (12.5%)", code: "0.0%" },
|
|
584
|
+
{ id: "date", label: "Date (3/14/2026)", code: "m/d/yyyy" },
|
|
585
|
+
{ id: "time", label: "Time (1:30 PM)", code: "h:mm AM/PM" }
|
|
586
|
+
];
|
|
587
|
+
function numberFormatForCellFormat(format) {
|
|
588
|
+
switch (format) {
|
|
589
|
+
case "currency":
|
|
590
|
+
return '"$"#,##0.00';
|
|
591
|
+
case "percentage":
|
|
592
|
+
return "0%";
|
|
593
|
+
case "date":
|
|
594
|
+
return "m/d/yyyy";
|
|
595
|
+
default:
|
|
596
|
+
return void 0;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function cellFormatForNumberFormat(code) {
|
|
600
|
+
switch (NumberFormat.parseCached(code).kind()) {
|
|
601
|
+
case "number":
|
|
602
|
+
case "fraction":
|
|
603
|
+
case "scientific":
|
|
604
|
+
case "special":
|
|
605
|
+
return "number";
|
|
606
|
+
case "currency":
|
|
607
|
+
case "accounting":
|
|
608
|
+
return "currency";
|
|
609
|
+
case "percentage":
|
|
610
|
+
return "percentage";
|
|
611
|
+
case "date":
|
|
612
|
+
return "date";
|
|
613
|
+
case "text":
|
|
614
|
+
return "text";
|
|
615
|
+
default:
|
|
616
|
+
return void 0;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
var isGeneralNumberFormat = (code) => code === void 0 || NumberFormat.parseCached(code).isGeneral();
|
|
620
|
+
|
|
621
|
+
// src/spreadsheet/formula/format.ts
|
|
622
|
+
function formatCellForDisplay(cell) {
|
|
623
|
+
if (!cell) return { text: "", numeric: false };
|
|
624
|
+
if (cell.error) return { text: cell.error, numeric: false };
|
|
625
|
+
const { value } = cell;
|
|
626
|
+
const code = cell.numberFormat ?? (typeof value === "number" ? numberFormatForCellFormat(cell.format) : void 0);
|
|
627
|
+
if (code !== void 0 && !isGeneralNumberFormat(code) && !isBlank(value) && typeof value !== "boolean") {
|
|
628
|
+
return NumberFormat.parseCached(code).format(value);
|
|
629
|
+
}
|
|
630
|
+
return { text: toText(value), numeric: typeof value === "number" };
|
|
631
|
+
}
|
|
632
|
+
function formatCellDisplay(cell) {
|
|
633
|
+
return formatCellForDisplay(cell).text;
|
|
634
|
+
}
|
|
635
|
+
function formatValueWithCode(value, format) {
|
|
636
|
+
const result = formatTextFunction(value, format);
|
|
637
|
+
return "text" in result ? result.text : null;
|
|
638
|
+
}
|
|
639
|
+
|
|
563
640
|
// src/spreadsheet/datasource/index.ts
|
|
564
641
|
var DataSourceBinder = class {
|
|
565
642
|
dataSources = /* @__PURE__ */ new Map();
|
|
@@ -678,28 +755,41 @@ function compareCellValues(a, b, direction = "asc") {
|
|
|
678
755
|
const aBlank = isBlankValue(a);
|
|
679
756
|
const bBlank = isBlankValue(b);
|
|
680
757
|
if (aBlank || bBlank) return aBlank === bBlank ? 0 : aBlank ? 1 : -1;
|
|
681
|
-
const aNum = toNumber(a);
|
|
682
|
-
const bNum = toNumber(b);
|
|
683
758
|
let result;
|
|
684
|
-
if (
|
|
685
|
-
|
|
686
|
-
else
|
|
687
|
-
|
|
759
|
+
if (isPlainText(a) && isPlainText(b) && !(parseIsoDate(a) && parseIsoDate(b))) {
|
|
760
|
+
result = compareText(a, b);
|
|
761
|
+
} else {
|
|
762
|
+
result = sortCompareValues(a, b);
|
|
763
|
+
}
|
|
688
764
|
if (result === 0) return 0;
|
|
689
765
|
return direction === "desc" ? -Math.sign(result) : Math.sign(result);
|
|
690
766
|
}
|
|
767
|
+
var isPlainText = (value) => typeof value === "string" && !isFormulaError(value);
|
|
768
|
+
function filterValue(value) {
|
|
769
|
+
if (isBlankValue(value) || isFormulaError(value)) return null;
|
|
770
|
+
if (typeof value === "number") return Number.isFinite(value) ? { value, kind: "number" } : null;
|
|
771
|
+
if (typeof value === "boolean") return { value, kind: "boolean" };
|
|
772
|
+
const num = numericValue(value);
|
|
773
|
+
if (num !== null) return { value: num, kind: "number" };
|
|
774
|
+
const text = String(value);
|
|
775
|
+
return { value: text, kind: parseIsoDate(text) ? "number" : "text" };
|
|
776
|
+
}
|
|
777
|
+
function filterOperand(operand) {
|
|
778
|
+
if (typeof operand !== "string") return filterValue(operand);
|
|
779
|
+
if (operand.trim() === "") return null;
|
|
780
|
+
const parsed = parseCellInput(operand.trim());
|
|
781
|
+
if (parsed.type === "number" || parsed.type === "date" || parsed.type === "boolean") return filterValue(parsed.value);
|
|
782
|
+
return { value: operand, kind: "text" };
|
|
783
|
+
}
|
|
691
784
|
function compareForFilter(value, operand) {
|
|
692
|
-
|
|
693
|
-
const
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
if (a !== null || b !== null) return null;
|
|
697
|
-
return compareText(String(value), String(operand));
|
|
785
|
+
const a = filterValue(value);
|
|
786
|
+
const b = filterOperand(operand);
|
|
787
|
+
if (!a || !b || a.kind !== b.kind) return null;
|
|
788
|
+
return compareValues(a.value, b.value);
|
|
698
789
|
}
|
|
699
790
|
function looselyEquals(value, operand) {
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
if (a !== null && b !== null) return a === b;
|
|
791
|
+
const c = compareForFilter(value, operand);
|
|
792
|
+
if (c !== null) return c === 0;
|
|
703
793
|
return String(value ?? "").trim().toLowerCase() === String(operand ?? "").trim().toLowerCase();
|
|
704
794
|
}
|
|
705
795
|
function matchesFilterRule(value, rule) {
|
|
@@ -774,7 +864,8 @@ function classifyFillValue(raw) {
|
|
|
774
864
|
if (raw.trim() === "") return { kind: "blank" };
|
|
775
865
|
const num = toNumber(raw);
|
|
776
866
|
if (num !== null) return { kind: "number", value: num };
|
|
777
|
-
const
|
|
867
|
+
const typed = parseCellInput(raw);
|
|
868
|
+
const date = ISO_DATE_PATTERN.exec(typed.type === "date" ? String(typed.value) : raw.trim());
|
|
778
869
|
if (date) {
|
|
779
870
|
const [year, month, day] = [Number(date[1]), Number(date[2]), Number(date[3])];
|
|
780
871
|
const time = Date.UTC(year, month - 1, day);
|
|
@@ -892,113 +983,6 @@ function getFillDirection(source, target) {
|
|
|
892
983
|
return null;
|
|
893
984
|
}
|
|
894
985
|
|
|
895
|
-
// src/spreadsheet/csv.ts
|
|
896
|
-
var BOM = "\uFEFF";
|
|
897
|
-
function parseCsv(text, options = {}) {
|
|
898
|
-
const delimiter = options.delimiter ?? ",";
|
|
899
|
-
if (delimiter.length !== 1 || delimiter === '"' || delimiter === "\n" || delimiter === "\r") {
|
|
900
|
-
throw new Error(`Invalid CSV delimiter: ${JSON.stringify(delimiter)}`);
|
|
901
|
-
}
|
|
902
|
-
const input = text.startsWith(BOM) ? text.slice(1) : text;
|
|
903
|
-
const records = [];
|
|
904
|
-
let record = [];
|
|
905
|
-
let field = "";
|
|
906
|
-
let i = 0;
|
|
907
|
-
let recordStarted = false;
|
|
908
|
-
const endField = () => {
|
|
909
|
-
record.push(field);
|
|
910
|
-
field = "";
|
|
911
|
-
};
|
|
912
|
-
const endRecord = () => {
|
|
913
|
-
endField();
|
|
914
|
-
records.push(record);
|
|
915
|
-
record = [];
|
|
916
|
-
recordStarted = false;
|
|
917
|
-
};
|
|
918
|
-
let atFieldStart = true;
|
|
919
|
-
while (i < input.length) {
|
|
920
|
-
const char = input[i];
|
|
921
|
-
if (char === '"' && atFieldStart) {
|
|
922
|
-
const quoted = readQuoted(input, i, delimiter);
|
|
923
|
-
if (quoted.wellFormed || !options.requireWellFormedQuotes) {
|
|
924
|
-
recordStarted = true;
|
|
925
|
-
atFieldStart = false;
|
|
926
|
-
field += quoted.value;
|
|
927
|
-
i = quoted.end;
|
|
928
|
-
continue;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
if (char === delimiter) {
|
|
932
|
-
recordStarted = true;
|
|
933
|
-
endField();
|
|
934
|
-
atFieldStart = true;
|
|
935
|
-
i++;
|
|
936
|
-
continue;
|
|
937
|
-
}
|
|
938
|
-
if (char === "\r" || char === "\n") {
|
|
939
|
-
endRecord();
|
|
940
|
-
atFieldStart = true;
|
|
941
|
-
i += char === "\r" && input[i + 1] === "\n" ? 2 : 1;
|
|
942
|
-
continue;
|
|
943
|
-
}
|
|
944
|
-
recordStarted = true;
|
|
945
|
-
atFieldStart = false;
|
|
946
|
-
field += char;
|
|
947
|
-
i++;
|
|
948
|
-
}
|
|
949
|
-
if (recordStarted || field !== "" || record.length > 0) endRecord();
|
|
950
|
-
return records;
|
|
951
|
-
}
|
|
952
|
-
function readQuoted(input, start, delimiter) {
|
|
953
|
-
let value = "";
|
|
954
|
-
let i = start + 1;
|
|
955
|
-
while (i < input.length) {
|
|
956
|
-
if (input[i] === '"') {
|
|
957
|
-
if (input[i + 1] === '"') {
|
|
958
|
-
value += '"';
|
|
959
|
-
i += 2;
|
|
960
|
-
continue;
|
|
961
|
-
}
|
|
962
|
-
const next = input[i + 1];
|
|
963
|
-
const wellFormed = next === void 0 || next === delimiter || next === "\r" || next === "\n";
|
|
964
|
-
return { value, end: i + 1, wellFormed };
|
|
965
|
-
}
|
|
966
|
-
value += input[i];
|
|
967
|
-
i++;
|
|
968
|
-
}
|
|
969
|
-
return { value, end: i, wellFormed: false };
|
|
970
|
-
}
|
|
971
|
-
function escapeCsvField(value, delimiter = ",", quoteAll = false) {
|
|
972
|
-
if (quoteAll || value.includes(delimiter) || /["\r\n]/.test(value)) {
|
|
973
|
-
return `"${value.replace(/"/g, '""')}"`;
|
|
974
|
-
}
|
|
975
|
-
return value;
|
|
976
|
-
}
|
|
977
|
-
function serializeCsv(rows, options = {}) {
|
|
978
|
-
const delimiter = options.delimiter ?? ",";
|
|
979
|
-
const lineEnding = options.lineEnding ?? "\r\n";
|
|
980
|
-
const body = rows.map(
|
|
981
|
-
(row) => row.map((value) => escapeCsvField(value == null ? "" : String(value), delimiter, options.quoteAll)).join(delimiter)
|
|
982
|
-
).join(lineEnding);
|
|
983
|
-
return options.bom ? BOM + body : body;
|
|
984
|
-
}
|
|
985
|
-
function parseTsv(text) {
|
|
986
|
-
const normalized = text.startsWith(BOM) ? text.slice(1) : text;
|
|
987
|
-
if (!normalized.includes('"')) {
|
|
988
|
-
const body = normalized.replace(/\r\n?/g, "\n").replace(/\n$/, "");
|
|
989
|
-
return body === "" ? [] : body.split("\n").map((line) => line.split(" "));
|
|
990
|
-
}
|
|
991
|
-
return parseCsv(normalized, { delimiter: " ", requireWellFormedQuotes: true });
|
|
992
|
-
}
|
|
993
|
-
function serializeTsv(rows) {
|
|
994
|
-
return rows.map(
|
|
995
|
-
(row) => row.map((value) => {
|
|
996
|
-
const text = value == null ? "" : String(value);
|
|
997
|
-
return /[\t\r\n]/.test(text) || text.startsWith('"') ? `"${text.replace(/"/g, '""')}"` : text;
|
|
998
|
-
}).join(" ")
|
|
999
|
-
).join("\n");
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
986
|
// src/spreadsheet/clipboard.ts
|
|
1003
987
|
var SPREADSHEET_CLIPBOARD_MIME = "application/x.pixerate.spreadsheet+json";
|
|
1004
988
|
var displayText = (value) => value === null || value === void 0 ? "" : String(value);
|
|
@@ -1071,11 +1055,16 @@ function parseHtmlTable(html) {
|
|
|
1071
1055
|
return rows.length > 0 ? rows : null;
|
|
1072
1056
|
}
|
|
1073
1057
|
function createSpreadsheetClipboardData(cells, origin) {
|
|
1074
|
-
const
|
|
1075
|
-
const payload = {
|
|
1058
|
+
const texts = cells.map((row) => row.map((cell) => cell.text ?? displayText(cell.value)));
|
|
1059
|
+
const payload = {
|
|
1060
|
+
type: "pixerate-spreadsheet",
|
|
1061
|
+
version: 1,
|
|
1062
|
+
origin,
|
|
1063
|
+
cells: cells.map((row) => row.map(({ raw, value }) => ({ raw, value })))
|
|
1064
|
+
};
|
|
1076
1065
|
return {
|
|
1077
|
-
"text/plain": serializeTsv(
|
|
1078
|
-
"text/html": serializeHtmlTable(
|
|
1066
|
+
"text/plain": serializeTsv(texts),
|
|
1067
|
+
"text/html": serializeHtmlTable(texts),
|
|
1079
1068
|
[SPREADSHEET_CLIPBOARD_MIME]: JSON.stringify(payload)
|
|
1080
1069
|
};
|
|
1081
1070
|
}
|
|
@@ -1289,6 +1278,7 @@ function buildSpreadsheetView(doc, getValue) {
|
|
|
1289
1278
|
|
|
1290
1279
|
// src/spreadsheet/controller.ts
|
|
1291
1280
|
var nextIdSequence = 0;
|
|
1281
|
+
var DEFAULT_MAX_HISTORY_BYTES = 64 * 1024 * 1024;
|
|
1292
1282
|
var MAX_SPILL_PASSES = 25;
|
|
1293
1283
|
var spillCellId = (rowId, colId) => `${rowId}\0${colId}`;
|
|
1294
1284
|
var SpreadsheetController = class {
|
|
@@ -1308,6 +1298,7 @@ var SpreadsheetController = class {
|
|
|
1308
1298
|
undoStack = [];
|
|
1309
1299
|
redoStack = [];
|
|
1310
1300
|
isApplyingHistory = false;
|
|
1301
|
+
maxHistoryBytes;
|
|
1311
1302
|
// Set while importing TSV: history and recalculation are done once at the end.
|
|
1312
1303
|
batching = false;
|
|
1313
1304
|
pendingCommits = [];
|
|
@@ -1349,6 +1340,7 @@ var SpreadsheetController = class {
|
|
|
1349
1340
|
...options.document?.table ? { table: options.document.table } : {}
|
|
1350
1341
|
};
|
|
1351
1342
|
this.primaryDataSourceId = options.primaryDataSourceId;
|
|
1343
|
+
this.maxHistoryBytes = Math.max(0, options.maxHistoryBytes ?? DEFAULT_MAX_HISTORY_BYTES);
|
|
1352
1344
|
this.onDocumentChange = options.onDocumentChange;
|
|
1353
1345
|
this.onSelectionChange = options.onSelectionChange;
|
|
1354
1346
|
this.onCellCommit = options.onCellCommit;
|
|
@@ -1363,6 +1355,7 @@ var SpreadsheetController = class {
|
|
|
1363
1355
|
getFormulaCell: () => this.evaluatingCell
|
|
1364
1356
|
});
|
|
1365
1357
|
this.syncWithDataSource();
|
|
1358
|
+
this.rederiveTypedValues();
|
|
1366
1359
|
this.recalculateAll();
|
|
1367
1360
|
}
|
|
1368
1361
|
loadDocument(document) {
|
|
@@ -1374,9 +1367,80 @@ var SpreadsheetController = class {
|
|
|
1374
1367
|
this.undoStack = [];
|
|
1375
1368
|
this.redoStack = [];
|
|
1376
1369
|
this.syncWithDataSource();
|
|
1370
|
+
this.rederiveTypedValues();
|
|
1377
1371
|
this.recalculateAll();
|
|
1378
1372
|
this.notifyChange();
|
|
1379
1373
|
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Re-parses the value of every stored typed (non-formula) cell from its
|
|
1376
|
+
* `raw` input, so documents saved before typed input was parsed (whose
|
|
1377
|
+
* values are text such as `"120"`) compare and sort as numbers. Cells of
|
|
1378
|
+
* bound columns in record rows keep their value: it belongs to the data
|
|
1379
|
+
* source.
|
|
1380
|
+
*/
|
|
1381
|
+
rederiveTypedValues() {
|
|
1382
|
+
for (const row of this.document.rows) {
|
|
1383
|
+
const cells = this.document.cells[row.id];
|
|
1384
|
+
if (!cells) continue;
|
|
1385
|
+
for (const col of this.document.columns) {
|
|
1386
|
+
const cell = cells[col.id];
|
|
1387
|
+
if (!cell || typeof cell.raw !== "string" || cell.raw.startsWith("=")) continue;
|
|
1388
|
+
if (col.type === "bound" && col.binding && row.recordId) continue;
|
|
1389
|
+
const typed = this.typedCell(cell.raw, cell.format, col, cell);
|
|
1390
|
+
cell.value = typed.value;
|
|
1391
|
+
if (typed.numberFormat) cell.numberFormat = typed.numberFormat;
|
|
1392
|
+
else delete cell.numberFormat;
|
|
1393
|
+
if (typed.format) cell.format = typed.format;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* Value of typed (non-formula) input: a number, boolean, ISO date, error
|
|
1399
|
+
* or text (see `parseCellInput`), its automatic number format, and the
|
|
1400
|
+
* `CellFormat` to use: the automatic one (currency, percentage, date,
|
|
1401
|
+
* number) when `format` is unset or `text` (the default of new columns).
|
|
1402
|
+
*/
|
|
1403
|
+
typedCell(raw, format, col, existing) {
|
|
1404
|
+
const parsed = parseCellInput(raw);
|
|
1405
|
+
const fixed = this.fixedNumberFormat(col, existing);
|
|
1406
|
+
const numberFormat = fixed ?? parsed.numberFormat;
|
|
1407
|
+
return {
|
|
1408
|
+
value: parsed.value,
|
|
1409
|
+
...numberFormat ? { numberFormat } : {},
|
|
1410
|
+
...existing?.explicitNumberFormat ? { explicitNumberFormat: true } : {},
|
|
1411
|
+
format: fixed === void 0 && parsed.cellFormat && (!format || format === "text") ? parsed.cellFormat : format
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* The number format a cell keeps whatever is typed into it: its explicit
|
|
1416
|
+
* one, else its column's. Undefined lets typed input choose one.
|
|
1417
|
+
*/
|
|
1418
|
+
fixedNumberFormat(col, existing) {
|
|
1419
|
+
if (existing?.explicitNumberFormat && existing.numberFormat !== void 0) return existing.numberFormat;
|
|
1420
|
+
return col?.numberFormat && !isGeneralNumberFormat(col.numberFormat) ? col.numberFormat : void 0;
|
|
1421
|
+
}
|
|
1422
|
+
/** `numberFormat` / `explicitNumberFormat` fields for a formula, bound or spilled cell. */
|
|
1423
|
+
fixedNumberFormatFields(col, existing) {
|
|
1424
|
+
const numberFormat = this.fixedNumberFormat(col, existing);
|
|
1425
|
+
if (numberFormat === void 0) return {};
|
|
1426
|
+
return existing?.explicitNumberFormat ? { numberFormat, explicitNumberFormat: true } : { numberFormat };
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Typed input for a bound field, coerced by the field's declared type:
|
|
1430
|
+
* number, boolean and date fields get a number, boolean or ISO date when
|
|
1431
|
+
* the input parses as one; string and enum fields (and unknown fields)
|
|
1432
|
+
* keep the text exactly as typed.
|
|
1433
|
+
*/
|
|
1434
|
+
boundInputValue(dataSourceId, field, raw) {
|
|
1435
|
+
if (raw.startsWith("=")) return raw;
|
|
1436
|
+
const type = this.binder.getDataSource(dataSourceId)?.getFields().find((f) => f.key === field)?.type;
|
|
1437
|
+
if (type !== "number" && type !== "boolean" && type !== "date") return raw;
|
|
1438
|
+
const parsed = parseCellInput(raw);
|
|
1439
|
+
if (type === "number" && parsed.type === "number") return parsed.value;
|
|
1440
|
+
if (type === "boolean" && parsed.type === "boolean") return parsed.value;
|
|
1441
|
+
if (type === "date" && parsed.type === "date") return parsed.value;
|
|
1442
|
+
return raw;
|
|
1443
|
+
}
|
|
1380
1444
|
registerDataSource(ds) {
|
|
1381
1445
|
this.binder.register(ds);
|
|
1382
1446
|
this.syncWithDataSource();
|
|
@@ -1418,10 +1482,13 @@ var SpreadsheetController = class {
|
|
|
1418
1482
|
const spilled = this.spillCells.get(spillCellId(rowId, colId));
|
|
1419
1483
|
if (spilled) return spilled;
|
|
1420
1484
|
}
|
|
1421
|
-
if (existing) return existing;
|
|
1485
|
+
if (existing && !existing.explicitNumberFormat) return existing;
|
|
1422
1486
|
const col = this.document.columns.find((c) => c.id === colId);
|
|
1423
1487
|
const row = this.document.rows.find((r) => r.id === rowId);
|
|
1424
|
-
|
|
1488
|
+
const derived = this.getDerivedCell(row, col);
|
|
1489
|
+
if (!existing) return derived;
|
|
1490
|
+
if (derived.raw === "") return existing;
|
|
1491
|
+
return { ...derived, numberFormat: existing.numberFormat, explicitNumberFormat: true };
|
|
1425
1492
|
}
|
|
1426
1493
|
/** Value of a cell with no stored data: bound record value, column formula, or blank. */
|
|
1427
1494
|
getDerivedCell(row, col) {
|
|
@@ -1434,17 +1501,19 @@ var SpreadsheetController = class {
|
|
|
1434
1501
|
return {
|
|
1435
1502
|
raw: boundVal !== void 0 ? String(boundVal) : "",
|
|
1436
1503
|
value: boundVal ?? "",
|
|
1437
|
-
format: col.format
|
|
1504
|
+
format: col.format,
|
|
1505
|
+
...this.fixedNumberFormatFields(col, void 0)
|
|
1438
1506
|
};
|
|
1439
1507
|
}
|
|
1440
1508
|
if (col && col.type === "formula" && col.formula) {
|
|
1441
1509
|
return {
|
|
1442
1510
|
raw: col.formula,
|
|
1443
1511
|
value: "",
|
|
1444
|
-
format: col.format
|
|
1512
|
+
format: col.format,
|
|
1513
|
+
...this.fixedNumberFormatFields(col, void 0)
|
|
1445
1514
|
};
|
|
1446
1515
|
}
|
|
1447
|
-
return { raw: "", value: "", format: col?.format };
|
|
1516
|
+
return { raw: "", value: "", format: col?.format, ...this.fixedNumberFormatFields(col, void 0) };
|
|
1448
1517
|
}
|
|
1449
1518
|
setCellValue(rowId, colId, raw) {
|
|
1450
1519
|
this.recordSnapshot();
|
|
@@ -1453,21 +1522,21 @@ var SpreadsheetController = class {
|
|
|
1453
1522
|
}
|
|
1454
1523
|
const col = this.document.columns.find((c) => c.id === colId);
|
|
1455
1524
|
const row = this.document.rows.find((r) => r.id === rowId);
|
|
1525
|
+
const isFormula = raw.startsWith("=");
|
|
1526
|
+
const existing = this.document.cells[rowId][colId];
|
|
1527
|
+
let cell;
|
|
1456
1528
|
if (col?.type === "bound" && col.binding && row?.recordId) {
|
|
1457
|
-
this.
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1529
|
+
const value = this.boundInputValue(col.binding.dataSource, col.binding.field, raw);
|
|
1530
|
+
this.binder.commitBoundValue(col.binding.dataSource, row.recordId, col.binding.field, value);
|
|
1531
|
+
cell = { raw, value, format: col.format, ...this.fixedNumberFormatFields(col, existing) };
|
|
1532
|
+
} else if (isFormula) {
|
|
1533
|
+
cell = { raw, value: raw, format: col?.format, ...this.fixedNumberFormatFields(col, existing) };
|
|
1534
|
+
} else {
|
|
1535
|
+
cell = { raw, ...this.typedCell(raw, col?.format, col, existing) };
|
|
1463
1536
|
}
|
|
1464
1537
|
const cellKey = this.getCellKey(rowId, colId);
|
|
1465
|
-
this.depGraph.setCellFormula(cellKey,
|
|
1466
|
-
this.document.cells[rowId][colId] =
|
|
1467
|
-
raw,
|
|
1468
|
-
value: raw,
|
|
1469
|
-
format: col?.format
|
|
1470
|
-
};
|
|
1538
|
+
this.depGraph.setCellFormula(cellKey, isFormula ? raw : null);
|
|
1539
|
+
this.document.cells[rowId][colId] = cell;
|
|
1471
1540
|
this.document.updatedAt = Date.now();
|
|
1472
1541
|
if (this.batching) {
|
|
1473
1542
|
this.pendingCommits.push([rowId, colId, raw]);
|
|
@@ -1666,7 +1735,13 @@ var SpreadsheetController = class {
|
|
|
1666
1735
|
const rowObj = this.document.rows[row + r];
|
|
1667
1736
|
const key = `${column.key}${row + r + 1}`.toUpperCase();
|
|
1668
1737
|
const value = array.values[c * rows + r] ?? "";
|
|
1669
|
-
const display = {
|
|
1738
|
+
const display = {
|
|
1739
|
+
raw: "",
|
|
1740
|
+
value,
|
|
1741
|
+
format: column.format,
|
|
1742
|
+
spillAnchor: anchor,
|
|
1743
|
+
...this.fixedNumberFormatFields(column, this.document.cells[rowObj.id]?.[column.id])
|
|
1744
|
+
};
|
|
1670
1745
|
if (isFormulaError(value)) {
|
|
1671
1746
|
display.error = value;
|
|
1672
1747
|
display.errorMessage = describeFormulaError(value);
|
|
@@ -1891,6 +1966,7 @@ var SpreadsheetController = class {
|
|
|
1891
1966
|
binding: config?.binding,
|
|
1892
1967
|
formula: config?.formula,
|
|
1893
1968
|
format: config?.format || "text",
|
|
1969
|
+
...config?.numberFormat ? { numberFormat: config.numberFormat } : {},
|
|
1894
1970
|
readOnly: config?.readOnly
|
|
1895
1971
|
};
|
|
1896
1972
|
this.shiftReferences({ axis: "col", index, type: "insert" });
|
|
@@ -1983,7 +2059,7 @@ var SpreadsheetController = class {
|
|
|
1983
2059
|
let maxLen = (col.title || "").length + (col.key ? col.key.length + 3 : 0);
|
|
1984
2060
|
for (const row of this.document.rows) {
|
|
1985
2061
|
const cell = this.getCell(row.id, col.id);
|
|
1986
|
-
const valStr =
|
|
2062
|
+
const valStr = formatCellDisplay(cell);
|
|
1987
2063
|
if (valStr.length > maxLen) {
|
|
1988
2064
|
maxLen = valStr.length;
|
|
1989
2065
|
}
|
|
@@ -2007,8 +2083,7 @@ var SpreadsheetController = class {
|
|
|
2007
2083
|
) : this.document.columns;
|
|
2008
2084
|
return rows.map(
|
|
2009
2085
|
(row) => columns.map((col) => {
|
|
2010
|
-
|
|
2011
|
-
return String(cell.value ?? "").replace(/\t/g, " ");
|
|
2086
|
+
return formatCellDisplay(this.getCell(row.id, col.id)).replace(/\t/g, " ");
|
|
2012
2087
|
}).join(" ")
|
|
2013
2088
|
).join("\n");
|
|
2014
2089
|
}
|
|
@@ -2085,7 +2160,7 @@ var SpreadsheetController = class {
|
|
|
2085
2160
|
*/
|
|
2086
2161
|
exportToCsv(options = {}) {
|
|
2087
2162
|
const matrix = this.readMatrix(options.range ?? null).map(
|
|
2088
|
-
(row) => row.map((cell) => options.formulas ? cell.raw :
|
|
2163
|
+
(row) => row.map((cell) => options.formulas ? cell.raw : formatCellDisplay(cell))
|
|
2089
2164
|
);
|
|
2090
2165
|
return serializeCsv(matrix, {
|
|
2091
2166
|
delimiter: options.delimiter,
|
|
@@ -2126,10 +2201,11 @@ var SpreadsheetController = class {
|
|
|
2126
2201
|
const cells = this.readMatrix(r).map(
|
|
2127
2202
|
(row) => row.map((cell) => {
|
|
2128
2203
|
const value = cell.error || cell.value;
|
|
2204
|
+
const text = formatCellDisplay(cell);
|
|
2129
2205
|
if (cell.spillAnchor && !this.spillAnchorInRange(cell.spillAnchor, r)) {
|
|
2130
|
-
return { raw:
|
|
2206
|
+
return { raw: text, value, text };
|
|
2131
2207
|
}
|
|
2132
|
-
return { raw: cell.raw, value };
|
|
2208
|
+
return { raw: cell.raw, value, text };
|
|
2133
2209
|
})
|
|
2134
2210
|
);
|
|
2135
2211
|
return createSpreadsheetClipboardData(cells, { row: r.startRow, col: r.startCol });
|
|
@@ -2378,6 +2454,94 @@ var SpreadsheetController = class {
|
|
|
2378
2454
|
this.table[key] = value;
|
|
2379
2455
|
});
|
|
2380
2456
|
}
|
|
2457
|
+
// --- Number formats ---
|
|
2458
|
+
//
|
|
2459
|
+
// Cells are replaced, never mutated in place: framework bindings compare
|
|
2460
|
+
// cell objects by identity to decide what to re-render.
|
|
2461
|
+
/**
|
|
2462
|
+
* Sets an explicit number format code (e.g. `0.00`, `"$"#,##0.00`,
|
|
2463
|
+
* `m/d/yyyy`, `[Red]0;[Blue]-0`) on every cell in `range`, including blank
|
|
2464
|
+
* cells, so values typed later keep it. `null` removes explicit formats: the
|
|
2465
|
+
* cells go back to their column's format or the one chosen for typed input.
|
|
2466
|
+
* Undoable as one step. Returns false when there is no range.
|
|
2467
|
+
*/
|
|
2468
|
+
setCellNumberFormat(range, code) {
|
|
2469
|
+
if (!range) return false;
|
|
2470
|
+
const r = normalizeRange(range);
|
|
2471
|
+
this.recordSnapshot();
|
|
2472
|
+
const kind = code === null ? void 0 : cellFormatForNumberFormat(code);
|
|
2473
|
+
for (let ri = r.startRow; ri <= r.endRow; ri++) {
|
|
2474
|
+
const row = this.document.rows[ri];
|
|
2475
|
+
if (!row) continue;
|
|
2476
|
+
for (let ci = r.startCol; ci <= r.endCol; ci++) {
|
|
2477
|
+
const col = this.document.columns[ci];
|
|
2478
|
+
if (!col) continue;
|
|
2479
|
+
const cells = this.document.cells[row.id] ??= {};
|
|
2480
|
+
const existing = cells[col.id];
|
|
2481
|
+
if (code === null) {
|
|
2482
|
+
if (!existing?.explicitNumberFormat) continue;
|
|
2483
|
+
if (existing.raw === "" && existing.value === "" && !existing.error) {
|
|
2484
|
+
delete cells[col.id];
|
|
2485
|
+
} else {
|
|
2486
|
+
const { explicitNumberFormat: _explicit, ...rest } = existing;
|
|
2487
|
+
cells[col.id] = this.withNumberFormat(rest, this.implicitNumberFormat(rest, col, row));
|
|
2488
|
+
}
|
|
2489
|
+
} else {
|
|
2490
|
+
cells[col.id] = {
|
|
2491
|
+
...existing ?? { raw: "", value: "" },
|
|
2492
|
+
format: kind ?? existing?.format ?? col.format,
|
|
2493
|
+
numberFormat: code,
|
|
2494
|
+
explicitNumberFormat: true
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2497
|
+
this.refreshSpilledNumberFormat(row, col);
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
this.document.updatedAt = Date.now();
|
|
2501
|
+
this.notifyChange();
|
|
2502
|
+
return true;
|
|
2503
|
+
}
|
|
2504
|
+
/**
|
|
2505
|
+
* Sets the number format code of a column (`null` or `General` clears it).
|
|
2506
|
+
* Its cells without an explicit format of their own use it, including
|
|
2507
|
+
* values typed later; when cleared they go back to the format chosen for
|
|
2508
|
+
* typed input. Undoable.
|
|
2509
|
+
*/
|
|
2510
|
+
setColumnNumberFormat(colId, code) {
|
|
2511
|
+
const col = this.getColumn(colId);
|
|
2512
|
+
if (!col) return;
|
|
2513
|
+
this.recordSnapshot();
|
|
2514
|
+
if (code === null || isGeneralNumberFormat(code)) delete col.numberFormat;
|
|
2515
|
+
else col.numberFormat = code;
|
|
2516
|
+
for (const row of this.document.rows) {
|
|
2517
|
+
const cells = this.document.cells[row.id];
|
|
2518
|
+
const cell = cells?.[col.id];
|
|
2519
|
+
if (cells && cell && !cell.explicitNumberFormat) {
|
|
2520
|
+
cells[col.id] = this.withNumberFormat(cell, this.implicitNumberFormat(cell, col, row));
|
|
2521
|
+
}
|
|
2522
|
+
this.refreshSpilledNumberFormat(row, col);
|
|
2523
|
+
}
|
|
2524
|
+
this.document.updatedAt = Date.now();
|
|
2525
|
+
this.notifyChange();
|
|
2526
|
+
}
|
|
2527
|
+
/** The column's format, or the one typed input chose (typed cells only). */
|
|
2528
|
+
implicitNumberFormat(cell, col, row) {
|
|
2529
|
+
const typed = cell.raw !== "" && !cell.raw.startsWith("=") && !(col.type === "bound" && col.binding && row.recordId);
|
|
2530
|
+
return this.fixedNumberFormat(col, void 0) ?? (typed ? parseCellInput(cell.raw).numberFormat : void 0);
|
|
2531
|
+
}
|
|
2532
|
+
/** A copy of `cell` with `numberFormat` set, or removed when undefined. */
|
|
2533
|
+
withNumberFormat(cell, numberFormat) {
|
|
2534
|
+
const { numberFormat: _old, ...rest } = cell;
|
|
2535
|
+
return numberFormat === void 0 ? rest : { ...rest, numberFormat };
|
|
2536
|
+
}
|
|
2537
|
+
/** Re-applies the stored cell's (or the column's) format to a spilled value shown there. */
|
|
2538
|
+
refreshSpilledNumberFormat(row, col) {
|
|
2539
|
+
const id = spillCellId(row.id, col.id);
|
|
2540
|
+
const spilled = this.spillCells.get(id);
|
|
2541
|
+
if (!spilled) return;
|
|
2542
|
+
const { numberFormat: _old, explicitNumberFormat: _explicit, ...rest } = spilled;
|
|
2543
|
+
this.spillCells.set(id, { ...rest, ...this.fixedNumberFormatFields(col, this.document.cells[row.id]?.[col.id]) });
|
|
2544
|
+
}
|
|
2381
2545
|
// --- Hide / unhide ---
|
|
2382
2546
|
hideRows(indices) {
|
|
2383
2547
|
this.setHidden("row", indices, true);
|
|
@@ -2648,6 +2812,28 @@ var SpreadsheetController = class {
|
|
|
2648
2812
|
this.undoStack.push(JSON.stringify(this.document));
|
|
2649
2813
|
if (this.undoStack.length > 50) this.undoStack.shift();
|
|
2650
2814
|
this.redoStack = [];
|
|
2815
|
+
this.trimHistory();
|
|
2816
|
+
}
|
|
2817
|
+
/** Drops the oldest snapshots beyond `maxHistoryBytes`, always keeping the newest. */
|
|
2818
|
+
trimHistory() {
|
|
2819
|
+
if (this.maxHistoryBytes <= 0 || this.undoStack.length <= 1) return;
|
|
2820
|
+
let total = 0;
|
|
2821
|
+
for (const s of this.undoStack) total += s.length * 2;
|
|
2822
|
+
for (const s of this.redoStack) total += s.length * 2;
|
|
2823
|
+
while (total > this.maxHistoryBytes && this.undoStack.length > 1) {
|
|
2824
|
+
total -= this.undoStack.shift().length * 2;
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
/** Approximate bytes held by the undo/redo snapshots (UTF-16 size). */
|
|
2828
|
+
getHistoryByteSize() {
|
|
2829
|
+
let total = 0;
|
|
2830
|
+
for (const s of this.undoStack) total += s.length * 2;
|
|
2831
|
+
for (const s of this.redoStack) total += s.length * 2;
|
|
2832
|
+
return total;
|
|
2833
|
+
}
|
|
2834
|
+
/** Number of undo / redo snapshots currently held. */
|
|
2835
|
+
getHistoryDepth() {
|
|
2836
|
+
return { undo: this.undoStack.length, redo: this.redoStack.length };
|
|
2651
2837
|
}
|
|
2652
2838
|
undo() {
|
|
2653
2839
|
if (this.undoStack.length === 0) return;
|
|
@@ -2689,6 +2875,17 @@ function stepInOrder(order, from, delta) {
|
|
|
2689
2875
|
}
|
|
2690
2876
|
|
|
2691
2877
|
// src/spreadsheet/menu.ts
|
|
2878
|
+
function runSpreadsheetMenuItem(item, ask) {
|
|
2879
|
+
if (!item.input) {
|
|
2880
|
+
item.run();
|
|
2881
|
+
return true;
|
|
2882
|
+
}
|
|
2883
|
+
const prompt = ask ?? ((label, value) => typeof window !== "undefined" && window.prompt ? window.prompt(label, value) : null);
|
|
2884
|
+
const text = prompt(item.input.label, item.input.defaultValue ?? "");
|
|
2885
|
+
if (text === null || text === void 0) return false;
|
|
2886
|
+
item.run(text);
|
|
2887
|
+
return true;
|
|
2888
|
+
}
|
|
2692
2889
|
function clampMenuPosition(point, size, viewport, margin = 4) {
|
|
2693
2890
|
const place = (start, length, limit) => {
|
|
2694
2891
|
if (start + length <= limit - margin) return Math.max(margin, start);
|
|
@@ -2711,7 +2908,8 @@ function getSpreadsheetMenuItems(controller, cell) {
|
|
|
2711
2908
|
if (!column || !row) return [];
|
|
2712
2909
|
const selection = controller.selectedRange && rangeContains(controller.selectedRange, cell.row, cell.col) ? normalizeRange(controller.selectedRange) : { startRow: cell.row, startCol: cell.col, endRow: cell.row, endCol: cell.col };
|
|
2713
2910
|
const colName = indexToColName(cell.col);
|
|
2714
|
-
const
|
|
2911
|
+
const cellData = controller.getCell(row.id, column.id);
|
|
2912
|
+
const value = cellData.value;
|
|
2715
2913
|
const table = doc.table ?? {};
|
|
2716
2914
|
const filter = controller.getFilterState();
|
|
2717
2915
|
const grouping = table.grouping;
|
|
@@ -2733,7 +2931,7 @@ function getSpreadsheetMenuItems(controller, cell) {
|
|
|
2733
2931
|
},
|
|
2734
2932
|
{
|
|
2735
2933
|
id: "filter-equals",
|
|
2736
|
-
label: `Show only rows where ${colName} = ${quote(
|
|
2934
|
+
label: `Show only rows where ${colName} = ${quote(formatCellDisplay(cellData))}`,
|
|
2737
2935
|
separatorBefore: true,
|
|
2738
2936
|
run: () => controller.addFilterRule({ colId: column.id, operator: "equals", value: String(value ?? "") })
|
|
2739
2937
|
},
|
|
@@ -2822,6 +3020,31 @@ function getSpreadsheetMenuItems(controller, cell) {
|
|
|
2822
3020
|
}
|
|
2823
3021
|
}
|
|
2824
3022
|
];
|
|
3023
|
+
const currentFormat = cellData.numberFormat;
|
|
3024
|
+
const presetMatch = NUMBER_FORMAT_PRESETS.find(
|
|
3025
|
+
(p) => p.id === "general" ? isGeneralNumberFormat(currentFormat) : p.code === currentFormat
|
|
3026
|
+
);
|
|
3027
|
+
NUMBER_FORMAT_PRESETS.forEach((preset, i) => {
|
|
3028
|
+
items.push({
|
|
3029
|
+
id: `number-format-${preset.id}`,
|
|
3030
|
+
label: `Format cells: ${preset.label}`,
|
|
3031
|
+
separatorBefore: i === 0,
|
|
3032
|
+
checked: presetMatch === preset,
|
|
3033
|
+
run: () => {
|
|
3034
|
+
controller.setCellNumberFormat(selection, preset.code);
|
|
3035
|
+
}
|
|
3036
|
+
});
|
|
3037
|
+
});
|
|
3038
|
+
items.push({
|
|
3039
|
+
id: "number-format-custom",
|
|
3040
|
+
label: "Format cells: Custom\u2026",
|
|
3041
|
+
checked: !presetMatch,
|
|
3042
|
+
input: { label: "Number format code, e.g. #,##0.00;[Red](#,##0.00)", defaultValue: currentFormat ?? "General" },
|
|
3043
|
+
run: (code) => {
|
|
3044
|
+
if (code === void 0) return;
|
|
3045
|
+
controller.setCellNumberFormat(selection, code.trim() === "" ? null : code);
|
|
3046
|
+
}
|
|
3047
|
+
});
|
|
2825
3048
|
if (grouping) {
|
|
2826
3049
|
items.push(
|
|
2827
3050
|
{
|
|
@@ -2880,6 +3103,8 @@ var SPREADSHEET_TABLE_ACTIONS = [
|
|
|
2880
3103
|
"toggleGroupCollapsed",
|
|
2881
3104
|
"getGroups",
|
|
2882
3105
|
"transposeRange",
|
|
3106
|
+
"setCellNumberFormat",
|
|
3107
|
+
"setColumnNumberFormat",
|
|
2883
3108
|
"exportToCsv",
|
|
2884
3109
|
"importFromCsv",
|
|
2885
3110
|
"copyToClipboardData",
|
|
@@ -2948,6 +3173,13 @@ export {
|
|
|
2948
3173
|
getFormulaCaretContext,
|
|
2949
3174
|
getFormulaHints,
|
|
2950
3175
|
applyFormulaSuggestion,
|
|
3176
|
+
NUMBER_FORMAT_PRESETS,
|
|
3177
|
+
numberFormatForCellFormat,
|
|
3178
|
+
cellFormatForNumberFormat,
|
|
3179
|
+
isGeneralNumberFormat,
|
|
3180
|
+
formatCellForDisplay,
|
|
3181
|
+
formatCellDisplay,
|
|
3182
|
+
formatValueWithCode,
|
|
2951
3183
|
DataSourceBinder,
|
|
2952
3184
|
normalizeRange,
|
|
2953
3185
|
rangeContains,
|
|
@@ -2963,11 +3195,6 @@ export {
|
|
|
2963
3195
|
fillSeries,
|
|
2964
3196
|
computeFillTarget,
|
|
2965
3197
|
getFillDirection,
|
|
2966
|
-
parseCsv,
|
|
2967
|
-
escapeCsvField,
|
|
2968
|
-
serializeCsv,
|
|
2969
|
-
parseTsv,
|
|
2970
|
-
serializeTsv,
|
|
2971
3198
|
SPREADSHEET_CLIPBOARD_MIME,
|
|
2972
3199
|
serializeHtmlTable,
|
|
2973
3200
|
parseHtmlTable,
|
|
@@ -2982,6 +3209,7 @@ export {
|
|
|
2982
3209
|
computeRowGroups,
|
|
2983
3210
|
buildSpreadsheetView,
|
|
2984
3211
|
SpreadsheetController,
|
|
3212
|
+
runSpreadsheetMenuItem,
|
|
2985
3213
|
clampMenuPosition,
|
|
2986
3214
|
getSpreadsheetMenuItems,
|
|
2987
3215
|
SPREADSHEET_TABLE_ACTIONS,
|
|
@@ -2992,4 +3220,4 @@ export {
|
|
|
2992
3220
|
registerSpreadsheetImportAdapters,
|
|
2993
3221
|
importFileIntoSpreadsheet
|
|
2994
3222
|
};
|
|
2995
|
-
//# sourceMappingURL=chunk-
|
|
3223
|
+
//# sourceMappingURL=chunk-U5KWZU4X.js.map
|