@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.
Files changed (188) hide show
  1. package/README.md +175 -2
  2. package/THIRD_PARTY_NOTICES.md +139 -0
  3. package/dist/agent/index.cjs +87 -111
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +70 -211
  6. package/dist/agent/index.d.ts +70 -211
  7. package/dist/agent/index.js +78 -102
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/arrangeShortcuts-B8O3f8-P.d.cts +184 -0
  10. package/dist/arrangeShortcuts-sLlD2rSX.d.ts +184 -0
  11. package/dist/canvas/index.cjs +2075 -86
  12. package/dist/canvas/index.cjs.map +1 -1
  13. package/dist/canvas/index.d.cts +608 -186
  14. package/dist/canvas/index.d.ts +608 -186
  15. package/dist/canvas/index.js +2060 -71
  16. package/dist/canvas/index.js.map +1 -1
  17. package/dist/chunk-26BVFKHU.cjs +453 -0
  18. package/dist/chunk-26BVFKHU.cjs.map +1 -0
  19. package/dist/chunk-2H7ZHPEJ.js +762 -0
  20. package/dist/chunk-2H7ZHPEJ.js.map +1 -0
  21. package/dist/{chunk-DENSCKFC.js → chunk-3CN2WD3X.js} +1730 -189
  22. package/dist/chunk-3CN2WD3X.js.map +1 -0
  23. package/dist/{chunk-JJDLPEWL.js → chunk-3LV6RRPH.js} +2 -2
  24. package/dist/{chunk-MJZBNWG7.js → chunk-5S3Z4PPF.js} +78 -557
  25. package/dist/chunk-5S3Z4PPF.js.map +1 -0
  26. package/dist/chunk-664LQYFJ.js +115 -0
  27. package/dist/chunk-664LQYFJ.js.map +1 -0
  28. package/dist/chunk-7DWMJCAT.js +453 -0
  29. package/dist/chunk-7DWMJCAT.js.map +1 -0
  30. package/dist/chunk-ARRTP33I.js +101 -0
  31. package/dist/chunk-ARRTP33I.js.map +1 -0
  32. package/dist/chunk-CPOKFNBG.cjs +101 -0
  33. package/dist/chunk-CPOKFNBG.cjs.map +1 -0
  34. package/dist/{chunk-AA4ZEVCC.cjs → chunk-D27YFC46.cjs} +101 -580
  35. package/dist/chunk-D27YFC46.cjs.map +1 -0
  36. package/dist/chunk-D6FH2YAX.cjs +131 -0
  37. package/dist/chunk-D6FH2YAX.cjs.map +1 -0
  38. package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
  39. package/dist/chunk-FD4LPFAE.cjs.map +1 -0
  40. package/dist/chunk-FODNLX5N.js +749 -0
  41. package/dist/chunk-FODNLX5N.js.map +1 -0
  42. package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
  43. package/dist/chunk-FPENXAEJ.js.map +1 -0
  44. package/dist/chunk-G2I4QJOT.cjs +762 -0
  45. package/dist/chunk-G2I4QJOT.cjs.map +1 -0
  46. package/dist/chunk-HLIMTC6N.js +1 -0
  47. package/dist/chunk-HLIMTC6N.js.map +1 -0
  48. package/dist/{chunk-QGHGJ56O.js → chunk-ITBSKI26.js} +1483 -19
  49. package/dist/chunk-ITBSKI26.js.map +1 -0
  50. package/dist/{chunk-6M3BCTBD.cjs → chunk-MA2F5KHF.cjs} +470 -242
  51. package/dist/chunk-MA2F5KHF.cjs.map +1 -0
  52. package/dist/{chunk-SAZRENEZ.cjs → chunk-MCSJFWAO.cjs} +1733 -192
  53. package/dist/chunk-MCSJFWAO.cjs.map +1 -0
  54. package/dist/chunk-MKA3AHLV.cjs +256 -0
  55. package/dist/chunk-MKA3AHLV.cjs.map +1 -0
  56. package/dist/chunk-OLZJXL4R.cjs +749 -0
  57. package/dist/chunk-OLZJXL4R.cjs.map +1 -0
  58. package/dist/chunk-OXSV64JX.js +689 -0
  59. package/dist/chunk-OXSV64JX.js.map +1 -0
  60. package/dist/chunk-P3RQ5UJE.cjs +689 -0
  61. package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
  62. package/dist/chunk-P6ODPZ4W.js +256 -0
  63. package/dist/chunk-P6ODPZ4W.js.map +1 -0
  64. package/dist/chunk-PGNZKFQS.js +685 -0
  65. package/dist/chunk-PGNZKFQS.js.map +1 -0
  66. package/dist/chunk-PL4JB3BF.cjs +1 -0
  67. package/dist/chunk-PL4JB3BF.cjs.map +1 -0
  68. package/dist/{chunk-GVSMRPHB.cjs → chunk-SBCZ6RN5.cjs} +1568 -104
  69. package/dist/chunk-SBCZ6RN5.cjs.map +1 -0
  70. package/dist/{chunk-CL37JGFW.cjs → chunk-SML64VJG.cjs} +4 -4
  71. package/dist/{chunk-CL37JGFW.cjs.map → chunk-SML64VJG.cjs.map} +1 -1
  72. package/dist/chunk-TNNJXF46.cjs +115 -0
  73. package/dist/chunk-TNNJXF46.cjs.map +1 -0
  74. package/dist/chunk-U5HZSFCS.js +131 -0
  75. package/dist/chunk-U5HZSFCS.js.map +1 -0
  76. package/dist/{chunk-RU52I7QO.js → chunk-U5KWZU4X.js} +388 -160
  77. package/dist/chunk-U5KWZU4X.js.map +1 -0
  78. package/dist/chunk-XNORY7M6.cjs +685 -0
  79. package/dist/chunk-XNORY7M6.cjs.map +1 -0
  80. package/dist/commands/index.cjs +860 -0
  81. package/dist/commands/index.cjs.map +1 -0
  82. package/dist/commands/index.d.cts +244 -0
  83. package/dist/commands/index.d.ts +244 -0
  84. package/dist/commands/index.js +860 -0
  85. package/dist/commands/index.js.map +1 -0
  86. package/dist/comment-store-BEMgVlAA.d.cts +135 -0
  87. package/dist/comment-store-BEMgVlAA.d.ts +135 -0
  88. package/dist/compare/index.cjs +15 -0
  89. package/dist/compare/index.cjs.map +1 -0
  90. package/dist/compare/index.d.cts +58 -0
  91. package/dist/compare/index.d.ts +58 -0
  92. package/dist/compare/index.js +15 -0
  93. package/dist/compare/index.js.map +1 -0
  94. package/dist/{controller-B7GIybc4.d.cts → controller-BhLLAoi8.d.cts} +119 -5
  95. package/dist/{controller-B7GIybc4.d.ts → controller-BhLLAoi8.d.ts} +119 -5
  96. package/dist/documents-BQjzJNe2.d.ts +76 -0
  97. package/dist/documents-KRTPhvst.d.cts +76 -0
  98. package/dist/extensions/index.cjs +53 -5
  99. package/dist/extensions/index.cjs.map +1 -1
  100. package/dist/extensions/index.d.cts +7 -3
  101. package/dist/extensions/index.d.ts +7 -3
  102. package/dist/extensions/index.js +52 -4
  103. package/dist/generation/index.cjs +1373 -0
  104. package/dist/generation/index.cjs.map +1 -0
  105. package/dist/generation/index.d.cts +554 -0
  106. package/dist/generation/index.d.ts +554 -0
  107. package/dist/generation/index.js +1373 -0
  108. package/dist/generation/index.js.map +1 -0
  109. package/dist/grammar/index.cjs +48 -3
  110. package/dist/grammar/index.cjs.map +1 -1
  111. package/dist/grammar/index.d.cts +322 -100
  112. package/dist/grammar/index.d.ts +322 -100
  113. package/dist/grammar/index.js +54 -9
  114. package/dist/history/index.cjs +12 -2
  115. package/dist/history/index.cjs.map +1 -1
  116. package/dist/history/index.d.cts +211 -7
  117. package/dist/history/index.d.ts +211 -7
  118. package/dist/history/index.js +13 -3
  119. package/dist/image-editor/index.cjs +2244 -38
  120. package/dist/image-editor/index.cjs.map +1 -1
  121. package/dist/image-editor/index.d.cts +742 -4
  122. package/dist/image-editor/index.d.ts +742 -4
  123. package/dist/image-editor/index.js +2222 -16
  124. package/dist/image-editor/index.js.map +1 -1
  125. package/dist/{index-CCr_z9kF.d.cts → index-BhJBSm2d.d.cts} +370 -4
  126. package/dist/{index-Ck6ajwXh.d.ts → index-fJrlkJCd.d.ts} +370 -4
  127. package/dist/index.cjs +193 -98
  128. package/dist/index.cjs.map +1 -1
  129. package/dist/index.d.cts +17 -48
  130. package/dist/index.d.ts +17 -48
  131. package/dist/index.js +198 -103
  132. package/dist/index.js.map +1 -1
  133. package/dist/migrate-iTlm3a8h.d.cts +112 -0
  134. package/dist/migrate-iTlm3a8h.d.ts +112 -0
  135. package/dist/persistence/index.cjs +676 -0
  136. package/dist/persistence/index.cjs.map +1 -0
  137. package/dist/persistence/index.d.cts +347 -0
  138. package/dist/persistence/index.d.ts +347 -0
  139. package/dist/persistence/index.js +676 -0
  140. package/dist/persistence/index.js.map +1 -0
  141. package/dist/registry-BDe-vlv3.d.ts +171 -0
  142. package/dist/registry-D32LpIc4.d.cts +354 -0
  143. package/dist/registry-DnzpgY27.d.cts +171 -0
  144. package/dist/registry-evKTP4ls.d.ts +354 -0
  145. package/dist/schema-DYDGdYL9.d.cts +41 -0
  146. package/dist/schema-DYDGdYL9.d.ts +41 -0
  147. package/dist/serializers/index.cjs +4 -3
  148. package/dist/serializers/index.cjs.map +1 -1
  149. package/dist/serializers/index.d.cts +2 -1
  150. package/dist/serializers/index.d.ts +2 -1
  151. package/dist/serializers/index.js +3 -2
  152. package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
  153. package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
  154. package/dist/spreadsheet/index.cjs +53 -3
  155. package/dist/spreadsheet/index.cjs.map +1 -1
  156. package/dist/spreadsheet/index.d.cts +247 -18
  157. package/dist/spreadsheet/index.d.ts +247 -18
  158. package/dist/spreadsheet/index.js +58 -8
  159. package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
  160. package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
  161. package/dist/{track-changes-BSF58RK0.d.cts → track-changes-BK5sVf4_.d.cts} +18 -136
  162. package/dist/{track-changes-BSF58RK0.d.ts → track-changes-CDjCrwKe.d.ts} +18 -136
  163. package/dist/{types-CJ4U-HHI.d.ts → types-DX4Mh4SL.d.cts} +1 -1
  164. package/dist/{types-CJ4U-HHI.d.cts → types-DX4Mh4SL.d.ts} +1 -1
  165. package/dist/types-GT5a5YAX.d.cts +86 -0
  166. package/dist/types-GT5a5YAX.d.ts +86 -0
  167. package/package.json +23 -2
  168. package/dist/chunk-2TWGLC5K.cjs +0 -262
  169. package/dist/chunk-2TWGLC5K.cjs.map +0 -1
  170. package/dist/chunk-4SUNJZWB.cjs.map +0 -1
  171. package/dist/chunk-6M3BCTBD.cjs.map +0 -1
  172. package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
  173. package/dist/chunk-AXV2ZW34.cjs +0 -118
  174. package/dist/chunk-AXV2ZW34.cjs.map +0 -1
  175. package/dist/chunk-B2ZRLN4S.js +0 -118
  176. package/dist/chunk-B2ZRLN4S.js.map +0 -1
  177. package/dist/chunk-DENSCKFC.js.map +0 -1
  178. package/dist/chunk-GVSMRPHB.cjs.map +0 -1
  179. package/dist/chunk-KAADNAPF.js +0 -262
  180. package/dist/chunk-KAADNAPF.js.map +0 -1
  181. package/dist/chunk-MJZBNWG7.js.map +0 -1
  182. package/dist/chunk-OYXSKQFR.js.map +0 -1
  183. package/dist/chunk-QGHGJ56O.js.map +0 -1
  184. package/dist/chunk-RU52I7QO.js.map +0 -1
  185. package/dist/chunk-SAZRENEZ.cjs.map +0 -1
  186. package/dist/types-BZgcDsC5.d.cts +0 -42
  187. package/dist/types-BZgcDsC5.d.ts +0 -42
  188. /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-DENSCKFC.js";
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 (aNum !== null && bNum !== null) result = aNum - bNum;
685
- else if (aNum !== null) result = -1;
686
- else if (bNum !== null) result = 1;
687
- else result = compareText(String(a), String(b));
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
- if (isBlankValue(value) || isBlankValue(operand)) return null;
693
- const a = toNumber(value);
694
- const b = toNumber(operand);
695
- if (a !== null && b !== null) return a - b;
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 a = toNumber(value);
701
- const b = toNumber(operand);
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 date = ISO_DATE_PATTERN.exec(raw.trim());
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 values = cells.map((row) => row.map((cell) => cell.value));
1075
- const payload = { type: "pixerate-spreadsheet", version: 1, origin, cells };
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(values.map((row) => row.map(displayText))),
1078
- "text/html": serializeHtmlTable(values),
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
- return this.getDerivedCell(row, col);
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.binder.commitBoundValue(
1458
- col.binding.dataSource,
1459
- row.recordId,
1460
- col.binding.field,
1461
- raw
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, raw.startsWith("=") ? raw : null);
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 = { raw: "", value, format: column.format, spillAnchor: anchor };
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 = cell.value != null ? String(cell.value) : "";
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
- const cell = this.getCell(row.id, col.id);
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 : String(cell.value ?? ""))
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: value === null || value === void 0 ? "" : String(value), value };
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 value = controller.getCell(row.id, column.id).value;
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(value)}`,
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-RU52I7QO.js.map
3223
+ //# sourceMappingURL=chunk-U5KWZU4X.js.map