@pixerate/editor 0.23.0 → 0.25.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 (187) hide show
  1. package/README.md +167 -2
  2. package/THIRD_PARTY_NOTICES.md +80 -0
  3. package/dist/agent/index.cjs +10 -8
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +4 -3
  6. package/dist/agent/index.d.ts +4 -3
  7. package/dist/agent/index.js +4 -2
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/canvas/index.cjs +4437 -411
  10. package/dist/canvas/index.cjs.map +1 -1
  11. package/dist/canvas/index.d.cts +934 -4
  12. package/dist/canvas/index.d.ts +934 -4
  13. package/dist/canvas/index.js +4309 -283
  14. package/dist/canvas/index.js.map +1 -1
  15. package/dist/chunk-4BVP64JJ.cjs +1406 -0
  16. package/dist/chunk-4BVP64JJ.cjs.map +1 -0
  17. package/dist/chunk-664LQYFJ.js +115 -0
  18. package/dist/chunk-664LQYFJ.js.map +1 -0
  19. package/dist/chunk-7WISYZFO.cjs +740 -0
  20. package/dist/chunk-7WISYZFO.cjs.map +1 -0
  21. package/dist/chunk-AL6JMGWW.cjs +783 -0
  22. package/dist/chunk-AL6JMGWW.cjs.map +1 -0
  23. package/dist/chunk-BVP3TSLH.cjs +69 -0
  24. package/dist/chunk-BVP3TSLH.cjs.map +1 -0
  25. package/dist/{chunk-RIKBJLS2.cjs → chunk-C7P22VIP.cjs} +2001 -279
  26. package/dist/chunk-C7P22VIP.cjs.map +1 -0
  27. package/dist/chunk-D6FH2YAX.cjs +131 -0
  28. package/dist/chunk-D6FH2YAX.cjs.map +1 -0
  29. package/dist/chunk-ECCCYKGL.js +740 -0
  30. package/dist/chunk-ECCCYKGL.js.map +1 -0
  31. package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
  32. package/dist/chunk-FD4LPFAE.cjs.map +1 -0
  33. package/dist/{chunk-QBJ22YZR.js → chunk-FH6F67DN.js} +1999 -277
  34. package/dist/chunk-FH6F67DN.js.map +1 -0
  35. package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
  36. package/dist/chunk-FPENXAEJ.js.map +1 -0
  37. package/dist/chunk-HLIMTC6N.js +1 -0
  38. package/dist/chunk-HLIMTC6N.js.map +1 -0
  39. package/dist/chunk-HRMEDT2L.js +1406 -0
  40. package/dist/chunk-HRMEDT2L.js.map +1 -0
  41. package/dist/{chunk-QGHGJ56O.js → chunk-IXHSYOVY.js} +2518 -122
  42. package/dist/chunk-IXHSYOVY.js.map +1 -0
  43. package/dist/chunk-MKA3AHLV.cjs +256 -0
  44. package/dist/chunk-MKA3AHLV.cjs.map +1 -0
  45. package/dist/chunk-NAXVVS5I.js +783 -0
  46. package/dist/chunk-NAXVVS5I.js.map +1 -0
  47. package/dist/chunk-O5RYH2PG.js +69 -0
  48. package/dist/chunk-O5RYH2PG.js.map +1 -0
  49. package/dist/chunk-OXSV64JX.js +689 -0
  50. package/dist/chunk-OXSV64JX.js.map +1 -0
  51. package/dist/chunk-P3RQ5UJE.cjs +689 -0
  52. package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
  53. package/dist/chunk-P6ODPZ4W.js +256 -0
  54. package/dist/chunk-P6ODPZ4W.js.map +1 -0
  55. package/dist/chunk-PL4JB3BF.cjs +1 -0
  56. package/dist/chunk-PL4JB3BF.cjs.map +1 -0
  57. package/dist/{chunk-GVSMRPHB.cjs → chunk-PO6A6QQH.cjs} +2607 -211
  58. package/dist/chunk-PO6A6QQH.cjs.map +1 -0
  59. package/dist/{chunk-JJDLPEWL.js → chunk-QGYDWPRM.js} +2 -2
  60. package/dist/chunk-RR7F5GUM.js +5726 -0
  61. package/dist/chunk-RR7F5GUM.js.map +1 -0
  62. package/dist/chunk-TNNJXF46.cjs +115 -0
  63. package/dist/chunk-TNNJXF46.cjs.map +1 -0
  64. package/dist/chunk-U5HZSFCS.js +131 -0
  65. package/dist/chunk-U5HZSFCS.js.map +1 -0
  66. package/dist/{chunk-MJZBNWG7.js → chunk-UAKDG62Z.js} +309 -581
  67. package/dist/chunk-UAKDG62Z.js.map +1 -0
  68. package/dist/{chunk-CL37JGFW.cjs → chunk-V3CHHWOE.cjs} +4 -4
  69. package/dist/{chunk-CL37JGFW.cjs.map → chunk-V3CHHWOE.cjs.map} +1 -1
  70. package/dist/chunk-WSQKHXW7.cjs +5726 -0
  71. package/dist/chunk-WSQKHXW7.cjs.map +1 -0
  72. package/dist/{chunk-AA4ZEVCC.cjs → chunk-X4N4WUKB.cjs} +336 -608
  73. package/dist/chunk-X4N4WUKB.cjs.map +1 -0
  74. package/dist/commands/index.cjs +74 -1
  75. package/dist/commands/index.cjs.map +1 -1
  76. package/dist/commands/index.d.cts +38 -353
  77. package/dist/commands/index.d.ts +38 -353
  78. package/dist/commands/index.js +73 -0
  79. package/dist/commands/index.js.map +1 -1
  80. package/dist/comment-store-BEMgVlAA.d.cts +135 -0
  81. package/dist/comment-store-BEMgVlAA.d.ts +135 -0
  82. package/dist/compare/index.cjs +15 -0
  83. package/dist/compare/index.cjs.map +1 -0
  84. package/dist/compare/index.d.cts +58 -0
  85. package/dist/compare/index.d.ts +58 -0
  86. package/dist/compare/index.js +15 -0
  87. package/dist/compare/index.js.map +1 -0
  88. package/dist/controller-BIJiNoVs.d.cts +1842 -0
  89. package/dist/controller-BIJiNoVs.d.ts +1842 -0
  90. package/dist/documents-CHT1U5Vs.d.ts +86 -0
  91. package/dist/documents-Gp7wb0Me.d.cts +86 -0
  92. package/dist/extensions/index.cjs +100 -5
  93. package/dist/extensions/index.cjs.map +1 -1
  94. package/dist/extensions/index.d.cts +16 -4
  95. package/dist/extensions/index.d.ts +16 -4
  96. package/dist/extensions/index.js +100 -5
  97. package/dist/generation/index.cjs +2317 -0
  98. package/dist/generation/index.cjs.map +1 -0
  99. package/dist/generation/index.d.cts +1015 -0
  100. package/dist/generation/index.d.ts +1015 -0
  101. package/dist/generation/index.js +2317 -0
  102. package/dist/generation/index.js.map +1 -0
  103. package/dist/grammar/index.cjs +48 -3
  104. package/dist/grammar/index.cjs.map +1 -1
  105. package/dist/grammar/index.d.cts +322 -100
  106. package/dist/grammar/index.d.ts +322 -100
  107. package/dist/grammar/index.js +54 -9
  108. package/dist/image-editor/index.cjs +4392 -481
  109. package/dist/image-editor/index.cjs.map +1 -1
  110. package/dist/image-editor/index.d.cts +1249 -97
  111. package/dist/image-editor/index.d.ts +1249 -97
  112. package/dist/image-editor/index.js +4281 -370
  113. package/dist/image-editor/index.js.map +1 -1
  114. package/dist/index-7a6bHTLy.d.ts +1754 -0
  115. package/dist/index-Csd5qI8u.d.cts +1754 -0
  116. package/dist/index.cjs +1682 -99
  117. package/dist/index.cjs.map +1 -1
  118. package/dist/index.d.cts +118 -21
  119. package/dist/index.d.ts +118 -21
  120. package/dist/index.js +1630 -47
  121. package/dist/index.js.map +1 -1
  122. package/dist/mention-text-CDnCFd3c.d.cts +53 -0
  123. package/dist/mention-text-CDnCFd3c.d.ts +53 -0
  124. package/dist/migrate-iTlm3a8h.d.cts +112 -0
  125. package/dist/migrate-iTlm3a8h.d.ts +112 -0
  126. package/dist/model-C1zApQM8.d.cts +219 -0
  127. package/dist/model-C1zApQM8.d.ts +219 -0
  128. package/dist/persistence/index.cjs +676 -0
  129. package/dist/persistence/index.cjs.map +1 -0
  130. package/dist/persistence/index.d.cts +230 -0
  131. package/dist/persistence/index.d.ts +230 -0
  132. package/dist/persistence/index.js +676 -0
  133. package/dist/persistence/index.js.map +1 -0
  134. package/dist/{registry-D5-R90p0.d.cts → registry-BDe-vlv3.d.ts} +2 -40
  135. package/dist/{registry-GJI4gToU.d.ts → registry-CEgwXED1.d.cts} +12 -1
  136. package/dist/{registry-GJI4gToU.d.cts → registry-CEgwXED1.d.ts} +12 -1
  137. package/dist/registry-D32LpIc4.d.cts +354 -0
  138. package/dist/{registry-D5-R90p0.d.ts → registry-DnzpgY27.d.cts} +2 -40
  139. package/dist/registry-evKTP4ls.d.ts +354 -0
  140. package/dist/schema-DYDGdYL9.d.cts +41 -0
  141. package/dist/schema-DYDGdYL9.d.ts +41 -0
  142. package/dist/serializers/index.cjs +12 -3
  143. package/dist/serializers/index.cjs.map +1 -1
  144. package/dist/serializers/index.d.cts +40 -12
  145. package/dist/serializers/index.d.ts +40 -12
  146. package/dist/serializers/index.js +12 -3
  147. package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
  148. package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
  149. package/dist/spreadsheet/index.cjs +161 -3
  150. package/dist/spreadsheet/index.cjs.map +1 -1
  151. package/dist/spreadsheet/index.d.cts +611 -20
  152. package/dist/spreadsheet/index.d.ts +611 -20
  153. package/dist/spreadsheet/index.js +168 -10
  154. package/dist/storage-BpFfspHm.d.cts +120 -0
  155. package/dist/storage-BpFfspHm.d.ts +120 -0
  156. package/dist/tokens/index.cjs +115 -0
  157. package/dist/tokens/index.cjs.map +1 -0
  158. package/dist/tokens/index.d.cts +212 -0
  159. package/dist/tokens/index.d.ts +212 -0
  160. package/dist/tokens/index.js +115 -0
  161. package/dist/tokens/index.js.map +1 -0
  162. package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
  163. package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
  164. package/dist/{track-changes-BSF58RK0.d.cts → track-changes-D_VAPELi.d.cts} +110 -144
  165. package/dist/{track-changes-BSF58RK0.d.ts → track-changes-Db_fGEvx.d.ts} +110 -144
  166. package/package.json +21 -1
  167. package/dist/chunk-4SUNJZWB.cjs.map +0 -1
  168. package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
  169. package/dist/chunk-AXV2ZW34.cjs +0 -118
  170. package/dist/chunk-AXV2ZW34.cjs.map +0 -1
  171. package/dist/chunk-B2ZRLN4S.js +0 -118
  172. package/dist/chunk-B2ZRLN4S.js.map +0 -1
  173. package/dist/chunk-GVSMRPHB.cjs.map +0 -1
  174. package/dist/chunk-K3R5NG2D.js +0 -3100
  175. package/dist/chunk-K3R5NG2D.js.map +0 -1
  176. package/dist/chunk-MJZBNWG7.js.map +0 -1
  177. package/dist/chunk-OYXSKQFR.js.map +0 -1
  178. package/dist/chunk-QBJ22YZR.js.map +0 -1
  179. package/dist/chunk-QGHGJ56O.js.map +0 -1
  180. package/dist/chunk-RIKBJLS2.cjs.map +0 -1
  181. package/dist/chunk-XDMXXC4Z.cjs +0 -3100
  182. package/dist/chunk-XDMXXC4Z.cjs.map +0 -1
  183. package/dist/controller-pePCJLT1.d.cts +0 -959
  184. package/dist/controller-pePCJLT1.d.ts +0 -959
  185. package/dist/index-CCr_z9kF.d.cts +0 -899
  186. package/dist/index-Ck6ajwXh.d.ts +0 -899
  187. /package/dist/{chunk-JJDLPEWL.js.map → chunk-QGYDWPRM.js.map} +0 -0
@@ -1,3100 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } function _optionalChainDelete(ops) { const result = _optionalChain(ops); return result == null ? true : result; } var _class; var _class2; var _class3;
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
- var _chunkRIKBJLS2cjs = require('./chunk-RIKBJLS2.cjs');
22
-
23
- // src/spreadsheet/formula/dependency-graph.ts
24
- var columnDependencyKey = (col) => `${col.toUpperCase()}:`;
25
- function extractCellDependencies(node) {
26
- const deps = [];
27
- const visit = (curr) => {
28
- if (curr.type === "CellRef") {
29
- deps.push({ col: curr.col, row: curr.row });
30
- } else if (curr.type === "SpillRef") {
31
- deps.push({ col: curr.col, row: curr.row });
32
- } else if (curr.type === "Range" && curr.endRow >= _chunkRIKBJLS2cjs.WHOLE_COLUMN_END_ROW) {
33
- const startC = _chunkRIKBJLS2cjs.colNameToIndex.call(void 0, curr.startCol);
34
- const endC = _chunkRIKBJLS2cjs.colNameToIndex.call(void 0, curr.endCol);
35
- for (let c = Math.min(startC, endC); c <= Math.max(startC, endC); c++) {
36
- deps.push({ col: _chunkRIKBJLS2cjs.indexToColName.call(void 0, c), row: 0 });
37
- }
38
- } else if (curr.type === "Range") {
39
- const startC = _chunkRIKBJLS2cjs.colNameToIndex.call(void 0, curr.startCol);
40
- const endC = _chunkRIKBJLS2cjs.colNameToIndex.call(void 0, curr.endCol);
41
- const minC = Math.min(startC, endC);
42
- const maxC = Math.max(startC, endC);
43
- const minR = Math.min(curr.startRow, curr.endRow);
44
- const maxR = Math.max(curr.startRow, curr.endRow);
45
- for (let c = minC; c <= maxC; c++) {
46
- for (let r = minR; r <= maxR; r++) {
47
- deps.push({ col: _chunkRIKBJLS2cjs.indexToColName.call(void 0, c), row: r });
48
- }
49
- }
50
- } else if (curr.type === "BinaryOp") {
51
- visit(curr.left);
52
- visit(curr.right);
53
- } else if (curr.type === "UnaryOp") {
54
- visit(curr.argument);
55
- } else if (curr.type === "FunctionCall") {
56
- curr.args.forEach(visit);
57
- }
58
- };
59
- visit(node);
60
- return deps;
61
- }
62
- var DependencyGraph = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this); }
63
- // Map of cellKey (e.g. "B2") -> Set of cellKeys that this cell depends ON
64
- __init() {this.dependencies = /* @__PURE__ */ new Map()}
65
- // Map of cellKey (e.g. "A1") -> Set of cellKeys that depend ON this cell (dependents)
66
- __init2() {this.dependents = /* @__PURE__ */ new Map()}
67
- // Dynamic-array edges, set by the controller after evaluating an anchor:
68
- // spilled cells depend on their anchor, and a blocked (#SPILL!) anchor
69
- // depends on the cells blocking it. Kept apart from formula dependencies,
70
- // so re-entering a formula doesn't drop them.
71
- __init3() {this.spillDependencies = /* @__PURE__ */ new Map()}
72
- __init4() {this.spillDependents = /* @__PURE__ */ new Map()}
73
- // anchor -> the edges it registered (so they can be replaced).
74
- __init5() {this.spillEdges = /* @__PURE__ */ new Map()}
75
- setCellFormula(cellKey, formulaStr) {
76
- const key = cellKey.toUpperCase();
77
- const oldDeps = this.dependencies.get(key) || /* @__PURE__ */ new Set();
78
- for (const dep of oldDeps) {
79
- _optionalChain([this, 'access', _2 => _2.dependents, 'access', _3 => _3.get, 'call', _4 => _4(dep), 'optionalAccess', _5 => _5.delete, 'call', _6 => _6(key)]);
80
- }
81
- this.dependencies.delete(key);
82
- if (!formulaStr || !formulaStr.startsWith("=")) {
83
- return;
84
- }
85
- try {
86
- const ast = _chunkRIKBJLS2cjs.parseFormula.call(void 0, formulaStr);
87
- const deps = extractCellDependencies(ast);
88
- const newDeps = /* @__PURE__ */ new Set();
89
- for (const d of deps) {
90
- const depKey = d.row === 0 ? columnDependencyKey(d.col) : `${d.col}${d.row}`.toUpperCase();
91
- newDeps.add(depKey);
92
- if (!this.dependents.has(depKey)) {
93
- this.dependents.set(depKey, /* @__PURE__ */ new Set());
94
- }
95
- this.dependents.get(depKey).add(key);
96
- }
97
- this.dependencies.set(key, newDeps);
98
- } catch (e) {
99
- }
100
- }
101
- /** Removes every registered formula and dependency. */
102
- clear() {
103
- this.dependencies.clear();
104
- this.dependents.clear();
105
- this.spillDependencies.clear();
106
- this.spillDependents.clear();
107
- this.spillEdges.clear();
108
- }
109
- /**
110
- * Records the dynamic-array edges of the anchor `anchorKey`, replacing its
111
- * previous ones: every key in `spilledKeys` (cells holding its spilled
112
- * values) depends on the anchor, and the anchor depends on every key in
113
- * `blockingKeys` (cells that stop it spilling). Pass empty lists to remove
114
- * the anchor's edges.
115
- */
116
- setSpillEdges(anchorKey, spilledKeys, blockingKeys = []) {
117
- const anchor = anchorKey.toUpperCase();
118
- for (const [from, to] of _nullishCoalesce(this.spillEdges.get(anchor), () => ( []))) {
119
- _optionalChain([this, 'access', _7 => _7.spillDependencies, 'access', _8 => _8.get, 'call', _9 => _9(from), 'optionalAccess', _10 => _10.delete, 'call', _11 => _11(to)]);
120
- _optionalChain([this, 'access', _12 => _12.spillDependents, 'access', _13 => _13.get, 'call', _14 => _14(to), 'optionalAccess', _15 => _15.delete, 'call', _16 => _16(from)]);
121
- }
122
- const edges = [];
123
- for (const key of spilledKeys) edges.push([key.toUpperCase(), anchor]);
124
- for (const key of blockingKeys) edges.push([anchor, key.toUpperCase()]);
125
- for (const [from, to] of edges) {
126
- if (!this.spillDependencies.has(from)) this.spillDependencies.set(from, /* @__PURE__ */ new Set());
127
- this.spillDependencies.get(from).add(to);
128
- if (!this.spillDependents.has(to)) this.spillDependents.set(to, /* @__PURE__ */ new Set());
129
- this.spillDependents.get(to).add(from);
130
- }
131
- if (edges.length > 0) this.spillEdges.set(anchor, edges);
132
- else this.spillEdges.delete(anchor);
133
- }
134
- /** The spilled cells registered for `anchorKey` by `setSpillEdges`. */
135
- getSpilledKeys(anchorKey) {
136
- const anchor = anchorKey.toUpperCase();
137
- return (_nullishCoalesce(this.spillEdges.get(anchor), () => ( []))).filter(([, to]) => to === anchor).map(([from]) => from);
138
- }
139
- /** Formula dependencies plus dynamic-array (spill) dependencies of a cell. */
140
- getDependencies(cellKey) {
141
- const key = cellKey.toUpperCase();
142
- return unionOf(this.dependencies.get(key), this.spillDependencies.get(key));
143
- }
144
- /** Cells depending on `cellKey`, through formulas or spills. */
145
- getDependents(cellKey) {
146
- const key = cellKey.toUpperCase();
147
- return unionOf(this.dependents.get(key), this.spillDependents.get(key));
148
- }
149
- /**
150
- * Detects if there is a cycle containing cellKey.
151
- */
152
- hasCycle(cellKey) {
153
- const key = cellKey.toUpperCase();
154
- const visited = /* @__PURE__ */ new Set();
155
- const recStack = /* @__PURE__ */ new Set();
156
- const dfs = (node) => {
157
- visited.add(node);
158
- recStack.add(node);
159
- const deps = this.getDependencies(node);
160
- for (const neighbor of deps) {
161
- if (!visited.has(neighbor)) {
162
- if (dfs(neighbor)) return true;
163
- } else if (recStack.has(neighbor)) {
164
- return true;
165
- }
166
- }
167
- recStack.delete(node);
168
- return false;
169
- };
170
- return dfs(key);
171
- }
172
- /**
173
- * Returns cells affected by updating cellKey in topological order.
174
- */
175
- getEvaluationOrder(startCellKey) {
176
- const start = startCellKey.toUpperCase();
177
- const visited = /* @__PURE__ */ new Set();
178
- const recStack = /* @__PURE__ */ new Set();
179
- const order = [];
180
- let cycleDetected = false;
181
- const affected = /* @__PURE__ */ new Set();
182
- const collectAffected = (node) => {
183
- affected.add(node);
184
- const directDependents = this.dependents.get(node) || /* @__PURE__ */ new Set();
185
- for (const dep of directDependents) {
186
- if (!affected.has(dep)) {
187
- collectAffected(dep);
188
- }
189
- }
190
- };
191
- collectAffected(start);
192
- const visit = (node) => {
193
- if (recStack.has(node)) {
194
- cycleDetected = true;
195
- return;
196
- }
197
- if (visited.has(node)) return;
198
- recStack.add(node);
199
- visited.add(node);
200
- const deps = this.dependencies.get(node) || /* @__PURE__ */ new Set();
201
- for (const neighbor of deps) {
202
- if (affected.has(neighbor)) {
203
- visit(neighbor);
204
- }
205
- }
206
- recStack.delete(node);
207
- order.push(node);
208
- };
209
- for (const node of affected) {
210
- if (!visited.has(node)) {
211
- visit(node);
212
- }
213
- }
214
- return { order, hasCycle: cycleDetected };
215
- }
216
- /**
217
- * Plans a recalculation over `cellKeys` (and, with `includeDependents`, every
218
- * cell that transitively depends on them). Returns the keys in evaluation
219
- * order (dependencies first) and the set of keys that are part of a cycle,
220
- * which must be marked `#CYCLE!` instead of evaluated. Every member of a
221
- * cycle is reported, not just the cell that closed it.
222
- */
223
- getEvaluationPlan(cellKeys, options = {}) {
224
- const nodes = /* @__PURE__ */ new Set();
225
- const queue = [];
226
- for (const key of cellKeys) {
227
- const upper = key.toUpperCase();
228
- if (!nodes.has(upper)) {
229
- nodes.add(upper);
230
- queue.push(upper);
231
- }
232
- }
233
- if (options.includeDependents) {
234
- while (queue.length > 0) {
235
- const node = queue.pop();
236
- const direct = [
237
- ..._nullishCoalesce(this.dependents.get(node), () => ( [])),
238
- ..._nullishCoalesce(this.spillDependents.get(node), () => ( [])),
239
- ..._nullishCoalesce(this.dependents.get(columnDependencyKey(node.replace(/\d+$/, ""))), () => ( []))
240
- ];
241
- for (const dep of direct) {
242
- if (!nodes.has(dep)) {
243
- nodes.add(dep);
244
- queue.push(dep);
245
- }
246
- }
247
- }
248
- }
249
- const neighbors = (node) => {
250
- const result = [];
251
- for (const dep of this.getDependencies(node)) {
252
- if (dep.endsWith(":")) {
253
- const col = dep.slice(0, -1);
254
- for (const candidate of nodes) {
255
- if (candidate.replace(/\d+$/, "") === col) result.push(candidate);
256
- }
257
- } else if (nodes.has(dep)) {
258
- result.push(dep);
259
- }
260
- }
261
- return result;
262
- };
263
- const order = [];
264
- const cyclic = /* @__PURE__ */ new Set();
265
- const index = /* @__PURE__ */ new Map();
266
- const low = /* @__PURE__ */ new Map();
267
- const stack = [];
268
- const onStack = /* @__PURE__ */ new Set();
269
- let counter = 0;
270
- for (const root of nodes) {
271
- if (index.has(root)) continue;
272
- const work = [];
273
- const enter = (node) => {
274
- index.set(node, counter);
275
- low.set(node, counter);
276
- counter++;
277
- stack.push(node);
278
- onStack.add(node);
279
- work.push({ node, edges: neighbors(node), next: 0 });
280
- };
281
- enter(root);
282
- while (work.length > 0) {
283
- const frame = work[work.length - 1];
284
- if (frame.next < frame.edges.length) {
285
- const target = frame.edges[frame.next++];
286
- if (!index.has(target)) {
287
- enter(target);
288
- } else if (onStack.has(target)) {
289
- low.set(frame.node, Math.min(low.get(frame.node), index.get(target)));
290
- }
291
- continue;
292
- }
293
- work.pop();
294
- if (work.length > 0) {
295
- const parent = work[work.length - 1].node;
296
- low.set(parent, Math.min(low.get(parent), low.get(frame.node)));
297
- }
298
- if (low.get(frame.node) === index.get(frame.node)) {
299
- const component = [];
300
- let member;
301
- do {
302
- member = stack.pop();
303
- onStack.delete(member);
304
- component.push(member);
305
- } while (member !== frame.node);
306
- const isCycle = component.length > 1 || frame.edges.includes(frame.node);
307
- for (const key of component) {
308
- order.push(key);
309
- if (isCycle) cyclic.add(key);
310
- }
311
- }
312
- }
313
- }
314
- return { order, cyclic };
315
- }
316
- }, _class);
317
- function unionOf(a, b) {
318
- if (!b || b.size === 0) return Array.from(_nullishCoalesce(a, () => ( [])));
319
- if (!a || a.size === 0) return Array.from(b);
320
- return Array.from(/* @__PURE__ */ new Set([...a, ...b]));
321
- }
322
-
323
- // src/spreadsheet/formula/references.ts
324
- var CELL_PART = /^(\$?)([A-Za-z]+)(\$?)(\d+)$/;
325
- var COLUMN_PART = /^(\$?)([A-Za-z]+)$/;
326
- var REF_CHAR = /[A-Za-z0-9_$.:]/;
327
- function parsePart(text) {
328
- const cell = CELL_PART.exec(text);
329
- if (cell) {
330
- return {
331
- colAbs: cell[1],
332
- col: _chunkRIKBJLS2cjs.colNameToIndex.call(void 0, cell[2]),
333
- rowAbs: cell[3],
334
- row: parseInt(cell[4], 10) - 1
335
- };
336
- }
337
- const column = COLUMN_PART.exec(text);
338
- if (column) {
339
- return { colAbs: column[1], col: _chunkRIKBJLS2cjs.colNameToIndex.call(void 0, column[2]), rowAbs: "", row: null };
340
- }
341
- return null;
342
- }
343
- function formatPart(part) {
344
- const col = `${part.colAbs}${_chunkRIKBJLS2cjs.indexToColName.call(void 0, part.col)}`;
345
- return part.row === null ? col : `${col}${part.rowAbs}${part.row + 1}`;
346
- }
347
- function shiftCoordinate(value, change) {
348
- if (change.type === "insert") {
349
- return value >= change.index ? value + 1 : value;
350
- }
351
- if (value === change.index) return null;
352
- return value > change.index ? value - 1 : value;
353
- }
354
- function shiftSpan(lo, hi, change) {
355
- if (change.type === "insert") {
356
- return [lo >= change.index ? lo + 1 : lo, hi >= change.index ? hi + 1 : hi];
357
- }
358
- if (lo === change.index && hi === change.index) return null;
359
- return [lo > change.index ? lo - 1 : lo, hi >= change.index ? hi - 1 : hi];
360
- }
361
- function rewriteReference(text, change) {
362
- const [startText, endText] = text.split(":");
363
- const start = parsePart(startText);
364
- if (!start) return text;
365
- const key = change.axis === "row" ? "row" : "col";
366
- if (endText === void 0) {
367
- if (start[key] === null) return text;
368
- const shifted = shiftCoordinate(start[key], change);
369
- if (shifted === null) return "#REF!";
370
- return formatPart({ ...start, [key]: shifted });
371
- }
372
- const end = parsePart(endText);
373
- if (!end) return text;
374
- if (start[key] === null || end[key] === null) return text;
375
- const startValue = start[key];
376
- const endValue = end[key];
377
- const span = shiftSpan(Math.min(startValue, endValue), Math.max(startValue, endValue), change);
378
- if (span === null) return "#REF!";
379
- const [lo, hi] = span;
380
- const newStart = { ...start, [key]: startValue <= endValue ? lo : hi };
381
- const newEnd = { ...end, [key]: startValue <= endValue ? hi : lo };
382
- return `${formatPart(newStart)}:${formatPart(newEnd)}`;
383
- }
384
- function shiftFormulaReferences(formula, change) {
385
- return mapFormulaReferences(formula, (text) => rewriteReference(text, change));
386
- }
387
- function mapFormulaReferences(formula, rewrite) {
388
- if (!formula.startsWith("=")) return formula;
389
- let tokens;
390
- try {
391
- tokens = _chunkRIKBJLS2cjs.tokenizeFormula.call(void 0, formula);
392
- } catch (e2) {
393
- return formula;
394
- }
395
- let result = "";
396
- let cursor = 0;
397
- for (let i = 0; i < tokens.length; i++) {
398
- const token = tokens[i];
399
- if (token.type !== "CELL_REF" && token.type !== "RANGE" && token.type !== "SPILL_REF") continue;
400
- if (_optionalChain([tokens, 'access', _17 => _17[i + 1], 'optionalAccess', _18 => _18.type]) === "LPAREN") continue;
401
- let end = token.position;
402
- while (end < formula.length && REF_CHAR.test(formula[end])) end++;
403
- const original = formula.slice(token.position, end);
404
- if (token.type === "SPILL_REF") {
405
- const rewritten = rewrite(original);
406
- result += formula.slice(cursor, token.position) + (rewritten === "#REF!" ? rewritten : `${rewritten}#`);
407
- cursor = end + 1;
408
- continue;
409
- }
410
- result += formula.slice(cursor, token.position) + rewrite(original);
411
- cursor = end;
412
- }
413
- return result + formula.slice(cursor);
414
- }
415
- function offsetFormulaReferences(formula, rowOffset, colOffset) {
416
- if (rowOffset === 0 && colOffset === 0) return formula;
417
- return mapFormulaReferences(formula, (text) => {
418
- const parts = text.split(":").map(parsePart);
419
- if (parts.some((p) => p === null)) return text;
420
- const moved = [];
421
- for (const part of parts) {
422
- const col = part.colAbs ? part.col : part.col + colOffset;
423
- const row = part.row === null || part.rowAbs ? part.row : part.row + rowOffset;
424
- if (col < 0 || row !== null && row < 0) return "#REF!";
425
- moved.push({ ...part, col, row });
426
- }
427
- return moved.map(formatPart).join(":");
428
- });
429
- }
430
- var MAX_REMAP_RANGE_CELLS = 1e5;
431
- function remapFormulaReferences(formula, moveCell) {
432
- return mapFormulaReferences(formula, (text) => {
433
- const parts = text.split(":").map(parsePart);
434
- if (parts.some((p) => p === null)) return text;
435
- const [start, end] = parts;
436
- if (start.row === null || end && end.row === null) return text;
437
- if (!end) {
438
- const target = moveCell({ row: start.row, col: start.col });
439
- return target ? formatPart({ ...start, row: target.row, col: target.col }) : text;
440
- }
441
- const lo = { row: Math.min(start.row, end.row), col: Math.min(start.col, end.col) };
442
- const hi = { row: Math.max(start.row, end.row), col: Math.max(start.col, end.col) };
443
- const area = (hi.row - lo.row + 1) * (hi.col - lo.col + 1);
444
- if (area > MAX_REMAP_RANGE_CELLS) return text;
445
- let minR = Infinity;
446
- let maxR = -Infinity;
447
- let minC = Infinity;
448
- let maxC = -Infinity;
449
- let movedAny = false;
450
- const targets = /* @__PURE__ */ new Set();
451
- for (let r = lo.row; r <= hi.row; r++) {
452
- for (let c = lo.col; c <= hi.col; c++) {
453
- const moved = moveCell({ row: r, col: c });
454
- if (moved) movedAny = true;
455
- const t = _nullishCoalesce(moved, () => ( { row: r, col: c }));
456
- targets.add(`${t.row}:${t.col}`);
457
- minR = Math.min(minR, t.row);
458
- maxR = Math.max(maxR, t.row);
459
- minC = Math.min(minC, t.col);
460
- maxC = Math.max(maxC, t.col);
461
- }
462
- }
463
- if (!movedAny || targets.size !== area || (maxR - minR + 1) * (maxC - minC + 1) !== area) {
464
- return text;
465
- }
466
- return `${formatPart({ ...start, row: minR, col: minC })}:${formatPart({ ...end, row: maxR, col: maxC })}`;
467
- });
468
- }
469
-
470
- // src/spreadsheet/formula/caret.ts
471
- var EMPTY = { prefix: null, call: null };
472
- function getFormulaCaretContext(input, caret = input.length) {
473
- const leading = input.length - input.trimStart().length;
474
- if (input[leading] !== "=") return EMPTY;
475
- const end = Math.max(0, Math.min(caret, input.length));
476
- if (end <= leading) return EMPTY;
477
- const stack = [];
478
- let quote2 = null;
479
- let inProperty = false;
480
- for (let i = leading + 1; i < end; i++) {
481
- const char = input[i];
482
- if (quote2) {
483
- if (char === "\\") i++;
484
- else if (char === quote2) quote2 = null;
485
- continue;
486
- }
487
- if (inProperty) {
488
- if (char === "]") inProperty = false;
489
- continue;
490
- }
491
- if (char === '"' || char === "'") {
492
- quote2 = char;
493
- } else if (char === "[") {
494
- inProperty = true;
495
- } else if (char === "(") {
496
- const name = /([A-Za-z_][A-Za-z0-9_.]*)\s*$/.exec(input.slice(leading + 1, i));
497
- const isFunction = name && !/^\$?[A-Za-z]+\$?\d+$/.test(name[1]) && !/[:$]$/.test(input.slice(0, i - name[0].length));
498
- stack.push({ name: isFunction ? name[1].toUpperCase() : null, argIndex: 0, openParen: i });
499
- } else if (char === ")") {
500
- stack.pop();
501
- } else if (char === "," && stack.length > 0) {
502
- stack[stack.length - 1].argIndex++;
503
- }
504
- }
505
- if (quote2 || inProperty) {
506
- return { prefix: null, call: innermostCall(stack) };
507
- }
508
- let prefix = null;
509
- const before = input.slice(leading + 1, end);
510
- const word = /[A-Za-z_][A-Za-z0-9_.]*$/.exec(before);
511
- if (word) {
512
- const start = leading + 1 + word.index;
513
- const preceding = _nullishCoalesce(input[start - 1], () => ( ""));
514
- if (!/[:$0-9.]/.test(preceding)) {
515
- prefix = { text: word[0], start, end };
516
- }
517
- }
518
- return { prefix, call: innermostCall(stack) };
519
- }
520
- function innermostCall(stack) {
521
- for (let i = stack.length - 1; i >= 0; i--) {
522
- const frame = stack[i];
523
- if (frame.name) return { name: frame.name, argIndex: frame.argIndex, openParen: frame.openParen };
524
- }
525
- return null;
526
- }
527
- var toSuggestion = (def) => ({
528
- name: def.name,
529
- signature: _chunkRIKBJLS2cjs.getFormulaSignature.call(void 0, def),
530
- description: def.description,
531
- category: def.category
532
- });
533
- function getFormulaHints(input, caret = input.length, options = {}) {
534
- const context = getFormulaCaretContext(input, caret);
535
- let suggestions = [];
536
- if (context.prefix) {
537
- const query = context.prefix.text.toUpperCase();
538
- suggestions = _chunkRIKBJLS2cjs.searchFormulaFunctions.call(void 0, query, { limit: void 0 }).filter((def) => def.name.startsWith(query)).slice(0, _nullishCoalesce(options.limit, () => ( 8))).map(toSuggestion);
539
- const followedByParen = input.slice(context.prefix.end).trimStart().startsWith("(");
540
- if (followedByParen || suggestions.length === 1 && suggestions[0].name === query) suggestions = [];
541
- }
542
- let activeCall = null;
543
- if (context.call) {
544
- const def = _chunkRIKBJLS2cjs.getFormulaFunction.call(void 0, context.call.name);
545
- if (def) {
546
- const specIndex = _chunkRIKBJLS2cjs.getFormulaArgumentSpecIndex.call(void 0, def, context.call.argIndex);
547
- activeCall = {
548
- name: def.name,
549
- signature: _chunkRIKBJLS2cjs.getFormulaSignature.call(void 0, def),
550
- description: def.description,
551
- parts: _chunkRIKBJLS2cjs.getFormulaSignatureParts.call(void 0, def, context.call.argIndex),
552
- argIndex: context.call.argIndex,
553
- argument: specIndex >= 0 ? def.args[specIndex] : null
554
- };
555
- }
556
- }
557
- return { context, suggestions, activeCall };
558
- }
559
- function applyFormulaSuggestion(input, caret, name) {
560
- const context = getFormulaCaretContext(input, caret);
561
- const start = _nullishCoalesce(_optionalChain([context, 'access', _19 => _19.prefix, 'optionalAccess', _20 => _20.start]), () => ( caret));
562
- const end = _nullishCoalesce(_optionalChain([context, 'access', _21 => _21.prefix, 'optionalAccess', _22 => _22.end]), () => ( caret));
563
- const hasParen = input[end] === "(";
564
- const insert = hasParen ? name.toUpperCase() : `${name.toUpperCase()}(`;
565
- const value = input.slice(0, start) + insert + input.slice(end);
566
- return { value, caret: start + insert.length + (hasParen ? 1 : 0) };
567
- }
568
-
569
- // src/spreadsheet/datasource/index.ts
570
- var DataSourceBinder = (_class2 = class {constructor() { _class2.prototype.__init6.call(this); }
571
- __init6() {this.dataSources = /* @__PURE__ */ new Map()}
572
- register(dataSource) {
573
- this.dataSources.set(dataSource.id, dataSource);
574
- }
575
- unregister(dataSourceId) {
576
- this.dataSources.delete(dataSourceId);
577
- }
578
- getDataSource(id) {
579
- return this.dataSources.get(id);
580
- }
581
- /**
582
- * Generates or reconciles spreadsheet rows from a primary data source.
583
- */
584
- syncRows(currentRows, dataSourceId) {
585
- const ds = this.dataSources.get(dataSourceId);
586
- if (!ds) return currentRows;
587
- const records = ds.getRecords();
588
- const existingRecordRowMap = /* @__PURE__ */ new Map();
589
- const freeformRows = [];
590
- for (const row of currentRows) {
591
- if (row.type === "record" && row.recordId) {
592
- existingRecordRowMap.set(row.recordId, row);
593
- } else {
594
- freeformRows.push(row);
595
- }
596
- }
597
- const nextRows = [];
598
- let rowIndex = 0;
599
- for (const record of records) {
600
- const existing = existingRecordRowMap.get(record.id);
601
- nextRows.push({
602
- ..._optionalChain([existing, 'optionalAccess', _23 => _23.hidden]) ? { hidden: true } : {},
603
- id: _optionalChain([existing, 'optionalAccess', _24 => _24.id]) || `row_${record.id}`,
604
- index: rowIndex++,
605
- height: _optionalChain([existing, 'optionalAccess', _25 => _25.height]) || 32,
606
- recordId: record.id,
607
- type: "record"
608
- });
609
- existingRecordRowMap.delete(record.id);
610
- }
611
- for (const freeform of freeformRows) {
612
- nextRows.push({
613
- ...freeform,
614
- index: rowIndex++
615
- });
616
- }
617
- return nextRows;
618
- }
619
- /**
620
- * Resolves a record's bound field value.
621
- */
622
- resolveBoundValue(dataSourceId, recordId, field) {
623
- const ds = this.dataSources.get(dataSourceId);
624
- if (!ds) return void 0;
625
- const records = ds.getRecords();
626
- const record = records.find((r) => r.id === recordId);
627
- return record ? record[field] : void 0;
628
- }
629
- /**
630
- * Dispatches an update to the underlying data source.
631
- */
632
- async commitBoundValue(dataSourceId, recordId, field, value) {
633
- const ds = this.dataSources.get(dataSourceId);
634
- if (!ds) return;
635
- await ds.updateRecord(recordId, field, value);
636
- }
637
- }, _class2);
638
-
639
- // src/spreadsheet/table.ts
640
- function normalizeRange(range) {
641
- return {
642
- startRow: Math.min(range.startRow, range.endRow),
643
- startCol: Math.min(range.startCol, range.endCol),
644
- endRow: Math.max(range.startRow, range.endRow),
645
- endCol: Math.max(range.startCol, range.endCol)
646
- };
647
- }
648
- function rangeContains(range, row, col) {
649
- const r = normalizeRange(range);
650
- return row >= r.startRow && row <= r.endRow && col >= r.startCol && col <= r.endCol;
651
- }
652
- function rangesIntersect(a, b) {
653
- const x = normalizeRange(a);
654
- const y = normalizeRange(b);
655
- return x.startRow <= y.endRow && y.startRow <= x.endRow && x.startCol <= y.endCol && y.startCol <= x.endCol;
656
- }
657
- function rangeContainsRange(outer, inner) {
658
- const o = normalizeRange(outer);
659
- const i = normalizeRange(inner);
660
- return i.startRow >= o.startRow && i.endRow <= o.endRow && i.startCol >= o.startCol && i.endCol <= o.endCol;
661
- }
662
- function isSingleCellRange(range) {
663
- const r = normalizeRange(range);
664
- return r.startRow === r.endRow && r.startCol === r.endCol;
665
- }
666
- var NUMBER_PATTERN = /^[-+]?(\d+\.?\d*|\.\d+)(e[-+]?\d+)?$/i;
667
- var ISO_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
668
- function isBlankValue(value) {
669
- return value === null || value === void 0 || typeof value === "string" && value.trim() === "";
670
- }
671
- function toNumber(value) {
672
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
673
- if (typeof value === "string") {
674
- const trimmed = value.trim();
675
- if (NUMBER_PATTERN.test(trimmed)) return parseFloat(trimmed);
676
- }
677
- return null;
678
- }
679
- var collator = typeof Intl !== "undefined" ? new Intl.Collator(void 0, { numeric: true, sensitivity: "base" }) : null;
680
- function compareText(a, b) {
681
- return collator ? collator.compare(a, b) : a < b ? -1 : a > b ? 1 : 0;
682
- }
683
- function compareCellValues(a, b, direction = "asc") {
684
- const aBlank = isBlankValue(a);
685
- const bBlank = isBlankValue(b);
686
- if (aBlank || bBlank) return aBlank === bBlank ? 0 : aBlank ? 1 : -1;
687
- let result;
688
- if (isPlainText(a) && isPlainText(b) && !(_chunkRIKBJLS2cjs.parseIsoDate.call(void 0, a) && _chunkRIKBJLS2cjs.parseIsoDate.call(void 0, b))) {
689
- result = compareText(a, b);
690
- } else {
691
- result = _chunkRIKBJLS2cjs.sortCompareValues.call(void 0, a, b);
692
- }
693
- if (result === 0) return 0;
694
- return direction === "desc" ? -Math.sign(result) : Math.sign(result);
695
- }
696
- var isPlainText = (value) => typeof value === "string" && !_chunkRIKBJLS2cjs.isFormulaError.call(void 0, value);
697
- function filterValue(value) {
698
- if (isBlankValue(value) || _chunkRIKBJLS2cjs.isFormulaError.call(void 0, value)) return null;
699
- if (typeof value === "number") return Number.isFinite(value) ? { value, kind: "number" } : null;
700
- if (typeof value === "boolean") return { value, kind: "boolean" };
701
- const num = _chunkRIKBJLS2cjs.numericValue.call(void 0, value);
702
- if (num !== null) return { value: num, kind: "number" };
703
- const text = String(value);
704
- return { value: text, kind: _chunkRIKBJLS2cjs.parseIsoDate.call(void 0, text) ? "number" : "text" };
705
- }
706
- function filterOperand(operand) {
707
- if (typeof operand !== "string") return filterValue(operand);
708
- if (operand.trim() === "") return null;
709
- const parsed = _chunkRIKBJLS2cjs.parseCellInput.call(void 0, operand.trim());
710
- if (parsed.type === "number" || parsed.type === "date" || parsed.type === "boolean") return filterValue(parsed.value);
711
- return { value: operand, kind: "text" };
712
- }
713
- function compareForFilter(value, operand) {
714
- const a = filterValue(value);
715
- const b = filterOperand(operand);
716
- if (!a || !b || a.kind !== b.kind) return null;
717
- return _chunkRIKBJLS2cjs.compareValues.call(void 0, a.value, b.value);
718
- }
719
- function looselyEquals(value, operand) {
720
- const c = compareForFilter(value, operand);
721
- if (c !== null) return c === 0;
722
- return String(_nullishCoalesce(value, () => ( ""))).trim().toLowerCase() === String(_nullishCoalesce(operand, () => ( ""))).trim().toLowerCase();
723
- }
724
- function matchesFilterRule(value, rule) {
725
- const text = String(_nullishCoalesce(value, () => ( ""))).toLowerCase();
726
- switch (rule.operator) {
727
- case "equals":
728
- return looselyEquals(value, rule.value);
729
- case "notEquals":
730
- return !looselyEquals(value, rule.value);
731
- case "contains":
732
- return text.includes(String(_nullishCoalesce(rule.value, () => ( ""))).toLowerCase());
733
- case "notContains":
734
- return !text.includes(String(_nullishCoalesce(rule.value, () => ( ""))).toLowerCase());
735
- case "greaterThan": {
736
- const c = compareForFilter(value, rule.value);
737
- return c !== null && c > 0;
738
- }
739
- case "greaterThanOrEqual": {
740
- const c = compareForFilter(value, rule.value);
741
- return c !== null && c >= 0;
742
- }
743
- case "lessThan": {
744
- const c = compareForFilter(value, rule.value);
745
- return c !== null && c < 0;
746
- }
747
- case "lessThanOrEqual": {
748
- const c = compareForFilter(value, rule.value);
749
- return c !== null && c <= 0;
750
- }
751
- case "between": {
752
- const lo = compareForFilter(value, rule.value);
753
- const hi = compareForFilter(value, rule.value2);
754
- return lo !== null && hi !== null && lo >= 0 && hi <= 0;
755
- }
756
- case "isBlank":
757
- return isBlankValue(value);
758
- case "isNotBlank":
759
- return !isBlankValue(value);
760
- case "oneOf":
761
- return (_nullishCoalesce(rule.values, () => ( []))).some((candidate) => String(candidate) === String(_nullishCoalesce(value, () => ( ""))));
762
- default:
763
- return true;
764
- }
765
- }
766
- function aggregateValues(values, aggregate) {
767
- if (aggregate === "count") return values.filter((v) => !isBlankValue(v)).length;
768
- const numbers = [];
769
- for (const v of values) {
770
- const n = toNumber(v);
771
- if (n !== null) numbers.push(n);
772
- }
773
- if (numbers.length === 0) return aggregate === "sum" ? 0 : null;
774
- switch (aggregate) {
775
- case "sum":
776
- return roundFloat(numbers.reduce((s, n) => s + n, 0));
777
- case "average":
778
- return roundFloat(numbers.reduce((s, n) => s + n, 0) / numbers.length);
779
- case "min":
780
- return numbers.reduce((m, n) => n < m ? n : m, numbers[0]);
781
- case "max":
782
- return numbers.reduce((m, n) => n > m ? n : m, numbers[0]);
783
- default:
784
- return null;
785
- }
786
- }
787
- function roundFloat(value) {
788
- return Number.isInteger(value) ? value : parseFloat(value.toPrecision(12));
789
- }
790
- var DAY_MS = 864e5;
791
- var TRAILING_NUMBER = /^(.*?)(\d+)(\D*)$/;
792
- function classifyFillValue(raw) {
793
- if (raw.trim() === "") return { kind: "blank" };
794
- const num = toNumber(raw);
795
- if (num !== null) return { kind: "number", value: num };
796
- const typed = _chunkRIKBJLS2cjs.parseCellInput.call(void 0, raw);
797
- const date = ISO_DATE_PATTERN.exec(typed.type === "date" ? String(typed.value) : raw.trim());
798
- if (date) {
799
- const [year, month, day] = [Number(date[1]), Number(date[2]), Number(date[3])];
800
- const time = Date.UTC(year, month - 1, day);
801
- const check = new Date(time);
802
- if (check.getUTCMonth() === month - 1 && check.getUTCDate() === day) {
803
- return { kind: "date", time, year, month, day };
804
- }
805
- }
806
- const textNumber = TRAILING_NUMBER.exec(raw);
807
- if (textNumber && textNumber[1] !== "") {
808
- return {
809
- kind: "textNumber",
810
- prefix: textNumber[1],
811
- value: parseInt(textNumber[2], 10),
812
- width: textNumber[2].length,
813
- suffix: textNumber[3]
814
- };
815
- }
816
- return { kind: "text", text: raw };
817
- }
818
- var pad = (n, width) => String(Math.abs(n)).padStart(width, "0");
819
- function formatDate(time) {
820
- const d = new Date(time);
821
- return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1, 2)}-${pad(d.getUTCDate(), 2)}`;
822
- }
823
- function constantStep(values) {
824
- const step = values[1] - values[0];
825
- for (let i = 2; i < values.length; i++) {
826
- if (Math.abs(values[i] - values[i - 1] - step) > 1e-9) return null;
827
- }
828
- return step;
829
- }
830
- function linearTrend(values, x) {
831
- const n = values.length;
832
- const meanX = (n - 1) / 2;
833
- const meanY = values.reduce((s, v) => s + v, 0) / n;
834
- let num = 0;
835
- let den = 0;
836
- values.forEach((v, i) => {
837
- num += (i - meanX) * (v - meanY);
838
- den += (i - meanX) ** 2;
839
- });
840
- const slope = den === 0 ? 0 : num / den;
841
- return meanY + slope * (x - meanX);
842
- }
843
- function fillSeries(source, count) {
844
- if (count <= 0) return [];
845
- if (source.length === 0) return Array(count).fill("");
846
- const repeat = () => Array.from({ length: count }, (_, i) => source[i % source.length]);
847
- const parsed = source.map(classifyFillValue);
848
- const kind = parsed[0].kind;
849
- if (!parsed.every((p) => p.kind === kind)) return repeat();
850
- const n = source.length;
851
- if (kind === "number") {
852
- if (n === 1) return repeat();
853
- const values = parsed.map((p) => p.value);
854
- return Array.from({ length: count }, (_, i) => String(roundFloat(linearTrend(values, n + i))));
855
- }
856
- if (kind === "date") {
857
- const dates = parsed;
858
- if (n === 1) {
859
- return Array.from({ length: count }, (_, i) => formatDate(dates[0].time + (i + 1) * DAY_MS));
860
- }
861
- const monthIndex = dates.map((d) => d.year * 12 + (d.month - 1));
862
- const monthStep = constantStep(monthIndex);
863
- if (monthStep !== null && monthStep !== 0 && dates.every((d) => d.day === dates[0].day)) {
864
- const last = monthIndex[n - 1];
865
- return Array.from({ length: count }, (_, i) => {
866
- const m = last + monthStep * (i + 1);
867
- const year = Math.floor(m / 12);
868
- const month = m - year * 12;
869
- const daysInMonth = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
870
- return formatDate(Date.UTC(year, month, Math.min(dates[0].day, daysInMonth)));
871
- });
872
- }
873
- const dayStep = constantStep(dates.map((d) => Math.round(d.time / DAY_MS)));
874
- if (dayStep === null) return repeat();
875
- const lastTime = dates[n - 1].time;
876
- return Array.from({ length: count }, (_, i) => formatDate(lastTime + dayStep * (i + 1) * DAY_MS));
877
- }
878
- if (kind === "textNumber") {
879
- const items = parsed;
880
- const { prefix, suffix } = items[0];
881
- if (!items.every((t) => t.prefix === prefix && t.suffix === suffix)) return repeat();
882
- const step = n === 1 ? 1 : constantStep(items.map((t) => t.value));
883
- if (step === null) return repeat();
884
- const last = items[n - 1];
885
- return Array.from({ length: count }, (_, i) => {
886
- const value = last.value + step * (i + 1);
887
- const digits = value < 0 ? `-${pad(value, last.width)}` : pad(value, last.width);
888
- return `${prefix}${digits}${suffix}`;
889
- });
890
- }
891
- return repeat();
892
- }
893
- function computeFillTarget(source, pointer) {
894
- const s = normalizeRange(source);
895
- const rowOverflow = pointer.row > s.endRow ? pointer.row - s.endRow : pointer.row < s.startRow ? s.startRow - pointer.row : 0;
896
- const colOverflow = pointer.col > s.endCol ? pointer.col - s.endCol : pointer.col < s.startCol ? s.startCol - pointer.col : 0;
897
- if (rowOverflow === 0 && colOverflow === 0) return null;
898
- if (rowOverflow >= colOverflow) {
899
- return pointer.row > s.endRow ? { ...s, endRow: pointer.row } : { ...s, startRow: pointer.row };
900
- }
901
- return pointer.col > s.endCol ? { ...s, endCol: pointer.col } : { ...s, startCol: pointer.col };
902
- }
903
- function getFillDirection(source, target) {
904
- const s = normalizeRange(source);
905
- const t = normalizeRange(target);
906
- const sameCols = s.startCol === t.startCol && s.endCol === t.endCol;
907
- const sameRows = s.startRow === t.startRow && s.endRow === t.endRow;
908
- if (sameCols && t.startRow === s.startRow && t.endRow > s.endRow) return "down";
909
- if (sameCols && t.endRow === s.endRow && t.startRow < s.startRow) return "up";
910
- if (sameRows && t.startCol === s.startCol && t.endCol > s.endCol) return "right";
911
- if (sameRows && t.endCol === s.endCol && t.startCol < s.startCol) return "left";
912
- return null;
913
- }
914
-
915
- // src/spreadsheet/csv.ts
916
- var BOM = "\uFEFF";
917
- function parseCsv(text, options = {}) {
918
- const delimiter = _nullishCoalesce(options.delimiter, () => ( ","));
919
- if (delimiter.length !== 1 || delimiter === '"' || delimiter === "\n" || delimiter === "\r") {
920
- throw new Error(`Invalid CSV delimiter: ${JSON.stringify(delimiter)}`);
921
- }
922
- const input = text.startsWith(BOM) ? text.slice(1) : text;
923
- const records = [];
924
- let record = [];
925
- let field = "";
926
- let i = 0;
927
- let recordStarted = false;
928
- const endField = () => {
929
- record.push(field);
930
- field = "";
931
- };
932
- const endRecord = () => {
933
- endField();
934
- records.push(record);
935
- record = [];
936
- recordStarted = false;
937
- };
938
- let atFieldStart = true;
939
- while (i < input.length) {
940
- const char = input[i];
941
- if (char === '"' && atFieldStart) {
942
- const quoted = readQuoted(input, i, delimiter);
943
- if (quoted.wellFormed || !options.requireWellFormedQuotes) {
944
- recordStarted = true;
945
- atFieldStart = false;
946
- field += quoted.value;
947
- i = quoted.end;
948
- continue;
949
- }
950
- }
951
- if (char === delimiter) {
952
- recordStarted = true;
953
- endField();
954
- atFieldStart = true;
955
- i++;
956
- continue;
957
- }
958
- if (char === "\r" || char === "\n") {
959
- endRecord();
960
- atFieldStart = true;
961
- i += char === "\r" && input[i + 1] === "\n" ? 2 : 1;
962
- continue;
963
- }
964
- recordStarted = true;
965
- atFieldStart = false;
966
- field += char;
967
- i++;
968
- }
969
- if (recordStarted || field !== "" || record.length > 0) endRecord();
970
- return records;
971
- }
972
- function readQuoted(input, start, delimiter) {
973
- let value = "";
974
- let i = start + 1;
975
- while (i < input.length) {
976
- if (input[i] === '"') {
977
- if (input[i + 1] === '"') {
978
- value += '"';
979
- i += 2;
980
- continue;
981
- }
982
- const next = input[i + 1];
983
- const wellFormed = next === void 0 || next === delimiter || next === "\r" || next === "\n";
984
- return { value, end: i + 1, wellFormed };
985
- }
986
- value += input[i];
987
- i++;
988
- }
989
- return { value, end: i, wellFormed: false };
990
- }
991
- function escapeCsvField(value, delimiter = ",", quoteAll = false) {
992
- if (quoteAll || value.includes(delimiter) || /["\r\n]/.test(value)) {
993
- return `"${value.replace(/"/g, '""')}"`;
994
- }
995
- return value;
996
- }
997
- function serializeCsv(rows, options = {}) {
998
- const delimiter = _nullishCoalesce(options.delimiter, () => ( ","));
999
- const lineEnding = _nullishCoalesce(options.lineEnding, () => ( "\r\n"));
1000
- const body = rows.map(
1001
- (row) => row.map((value) => escapeCsvField(value == null ? "" : String(value), delimiter, options.quoteAll)).join(delimiter)
1002
- ).join(lineEnding);
1003
- return options.bom ? BOM + body : body;
1004
- }
1005
- function parseTsv(text) {
1006
- const normalized = text.startsWith(BOM) ? text.slice(1) : text;
1007
- if (!normalized.includes('"')) {
1008
- const body = normalized.replace(/\r\n?/g, "\n").replace(/\n$/, "");
1009
- return body === "" ? [] : body.split("\n").map((line) => line.split(" "));
1010
- }
1011
- return parseCsv(normalized, { delimiter: " ", requireWellFormedQuotes: true });
1012
- }
1013
- function serializeTsv(rows) {
1014
- return rows.map(
1015
- (row) => row.map((value) => {
1016
- const text = value == null ? "" : String(value);
1017
- return /[\t\r\n]/.test(text) || text.startsWith('"') ? `"${text.replace(/"/g, '""')}"` : text;
1018
- }).join(" ")
1019
- ).join("\n");
1020
- }
1021
-
1022
- // src/spreadsheet/clipboard.ts
1023
- var SPREADSHEET_CLIPBOARD_MIME = "application/x.pixerate.spreadsheet+json";
1024
- var displayText = (value) => value === null || value === void 0 ? "" : String(value);
1025
- function escapeHtml(text) {
1026
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1027
- }
1028
- function serializeHtmlTable(values) {
1029
- const body = values.map(
1030
- (row) => `<tr>${row.map((v) => `<td>${escapeHtml(displayText(v)).replace(/\r?\n/g, "<br>")}</td>`).join("")}</tr>`
1031
- ).join("");
1032
- return `<table><tbody>${body}</tbody></table>`;
1033
- }
1034
- var NAMED_ENTITIES = {
1035
- amp: "&",
1036
- lt: "<",
1037
- gt: ">",
1038
- quot: '"',
1039
- apos: "'",
1040
- nbsp: " "
1041
- };
1042
- function decodeEntities(text) {
1043
- return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (match, entity) => {
1044
- if (entity[0] === "#") {
1045
- const code = entity[1] === "x" || entity[1] === "X" ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);
1046
- return Number.isFinite(code) ? String.fromCodePoint(code) : match;
1047
- }
1048
- return _nullishCoalesce(NAMED_ENTITIES[entity.toLowerCase()], () => ( match));
1049
- });
1050
- }
1051
- var LINE_BREAK = "\uE000";
1052
- function cellHtmlToText(html) {
1053
- const text = html.replace(/<!--[\s\S]*?-->/g, "").replace(/<(style|script)[\s\S]*?<\/\1\s*>/gi, "").replace(/<br\s*\/?>/gi, LINE_BREAK).replace(/<\/(p|div)\s*>/gi, LINE_BREAK).replace(/<[^>]*>/g, "").replace(/\s+/g, " ");
1054
- return decodeEntities(text).replace(/ /g, " ").split(LINE_BREAK).map((line) => line.trim()).join("\n").replace(/^\n+|\n+$/g, "");
1055
- }
1056
- function parseHtmlTable(html) {
1057
- const tableMatch = /<table[\s>][\s\S]*?<\/table\s*>/i.exec(html);
1058
- if (!tableMatch) return null;
1059
- const table = tableMatch[0];
1060
- const rows = [];
1061
- const occupied = /* @__PURE__ */ new Map();
1062
- const rowPattern = /<tr[\s>]([\s\S]*?)<\/tr\s*>/gi;
1063
- const cellPattern = /<(td|th)(\s[^>]*)?>([\s\S]*?)<\/\1\s*>/gi;
1064
- let rowMatch;
1065
- let rowIndex = 0;
1066
- while (rowMatch = rowPattern.exec(table)) {
1067
- const row = [];
1068
- const taken = _nullishCoalesce(occupied.get(rowIndex), () => ( /* @__PURE__ */ new Set()));
1069
- let col = 0;
1070
- let cellMatch;
1071
- cellPattern.lastIndex = 0;
1072
- while (cellMatch = cellPattern.exec(rowMatch[1])) {
1073
- while (taken.has(col)) row[col++] = "";
1074
- const attrs = _nullishCoalesce(cellMatch[2], () => ( ""));
1075
- const colspan = Math.max(1, parseInt(_nullishCoalesce(_optionalChain([/colspan\s*=\s*["']?(\d+)/i, 'access', _26 => _26.exec, 'call', _27 => _27(attrs), 'optionalAccess', _28 => _28[1]]), () => ( "1")), 10));
1076
- const rowspan = Math.max(1, parseInt(_nullishCoalesce(_optionalChain([/rowspan\s*=\s*["']?(\d+)/i, 'access', _29 => _29.exec, 'call', _30 => _30(attrs), 'optionalAccess', _31 => _31[1]]), () => ( "1")), 10));
1077
- row[col] = cellHtmlToText(cellMatch[3]);
1078
- for (let c = col; c < col + colspan; c++) {
1079
- if (c > col) row[c] = "";
1080
- for (let r = rowIndex + 1; r < rowIndex + rowspan; r++) {
1081
- if (!occupied.has(r)) occupied.set(r, /* @__PURE__ */ new Set());
1082
- occupied.get(r).add(c);
1083
- }
1084
- }
1085
- col += colspan;
1086
- }
1087
- while (taken.has(col)) row[col++] = "";
1088
- rows.push(Array.from(row, (v) => _nullishCoalesce(v, () => ( ""))));
1089
- rowIndex++;
1090
- }
1091
- return rows.length > 0 ? rows : null;
1092
- }
1093
- function createSpreadsheetClipboardData(cells, origin) {
1094
- const texts = cells.map((row) => row.map((cell) => _nullishCoalesce(cell.text, () => ( displayText(cell.value)))));
1095
- const payload = {
1096
- type: "pixerate-spreadsheet",
1097
- version: 1,
1098
- origin,
1099
- cells: cells.map((row) => row.map(({ raw, value }) => ({ raw, value })))
1100
- };
1101
- return {
1102
- "text/plain": serializeTsv(texts),
1103
- "text/html": serializeHtmlTable(texts),
1104
- [SPREADSHEET_CLIPBOARD_MIME]: JSON.stringify(payload)
1105
- };
1106
- }
1107
- function readFormat(source, format) {
1108
- try {
1109
- if (typeof source.getData === "function") {
1110
- return _nullishCoalesce(source.getData(format), () => ( ""));
1111
- }
1112
- return _nullishCoalesce(source[format], () => ( ""));
1113
- } catch (e3) {
1114
- return "";
1115
- }
1116
- }
1117
- function parsePayload(json) {
1118
- try {
1119
- const payload = JSON.parse(json);
1120
- if (_optionalChain([payload, 'optionalAccess', _32 => _32.type]) !== "pixerate-spreadsheet" || !Array.isArray(payload.cells) || !payload.cells.every((row) => Array.isArray(row)) || typeof _optionalChain([payload, 'access', _33 => _33.origin, 'optionalAccess', _34 => _34.row]) !== "number" || typeof _optionalChain([payload, 'access', _35 => _35.origin, 'optionalAccess', _36 => _36.col]) !== "number") {
1121
- return null;
1122
- }
1123
- return payload;
1124
- } catch (e4) {
1125
- return null;
1126
- }
1127
- }
1128
- function readSpreadsheetClipboard(source) {
1129
- const payload = parsePayload(readFormat(source, SPREADSHEET_CLIPBOARD_MIME));
1130
- if (payload) {
1131
- return {
1132
- format: "internal",
1133
- origin: payload.origin,
1134
- rows: payload.cells.map((row) => row.map((cell) => typeof _optionalChain([cell, 'optionalAccess', _37 => _37.raw]) === "string" ? cell.raw : displayText(_optionalChain([cell, 'optionalAccess', _38 => _38.value]))))
1135
- };
1136
- }
1137
- const html = readFormat(source, "text/html");
1138
- if (html) {
1139
- const rows = parseHtmlTable(html);
1140
- if (rows) return { format: "html", rows };
1141
- }
1142
- const text = readFormat(source, "text/plain");
1143
- if (text) {
1144
- const rows = parseTsv(text);
1145
- if (rows.length > 0) return { format: "text", rows };
1146
- }
1147
- return null;
1148
- }
1149
- function rebaseClipboardRows(rows, origin, target) {
1150
- const rowOffset = target.row - origin.row;
1151
- const colOffset = target.col - origin.col;
1152
- return rows.map(
1153
- (row) => row.map((raw) => raw.startsWith("=") ? offsetFormulaReferences(raw, rowOffset, colOffset) : raw)
1154
- );
1155
- }
1156
-
1157
- // src/spreadsheet/view.ts
1158
- var DEFAULT_ROW_HEIGHT = 32;
1159
- var DEFAULT_COLUMN_WIDTH = 130;
1160
- var clampCount = (value, max) => Math.max(0, Math.min(Math.floor(_nullishCoalesce(value, () => ( 0))), max));
1161
- function getTableSections(doc) {
1162
- const total = doc.rows.length;
1163
- const headerRows = clampCount(_optionalChain([doc, 'access', _39 => _39.table, 'optionalAccess', _40 => _40.headerRowCount]), total);
1164
- const footerRows = clampCount(_optionalChain([doc, 'access', _41 => _41.table, 'optionalAccess', _42 => _42.footerRowCount]), total - headerRows);
1165
- return { headerRows, footerRows };
1166
- }
1167
- function getRowSection(doc, rowIndex) {
1168
- const { headerRows, footerRows } = getTableSections(doc);
1169
- if (rowIndex < headerRows) return "header";
1170
- if (rowIndex >= doc.rows.length - footerRows) return "footer";
1171
- return "body";
1172
- }
1173
- function rowPassesFilters(doc, rowIndex, getValue) {
1174
- const filter = _optionalChain([doc, 'access', _43 => _43.table, 'optionalAccess', _44 => _44.filter]);
1175
- if (!_optionalChain([filter, 'optionalAccess', _45 => _45.enabled]) || filter.rules.length === 0) return true;
1176
- if (getRowSection(doc, rowIndex) !== "body") return true;
1177
- return filter.rules.every((rule) => {
1178
- const colIndex = doc.columns.findIndex((c) => c.id === rule.colId);
1179
- if (colIndex === -1) return true;
1180
- return matchesFilterRule(getValue(rowIndex, colIndex), rule);
1181
- });
1182
- }
1183
- function computeRowGroups(doc, getValue) {
1184
- const grouping = _optionalChain([doc, 'access', _46 => _46.table, 'optionalAccess', _47 => _47.grouping]);
1185
- if (!grouping) return [];
1186
- const groupCol = doc.columns.findIndex((c) => c.id === grouping.colId);
1187
- if (groupCol === -1) return [];
1188
- const { headerRows, footerRows } = getTableSections(doc);
1189
- const byKey = /* @__PURE__ */ new Map();
1190
- for (let r = headerRows; r < doc.rows.length - footerRows; r++) {
1191
- if (doc.rows[r].hidden || !rowPassesFilters(doc, r, getValue)) continue;
1192
- const value = getValue(r, groupCol);
1193
- const key = isBlankValue(value) ? "" : String(value).trim();
1194
- if (!byKey.has(key)) byKey.set(key, []);
1195
- byKey.get(key).push(r);
1196
- }
1197
- const collapsed = new Set(grouping.collapsed);
1198
- return Array.from(byKey.keys()).sort((a, b) => compareCellValues(a, b)).map((key) => {
1199
- const rowIndices = byKey.get(key);
1200
- const summaries = {};
1201
- for (const summary of grouping.summaries) {
1202
- const colIndex = doc.columns.findIndex((c) => c.id === summary.colId);
1203
- if (colIndex === -1) continue;
1204
- summaries[summary.colId] = aggregateValues(
1205
- rowIndices.map((r) => getValue(r, colIndex)),
1206
- summary.aggregate
1207
- );
1208
- }
1209
- return { key, label: key === "" ? "(blank)" : key, rowIndices, collapsed: collapsed.has(key), summaries };
1210
- });
1211
- }
1212
- function buildSpreadsheetView(doc, getValue) {
1213
- const { headerRows, footerRows } = getTableSections(doc);
1214
- const frozenRows = clampCount(_optionalChain([doc, 'access', _48 => _48.table, 'optionalAccess', _49 => _49.frozenRowCount]), doc.rows.length);
1215
- const frozenCols = clampCount(_optionalChain([doc, 'access', _50 => _50.table, 'optionalAccess', _51 => _51.frozenColumnCount]), doc.columns.length);
1216
- const headerCols = clampCount(_optionalChain([doc, 'access', _52 => _52.table, 'optionalAccess', _53 => _53.headerColumnCount]), doc.columns.length);
1217
- const columns = [];
1218
- let left = 0;
1219
- let hiddenColumnCount = 0;
1220
- doc.columns.forEach((column, index) => {
1221
- if (column.hidden) {
1222
- hiddenColumnCount++;
1223
- return;
1224
- }
1225
- const frozen = index < frozenCols;
1226
- columns.push({ column, index, header: index < headerCols, frozen, stickyLeft: frozen ? left : 0 });
1227
- if (frozen) left += column.width || DEFAULT_COLUMN_WIDTH;
1228
- });
1229
- const rows = [];
1230
- let hiddenRowCount = 0;
1231
- let filteredOutCount = 0;
1232
- const pushRow = (index, section, groupKey) => {
1233
- const row = doc.rows[index];
1234
- rows.push({ kind: "row", row, index, section, frozen: false, stickyTop: 0, groupKey });
1235
- };
1236
- for (let r = 0; r < headerRows; r++) {
1237
- if (doc.rows[r].hidden) hiddenRowCount++;
1238
- else pushRow(r, "header");
1239
- }
1240
- const bodyEnd = doc.rows.length - footerRows;
1241
- const groups = computeRowGroups(doc, getValue);
1242
- for (let r = headerRows; r < bodyEnd; r++) {
1243
- if (doc.rows[r].hidden) hiddenRowCount++;
1244
- else if (!rowPassesFilters(doc, r, getValue)) filteredOutCount++;
1245
- }
1246
- const grouping = _optionalChain([doc, 'access', _54 => _54.table, 'optionalAccess', _55 => _55.grouping]);
1247
- if (grouping && doc.columns.some((c) => c.id === grouping.colId)) {
1248
- for (const group of groups) {
1249
- rows.push({ kind: "group", group });
1250
- if (!group.collapsed) group.rowIndices.forEach((r) => pushRow(r, "body", group.key));
1251
- }
1252
- } else {
1253
- for (let r = headerRows; r < bodyEnd; r++) {
1254
- if (!doc.rows[r].hidden && rowPassesFilters(doc, r, getValue)) pushRow(r, "body");
1255
- }
1256
- }
1257
- for (let r = bodyEnd; r < doc.rows.length; r++) {
1258
- if (doc.rows[r].hidden) hiddenRowCount++;
1259
- else pushRow(r, "footer");
1260
- }
1261
- let top = 0;
1262
- for (const viewRow of rows) {
1263
- if (viewRow.kind !== "row" || viewRow.index >= frozenRows) break;
1264
- viewRow.frozen = true;
1265
- viewRow.stickyTop = top;
1266
- top += viewRow.row.height || DEFAULT_ROW_HEIGHT;
1267
- }
1268
- const rowOrder = rows.flatMap((r) => r.kind === "row" ? [r.index] : []);
1269
- const columnOrder = columns.map((c) => c.index);
1270
- const rowPos = new Map(rowOrder.map((index, pos) => [index, pos]));
1271
- const colPos = new Map(columnOrder.map((index, pos) => [index, pos]));
1272
- const spans = /* @__PURE__ */ new Map();
1273
- const contiguousRun = (indices, positions) => {
1274
- const visible = indices.filter((i) => positions.has(i)).sort((a, b) => positions.get(a) - positions.get(b));
1275
- if (visible.length === 0) return null;
1276
- let run = 1;
1277
- while (run < visible.length && positions.get(visible[run]) === positions.get(visible[0]) + run) run++;
1278
- return { first: visible[0], run };
1279
- };
1280
- for (const raw of _nullishCoalesce(_optionalChain([doc, 'access', _56 => _56.table, 'optionalAccess', _57 => _57.merges]), () => ( []))) {
1281
- const merge = normalizeRange(raw);
1282
- const rowIdx = [];
1283
- const colIdx = [];
1284
- for (let r = merge.startRow; r <= merge.endRow; r++) rowIdx.push(r);
1285
- for (let c = merge.startCol; c <= merge.endCol; c++) colIdx.push(c);
1286
- const rowRun = contiguousRun(rowIdx, rowPos);
1287
- const colRun = contiguousRun(colIdx, colPos);
1288
- for (const r of rowIdx) for (const c of colIdx) spans.set(`${r}:${c}`, null);
1289
- if (rowRun && colRun) {
1290
- spans.set(`${rowRun.first}:${colRun.first}`, {
1291
- rowSpan: rowRun.run,
1292
- colSpan: colRun.run,
1293
- source: { row: merge.startRow, col: merge.startCol },
1294
- merge
1295
- });
1296
- }
1297
- }
1298
- return {
1299
- rows,
1300
- columns,
1301
- groups,
1302
- rowOrder,
1303
- columnOrder,
1304
- filteredOutCount,
1305
- hiddenRowCount,
1306
- hiddenColumnCount,
1307
- getCellSpan(row, col) {
1308
- const span = spans.get(`${row}:${col}`);
1309
- if (span === void 0) return { rowSpan: 1, colSpan: 1, source: { row, col } };
1310
- return span;
1311
- }
1312
- };
1313
- }
1314
-
1315
- // src/spreadsheet/controller.ts
1316
- var nextIdSequence = 0;
1317
- var DEFAULT_MAX_HISTORY_BYTES = 64 * 1024 * 1024;
1318
- var MAX_SPILL_PASSES = 25;
1319
- var spillCellId = (rowId, colId) => `${rowId}\0${colId}`;
1320
- var SpreadsheetController = (_class3 = class {
1321
-
1322
- __init7() {this.activeCell = null}
1323
- __init8() {this.selectedRange = null}
1324
- __init9() {this.editingCell = null}
1325
- __init10() {this.draftValue = ""}
1326
- __init11() {this.binder = new DataSourceBinder()}
1327
- __init12() {this.depGraph = new DependencyGraph()}
1328
-
1329
-
1330
-
1331
-
1332
-
1333
- // History stack for undo/redo
1334
- __init13() {this.undoStack = []}
1335
- __init14() {this.redoStack = []}
1336
- __init15() {this.isApplyingHistory = false}
1337
-
1338
- // Set while importing TSV: history and recalculation are done once at the end.
1339
- __init16() {this.batching = false}
1340
- __init17() {this.pendingCommits = []}
1341
- // Row being evaluated, so [property] references resolve per row.
1342
- __init18() {this.evaluatingRowIndex = null}
1343
- // Cell being evaluated, for implicit intersection (`=@A:A`).
1344
- __init19() {this.evaluatingCell = null}
1345
- // Dynamic arrays. Spilled values are derived state: they live here, not in
1346
- // the document, and every recalculation rebuilds them.
1347
- __init20() {this.spills = /* @__PURE__ */ new Map()}
1348
- // anchor key -> spill
1349
- __init21() {this.spilledBy = /* @__PURE__ */ new Map()}
1350
- // spilled cell key -> anchor key
1351
- __init22() {this.spillCells = /* @__PURE__ */ new Map()}
1352
- // spillCellId -> displayed cell
1353
- // Cells whose spill ownership changed during the current pass.
1354
- __init23() {this.spillChanges = null}
1355
- constructor(options = {}) {;_class3.prototype.__init7.call(this);_class3.prototype.__init8.call(this);_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);_class3.prototype.__init11.call(this);_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);_class3.prototype.__init15.call(this);_class3.prototype.__init16.call(this);_class3.prototype.__init17.call(this);_class3.prototype.__init18.call(this);_class3.prototype.__init19.call(this);_class3.prototype.__init20.call(this);_class3.prototype.__init21.call(this);_class3.prototype.__init22.call(this);_class3.prototype.__init23.call(this);_class3.prototype.__init24.call(this);
1356
- const defaultCols = [
1357
- { id: "col_a", key: "A", title: "A", width: 120, type: "freeform" },
1358
- { id: "col_b", key: "B", title: "B", width: 120, type: "freeform" },
1359
- { id: "col_c", key: "C", title: "C", width: 120, type: "freeform" },
1360
- { id: "col_d", key: "D", title: "D", width: 120, type: "freeform" }
1361
- ];
1362
- const defaultRows = options.primaryDataSourceId ? [] : Array.from({ length: 10 }, (_, i) => ({
1363
- id: `row_${i + 1}`,
1364
- index: i,
1365
- height: 32,
1366
- type: "freeform"
1367
- }));
1368
- this.document = {
1369
- id: _optionalChain([options, 'access', _58 => _58.document, 'optionalAccess', _59 => _59.id]) || `sheet_${Date.now()}`,
1370
- name: _optionalChain([options, 'access', _60 => _60.document, 'optionalAccess', _61 => _61.name]) || "Sheet 1",
1371
- columns: _optionalChain([options, 'access', _62 => _62.document, 'optionalAccess', _63 => _63.columns]) || defaultCols,
1372
- rows: _optionalChain([options, 'access', _64 => _64.document, 'optionalAccess', _65 => _65.rows]) || defaultRows,
1373
- cells: _optionalChain([options, 'access', _66 => _66.document, 'optionalAccess', _67 => _67.cells]) || {},
1374
- createdAt: _optionalChain([options, 'access', _68 => _68.document, 'optionalAccess', _69 => _69.createdAt]) || Date.now(),
1375
- updatedAt: _optionalChain([options, 'access', _70 => _70.document, 'optionalAccess', _71 => _71.updatedAt]) || Date.now(),
1376
- ..._optionalChain([options, 'access', _72 => _72.document, 'optionalAccess', _73 => _73.table]) ? { table: options.document.table } : {}
1377
- };
1378
- this.primaryDataSourceId = options.primaryDataSourceId;
1379
- this.maxHistoryBytes = Math.max(0, _nullishCoalesce(options.maxHistoryBytes, () => ( DEFAULT_MAX_HISTORY_BYTES)));
1380
- this.onDocumentChange = options.onDocumentChange;
1381
- this.onSelectionChange = options.onSelectionChange;
1382
- this.onCellCommit = options.onCellCommit;
1383
- if (options.dataSources) {
1384
- options.dataSources.forEach((ds) => this.binder.register(ds));
1385
- }
1386
- this.evaluator = new (0, _chunkRIKBJLS2cjs.FormulaEvaluator)({
1387
- resolveCell: (colStr, rowNum) => this.resolveCellByCoord(colStr, rowNum),
1388
- resolveRange: (sCol, sRow, eCol, eRow) => this.resolveRangeByCoords(sCol, sRow, eCol, eRow),
1389
- resolveProperty: (prop) => this.resolveCurrentRowProperty(prop),
1390
- resolveSpill: (colStr, rowNum) => this.resolveSpillByCoord(colStr, rowNum),
1391
- getFormulaCell: () => this.evaluatingCell
1392
- });
1393
- this.syncWithDataSource();
1394
- this.rederiveTypedValues();
1395
- this.recalculateAll();
1396
- }
1397
- loadDocument(document) {
1398
- this.document = JSON.parse(JSON.stringify(document));
1399
- this.activeCell = null;
1400
- this.selectedRange = null;
1401
- this.editingCell = null;
1402
- this.draftValue = "";
1403
- this.undoStack = [];
1404
- this.redoStack = [];
1405
- this.syncWithDataSource();
1406
- this.rederiveTypedValues();
1407
- this.recalculateAll();
1408
- this.notifyChange();
1409
- }
1410
- /**
1411
- * Re-parses the value of every stored typed (non-formula) cell from its
1412
- * `raw` input, so documents saved before typed input was parsed (whose
1413
- * values are text such as `"120"`) compare and sort as numbers. Cells of
1414
- * bound columns in record rows keep their value: it belongs to the data
1415
- * source.
1416
- */
1417
- rederiveTypedValues() {
1418
- for (const row of this.document.rows) {
1419
- const cells = this.document.cells[row.id];
1420
- if (!cells) continue;
1421
- for (const col of this.document.columns) {
1422
- const cell = cells[col.id];
1423
- if (!cell || typeof cell.raw !== "string" || cell.raw.startsWith("=")) continue;
1424
- if (col.type === "bound" && col.binding && row.recordId) continue;
1425
- const typed = this.typedCell(cell.raw, cell.format);
1426
- cell.value = typed.value;
1427
- if (typed.numberFormat) cell.numberFormat = typed.numberFormat;
1428
- else delete cell.numberFormat;
1429
- if (typed.format) cell.format = typed.format;
1430
- }
1431
- }
1432
- }
1433
- /**
1434
- * Value of typed (non-formula) input: a number, boolean, ISO date, error
1435
- * or text (see `parseCellInput`), its automatic number format, and the
1436
- * `CellFormat` to use: the automatic one (currency, percentage, date,
1437
- * number) when `format` is unset or `text` (the default of new columns).
1438
- */
1439
- typedCell(raw, format) {
1440
- const parsed = _chunkRIKBJLS2cjs.parseCellInput.call(void 0, raw);
1441
- return {
1442
- value: parsed.value,
1443
- ...parsed.numberFormat ? { numberFormat: parsed.numberFormat } : {},
1444
- format: parsed.cellFormat && (!format || format === "text") ? parsed.cellFormat : format
1445
- };
1446
- }
1447
- /**
1448
- * Typed input for a bound field, coerced by the field's declared type:
1449
- * number, boolean and date fields get a number, boolean or ISO date when
1450
- * the input parses as one; string and enum fields (and unknown fields)
1451
- * keep the text exactly as typed.
1452
- */
1453
- boundInputValue(dataSourceId, field, raw) {
1454
- if (raw.startsWith("=")) return raw;
1455
- const type = _optionalChain([this, 'access', _74 => _74.binder, 'access', _75 => _75.getDataSource, 'call', _76 => _76(dataSourceId), 'optionalAccess', _77 => _77.getFields, 'call', _78 => _78(), 'access', _79 => _79.find, 'call', _80 => _80((f) => f.key === field), 'optionalAccess', _81 => _81.type]);
1456
- if (type !== "number" && type !== "boolean" && type !== "date") return raw;
1457
- const parsed = _chunkRIKBJLS2cjs.parseCellInput.call(void 0, raw);
1458
- if (type === "number" && parsed.type === "number") return parsed.value;
1459
- if (type === "boolean" && parsed.type === "boolean") return parsed.value;
1460
- if (type === "date" && parsed.type === "date") return parsed.value;
1461
- return raw;
1462
- }
1463
- registerDataSource(ds) {
1464
- this.binder.register(ds);
1465
- this.syncWithDataSource();
1466
- this.recalculateAll();
1467
- }
1468
- setPrimaryDataSource(dataSourceId) {
1469
- this.primaryDataSourceId = dataSourceId;
1470
- this.syncWithDataSource();
1471
- this.recalculateAll();
1472
- }
1473
- syncWithDataSource() {
1474
- if (!this.primaryDataSourceId) return;
1475
- this.document.rows = this.binder.syncRows(this.document.rows, this.primaryDataSourceId);
1476
- this.recomputeRowIndices();
1477
- }
1478
- recomputeRowIndices() {
1479
- this.document.rows.forEach((r, idx) => {
1480
- r.index = idx;
1481
- });
1482
- this.document.columns.forEach((c, idx) => {
1483
- c.key = _chunkRIKBJLS2cjs.indexToColName.call(void 0, idx);
1484
- });
1485
- }
1486
- getColumn(colIdOrKey) {
1487
- return this.document.columns.find(
1488
- (c) => c.id === colIdOrKey || c.key.toUpperCase() === colIdOrKey.toUpperCase()
1489
- );
1490
- }
1491
- getRow(rowIdOrIndex) {
1492
- if (typeof rowIdOrIndex === "number") {
1493
- return this.document.rows[rowIdOrIndex];
1494
- }
1495
- return this.document.rows.find((r) => r.id === rowIdOrIndex);
1496
- }
1497
- getCell(rowId, colId) {
1498
- const existing = _optionalChain([this, 'access', _82 => _82.document, 'access', _83 => _83.cells, 'access', _84 => _84[rowId], 'optionalAccess', _85 => _85[colId]]);
1499
- if (existing && existing.raw !== "") return existing;
1500
- if (this.spillCells.size > 0) {
1501
- const spilled = this.spillCells.get(spillCellId(rowId, colId));
1502
- if (spilled) return spilled;
1503
- }
1504
- if (existing) return existing;
1505
- const col = this.document.columns.find((c) => c.id === colId);
1506
- const row = this.document.rows.find((r) => r.id === rowId);
1507
- return this.getDerivedCell(row, col);
1508
- }
1509
- /** Value of a cell with no stored data: bound record value, column formula, or blank. */
1510
- getDerivedCell(row, col) {
1511
- if (col && row && col.type === "bound" && col.binding && row.recordId) {
1512
- const boundVal = this.binder.resolveBoundValue(
1513
- col.binding.dataSource,
1514
- row.recordId,
1515
- col.binding.field
1516
- );
1517
- return {
1518
- raw: boundVal !== void 0 ? String(boundVal) : "",
1519
- value: _nullishCoalesce(boundVal, () => ( "")),
1520
- format: col.format
1521
- };
1522
- }
1523
- if (col && col.type === "formula" && col.formula) {
1524
- return {
1525
- raw: col.formula,
1526
- value: "",
1527
- format: col.format
1528
- };
1529
- }
1530
- return { raw: "", value: "", format: _optionalChain([col, 'optionalAccess', _86 => _86.format]) };
1531
- }
1532
- setCellValue(rowId, colId, raw) {
1533
- this.recordSnapshot();
1534
- if (!this.document.cells[rowId]) {
1535
- this.document.cells[rowId] = {};
1536
- }
1537
- const col = this.document.columns.find((c) => c.id === colId);
1538
- const row = this.document.rows.find((r) => r.id === rowId);
1539
- const isFormula = raw.startsWith("=");
1540
- let cell;
1541
- if (_optionalChain([col, 'optionalAccess', _87 => _87.type]) === "bound" && col.binding && _optionalChain([row, 'optionalAccess', _88 => _88.recordId])) {
1542
- const value = this.boundInputValue(col.binding.dataSource, col.binding.field, raw);
1543
- this.binder.commitBoundValue(col.binding.dataSource, row.recordId, col.binding.field, value);
1544
- cell = { raw, value, format: col.format };
1545
- } else if (isFormula) {
1546
- cell = { raw, value: raw, format: _optionalChain([col, 'optionalAccess', _89 => _89.format]) };
1547
- } else {
1548
- cell = { raw, ...this.typedCell(raw, _optionalChain([col, 'optionalAccess', _90 => _90.format])) };
1549
- }
1550
- const cellKey = this.getCellKey(rowId, colId);
1551
- this.depGraph.setCellFormula(cellKey, isFormula ? raw : null);
1552
- this.document.cells[rowId][colId] = cell;
1553
- this.document.updatedAt = Date.now();
1554
- if (this.batching) {
1555
- this.pendingCommits.push([rowId, colId, raw]);
1556
- return;
1557
- }
1558
- this.recalculateCell(rowId, colId);
1559
- this.notifyChange();
1560
- const evaluatedVal = _optionalChain([this, 'access', _91 => _91.document, 'access', _92 => _92.cells, 'access', _93 => _93[rowId], 'access', _94 => _94[colId], 'optionalAccess', _95 => _95.value]);
1561
- _optionalChain([this, 'access', _96 => _96.onCellCommit, 'optionalCall', _97 => _97(rowId, colId, raw, evaluatedVal)]);
1562
- }
1563
- getCellKey(rowId, colId) {
1564
- const row = this.document.rows.find((r) => r.id === rowId);
1565
- const col = this.document.columns.find((c) => c.id === colId);
1566
- if (!row || !col) return `${colId}_${rowId}`;
1567
- return `${col.key}${row.index + 1}`.toUpperCase();
1568
- }
1569
- /**
1570
- * Recalculates a cell and everything that depends on it, in dependency
1571
- * order. Every cell in a reference cycle is marked `#CYCLE!`.
1572
- */
1573
- recalculateCell(rowId, colId) {
1574
- const cellKey = this.getCellKey(rowId, colId);
1575
- const anchor = this.spilledBy.get(cellKey);
1576
- this.recalculateKeys(anchor ? [cellKey, anchor] : [cellKey], true);
1577
- }
1578
- /**
1579
- * Recalculates `keys` (and, with `includeDependents`, their dependents) in
1580
- * dependency order. Spill ranges are only known after evaluating their
1581
- * anchors, so when a pass changes which cells hold spilled values (and so
1582
- * the dependency graph), the dependents of those cells are recalculated in
1583
- * another pass, until nothing changes.
1584
- */
1585
- recalculateKeys(keys, includeDependents) {
1586
- let pending = Array.from(keys);
1587
- let withDependents = includeDependents;
1588
- for (let pass = 0; pass < MAX_SPILL_PASSES && pending.length > 0; pass++) {
1589
- const changed = /* @__PURE__ */ new Set();
1590
- this.spillChanges = changed;
1591
- try {
1592
- this.runEvaluationPlan(this.depGraph.getEvaluationPlan(pending, { includeDependents: withDependents }));
1593
- } finally {
1594
- this.spillChanges = null;
1595
- }
1596
- pending = Array.from(changed);
1597
- withDependents = true;
1598
- }
1599
- }
1600
- /**
1601
- * Rebuilds the dependency graph from scratch and recalculates every formula
1602
- * in dependency order.
1603
- */
1604
- recalculateAll() {
1605
- this.depGraph.clear();
1606
- this.spills.clear();
1607
- this.spilledBy.clear();
1608
- this.spillCells.clear();
1609
- const formulaKeys = [];
1610
- for (const row of this.document.rows) {
1611
- for (const col of this.document.columns) {
1612
- const cell = _nullishCoalesce(_optionalChain([this, 'access', _98 => _98.document, 'access', _99 => _99.cells, 'access', _100 => _100[row.id], 'optionalAccess', _101 => _101[col.id]]), () => ( this.getDerivedCell(row, col)));
1613
- if (cell.raw.startsWith("=")) {
1614
- const cellKey = `${col.key}${row.index + 1}`.toUpperCase();
1615
- this.depGraph.setCellFormula(cellKey, cell.raw);
1616
- formulaKeys.push(cellKey);
1617
- }
1618
- }
1619
- }
1620
- this.recalculateKeys(formulaKeys, false);
1621
- }
1622
- runEvaluationPlan(plan) {
1623
- for (const key of plan.order) {
1624
- if (plan.cyclic.has(key)) {
1625
- this.markCycle(key);
1626
- } else {
1627
- this.evalSingleCellByKey(key);
1628
- }
1629
- }
1630
- }
1631
- locateCellKey(cellKey) {
1632
- const match = cellKey.match(/^([A-Za-z]+)(\d+)$/);
1633
- if (!match) return null;
1634
- const col = this.document.columns.find((c) => c.key.toUpperCase() === match[1].toUpperCase());
1635
- const row = this.document.rows[parseInt(match[2], 10) - 1];
1636
- return col && row ? { row, col } : null;
1637
- }
1638
- markCycle(cellKey) {
1639
- const located = this.locateCellKey(cellKey);
1640
- if (!located) return;
1641
- const { row, col } = located;
1642
- const cell = this.getCell(row.id, col.id);
1643
- if (!cell.raw.startsWith("=")) return;
1644
- if (!this.document.cells[row.id]) this.document.cells[row.id] = {};
1645
- const deps = this.depGraph.getDependencies(cellKey);
1646
- const selfReference = deps.includes(cellKey) || deps.includes(`${col.key.toUpperCase()}:`);
1647
- const spilledKeys = this.depGraph.getSpilledKeys(cellKey);
1648
- const ownSpill = deps.find((dep) => spilledKeys.includes(dep));
1649
- const errorMessage = selfReference ? "This formula can't reference its own cell." : ownSpill ? `This formula refers to ${ownSpill}, which holds its own spilled results (a circular reference).` : "This formula is part of a circular reference: it depends on its own result through other cells.";
1650
- this.clearSpill(cellKey.toUpperCase(), true);
1651
- this.document.cells[row.id][col.id] = { ...cell, value: "#CYCLE!", error: "#CYCLE!", errorMessage };
1652
- }
1653
- evalSingleCellByKey(cellKey) {
1654
- const match = cellKey.match(/^([A-Za-z]+)(\d+)$/);
1655
- if (!match) return;
1656
- const colName = match[1].toUpperCase();
1657
- const rowNum = parseInt(match[2], 10);
1658
- const col = this.document.columns.find((c) => c.key.toUpperCase() === colName);
1659
- const row = this.document.rows[rowNum - 1];
1660
- if (!col || !row) return;
1661
- const cell = this.getCell(row.id, col.id);
1662
- if (!cell.raw.startsWith("=")) {
1663
- if (cell.error === "#CYCLE!") {
1664
- cell.error = void 0;
1665
- cell.errorMessage = void 0;
1666
- }
1667
- this.clearSpill(`${colName}${rowNum}`);
1668
- return;
1669
- }
1670
- try {
1671
- this.evaluatingRowIndex = rowNum - 1;
1672
- this.evaluatingCell = { col: colName, row: rowNum };
1673
- const result = this.evaluator.evaluateWithDiagnostics(cell.raw);
1674
- if (!this.document.cells[row.id]) {
1675
- this.document.cells[row.id] = {};
1676
- }
1677
- let { value, error, message } = result;
1678
- if (result.array) {
1679
- const blocked = this.placeSpill(`${colName}${rowNum}`, rowNum - 1, this.document.columns.indexOf(col), result.array);
1680
- if (blocked) {
1681
- value = "#SPILL!";
1682
- error = "#SPILL!";
1683
- message = blocked;
1684
- }
1685
- } else {
1686
- this.clearSpill(`${colName}${rowNum}`);
1687
- }
1688
- this.document.cells[row.id][col.id] = {
1689
- ...cell,
1690
- value,
1691
- // `#ERROR!` (malformed formula, wrong argument count) keeps its
1692
- // message in `error`, as before; other errors store their code.
1693
- error: error === "#ERROR!" ? message : error,
1694
- errorMessage: message
1695
- };
1696
- } finally {
1697
- this.evaluatingRowIndex = null;
1698
- this.evaluatingCell = null;
1699
- }
1700
- }
1701
- // --- Dynamic arrays (spilling) ---
1702
- /**
1703
- * Spills `array` from the anchor at (row, col) into the cells below and to
1704
- * the right. Returns why it can't (the anchor then shows `#SPILL!`), or
1705
- * null once placed. The grid never grows to fit an array, as in Excel.
1706
- */
1707
- placeSpill(anchor, row, col, array) {
1708
- const { rows, cols } = array;
1709
- const area = { startRow: row, startCol: col, endRow: row + rows - 1, endCol: col + cols - 1 };
1710
- const label = `${_chunkRIKBJLS2cjs.indexToColName.call(void 0, area.startCol)}${area.startRow + 1}:${_chunkRIKBJLS2cjs.indexToColName.call(void 0, area.endCol)}${area.endRow + 1}`;
1711
- let reason = null;
1712
- const blockers = [];
1713
- if (area.endRow >= this.document.rows.length || area.endCol >= this.document.columns.length) {
1714
- reason = `The ${rows}\xD7${cols} result needs ${label}, which extends past the edge of the sheet.`;
1715
- } else if (this.getMerges().some((m) => rangesIntersect(m, area))) {
1716
- reason = `The result can't spill into merged cells (it needs ${label}).`;
1717
- } else {
1718
- for (let c = area.startCol; c <= area.endCol; c++) {
1719
- const column = this.document.columns[c];
1720
- for (let r = area.startRow; r <= area.endRow; r++) {
1721
- if (r === row && c === col) continue;
1722
- const rowObj = this.document.rows[r];
1723
- const key = `${column.key}${r + 1}`.toUpperCase();
1724
- const stored = _optionalChain([this, 'access', _102 => _102.document, 'access', _103 => _103.cells, 'access', _104 => _104[rowObj.id], 'optionalAccess', _105 => _105[column.id]]);
1725
- const raw = stored ? stored.raw : this.getDerivedCell(rowObj, column).raw;
1726
- const owner = this.spilledBy.get(key);
1727
- if (raw !== "") {
1728
- blockers.push(key);
1729
- reason ??= `The result needs ${label}, but ${key} isn't empty.`;
1730
- } else if (owner && owner !== anchor) {
1731
- blockers.push(key);
1732
- reason ??= `The result needs ${label}, but ${key} already holds values spilled from ${owner}.`;
1733
- }
1734
- }
1735
- }
1736
- }
1737
- if (reason) {
1738
- this.clearSpill(anchor);
1739
- this.depGraph.setSpillEdges(anchor, [], blockers);
1740
- return reason;
1741
- }
1742
- const previous = this.spills.get(anchor);
1743
- const cells = [];
1744
- for (let c = 0; c < cols; c++) {
1745
- const column = this.document.columns[col + c];
1746
- for (let r = 0; r < rows; r++) {
1747
- if (r === 0 && c === 0) continue;
1748
- const rowObj = this.document.rows[row + r];
1749
- const key = `${column.key}${row + r + 1}`.toUpperCase();
1750
- const value = _nullishCoalesce(array.values[c * rows + r], () => ( ""));
1751
- const display = { raw: "", value, format: column.format, spillAnchor: anchor };
1752
- if (_chunkRIKBJLS2cjs.isFormulaError.call(void 0, value)) {
1753
- display.error = value;
1754
- display.errorMessage = _chunkRIKBJLS2cjs.describeFormulaError.call(void 0, value);
1755
- }
1756
- this.spillCells.set(spillCellId(rowObj.id, column.id), display);
1757
- if (this.spilledBy.get(key) !== anchor) {
1758
- this.spilledBy.set(key, anchor);
1759
- _optionalChain([this, 'access', _106 => _106.spillChanges, 'optionalAccess', _107 => _107.add, 'call', _108 => _108(key)]);
1760
- }
1761
- cells.push({ key, rowId: rowObj.id, colId: column.id });
1762
- }
1763
- }
1764
- if (previous) {
1765
- const kept = new Set(cells.map((cell) => cell.key));
1766
- for (const old of previous.cells) {
1767
- if (kept.has(old.key)) continue;
1768
- this.forgetSpilledCell(anchor, old);
1769
- }
1770
- }
1771
- this.spills.set(anchor, { anchor, row, col, rows, cols, values: array.values, cells });
1772
- this.depGraph.setSpillEdges(anchor, cells.map((cell) => cell.key));
1773
- return null;
1774
- }
1775
- /** Removes the array spilled from `anchor`. With `keepEdges`, its dependency edges stay. */
1776
- clearSpill(anchor, keepEdges = false) {
1777
- const record = this.spills.get(anchor);
1778
- if (record) {
1779
- for (const cell of record.cells) this.forgetSpilledCell(anchor, cell);
1780
- this.spills.delete(anchor);
1781
- }
1782
- if (!keepEdges) this.depGraph.setSpillEdges(anchor, []);
1783
- }
1784
- forgetSpilledCell(anchor, cell) {
1785
- if (this.spilledBy.get(cell.key) !== anchor) return;
1786
- this.spilledBy.delete(cell.key);
1787
- this.spillCells.delete(spillCellId(cell.rowId, cell.colId));
1788
- _optionalChain([this, 'access', _109 => _109.spillChanges, 'optionalAccess', _110 => _110.add, 'call', _111 => _111(cell.key)]);
1789
- }
1790
- resolveSpillByCoord(colStr, rowNum) {
1791
- const record = this.spills.get(`${colStr}${rowNum}`.toUpperCase());
1792
- if (record) return { rows: record.rows, cols: record.cols, values: record.values };
1793
- const value = this.resolveCellByCoord(colStr, rowNum);
1794
- return _chunkRIKBJLS2cjs.isFormulaError.call(void 0, value) ? value : "#REF!";
1795
- }
1796
- toSpill(record) {
1797
- const row = this.document.rows[record.row];
1798
- const col = this.document.columns[record.col];
1799
- return {
1800
- key: record.anchor,
1801
- anchor: { row: record.row, col: record.col },
1802
- range: {
1803
- startRow: record.row,
1804
- startCol: record.col,
1805
- endRow: record.row + record.rows - 1,
1806
- endCol: record.col + record.cols - 1
1807
- },
1808
- formula: row && col ? this.getCell(row.id, col.id).raw : ""
1809
- };
1810
- }
1811
- /**
1812
- * The dynamic array covering (row, col), whether it is the anchor (the cell
1813
- * holding the formula) or a spilled cell; null when there is none.
1814
- */
1815
- getSpillAt(row, col) {
1816
- if (this.spills.size === 0) return null;
1817
- const key = `${_chunkRIKBJLS2cjs.indexToColName.call(void 0, col)}${row + 1}`;
1818
- const record = _nullishCoalesce(this.spills.get(key), () => ( this.spills.get(_nullishCoalesce(this.spilledBy.get(key), () => ( "")))));
1819
- return record ? this.toSpill(record) : null;
1820
- }
1821
- /** Every dynamic array currently spilled. */
1822
- getSpills() {
1823
- return Array.from(this.spills.values(), (record) => this.toSpill(record));
1824
- }
1825
- /** Whether `range` overlaps a spilled array (its anchor or spilled cells). */
1826
- intersectsSpill(range) {
1827
- for (const record of this.spills.values()) {
1828
- const area = {
1829
- startRow: record.row,
1830
- startCol: record.col,
1831
- endRow: record.row + record.rows - 1,
1832
- endCol: record.col + record.cols - 1
1833
- };
1834
- if (rangesIntersect(area, range)) return true;
1835
- }
1836
- return false;
1837
- }
1838
- resolveCellByCoord(colStr, rowNum) {
1839
- const col = this.document.columns.find((c) => c.key.toUpperCase() === colStr.toUpperCase());
1840
- const row = this.document.rows[rowNum - 1];
1841
- if (!col || !row) return 0;
1842
- return _nullishCoalesce(this.getCell(row.id, col.id).value, () => ( ""));
1843
- }
1844
- resolveRangeByCoords(sCol, sRow, eCol, eRow) {
1845
- const startC = _chunkRIKBJLS2cjs.colNameToIndex.call(void 0, sCol);
1846
- const endC = _chunkRIKBJLS2cjs.colNameToIndex.call(void 0, eCol);
1847
- const minC = Math.min(startC, endC);
1848
- const maxC = Math.max(startC, endC);
1849
- const minR = Math.min(sRow, eRow);
1850
- const maxR = Math.min(Math.max(sRow, eRow), _chunkRIKBJLS2cjs.WHOLE_COLUMN_END_ROW, this.document.rows.length);
1851
- const results = [];
1852
- for (let c = minC; c <= maxC; c++) {
1853
- const colName = _chunkRIKBJLS2cjs.indexToColName.call(void 0, c);
1854
- const col = this.document.columns.find((colDef) => colDef.key.toUpperCase() === colName);
1855
- if (!col) continue;
1856
- for (let r = minR; r <= maxR; r++) {
1857
- const row = this.document.rows[r - 1];
1858
- if (!row) continue;
1859
- results.push(_nullishCoalesce(this.getCell(row.id, col.id).value, () => ( "")));
1860
- }
1861
- }
1862
- return results;
1863
- }
1864
- resolveCurrentRowProperty(prop) {
1865
- const rowIndex = _nullishCoalesce(this.evaluatingRowIndex, () => ( _optionalChain([this, 'access', _112 => _112.activeCell, 'optionalAccess', _113 => _113.row])));
1866
- if (rowIndex === void 0 || rowIndex === null) return 0;
1867
- const row = this.document.rows[rowIndex];
1868
- if (!row || !row.recordId || !this.primaryDataSourceId) return 0;
1869
- return _nullishCoalesce(this.binder.resolveBoundValue(this.primaryDataSourceId, row.recordId, prop), () => ( 0));
1870
- }
1871
- // --- Selection and Navigation ---
1872
- selectCell(row, col, extendSelection = false) {
1873
- const clampedRow = Math.max(0, Math.min(row, this.document.rows.length - 1));
1874
- const clampedCol = Math.max(0, Math.min(col, this.document.columns.length - 1));
1875
- const merge = extendSelection ? null : this.getMergeAt(clampedRow, clampedCol);
1876
- const activeRow = merge ? merge.startRow : clampedRow;
1877
- const activeCol = merge ? merge.startCol : clampedCol;
1878
- const rowObj = this.document.rows[activeRow];
1879
- const colObj = this.document.columns[activeCol];
1880
- this.activeCell = {
1881
- row: activeRow,
1882
- col: activeCol,
1883
- rowId: _optionalChain([rowObj, 'optionalAccess', _114 => _114.id]),
1884
- colId: _optionalChain([colObj, 'optionalAccess', _115 => _115.id])
1885
- };
1886
- if (extendSelection && this.selectedRange) {
1887
- this.selectedRange = this.expandRangeToMerges({
1888
- startRow: this.selectedRange.startRow,
1889
- startCol: this.selectedRange.startCol,
1890
- endRow: clampedRow,
1891
- endCol: clampedCol
1892
- });
1893
- } else if (merge) {
1894
- this.selectedRange = { ...merge };
1895
- } else {
1896
- this.selectedRange = {
1897
- startRow: clampedRow,
1898
- startCol: clampedCol,
1899
- endRow: clampedRow,
1900
- endCol: clampedCol
1901
- };
1902
- }
1903
- _optionalChain([this, 'access', _116 => _116.onSelectionChange, 'optionalCall', _117 => _117(this.activeCell, this.selectedRange)]);
1904
- }
1905
- selectRange(range) {
1906
- this.selectedRange = range;
1907
- _optionalChain([this, 'access', _118 => _118.onSelectionChange, 'optionalCall', _119 => _119(this.activeCell, this.selectedRange)]);
1908
- }
1909
- startEditing(row, col, initialValue) {
1910
- this.selectCell(_nullishCoalesce(_nullishCoalesce(row, () => ( _optionalChain([this, 'access', _120 => _120.activeCell, 'optionalAccess', _121 => _121.row]))), () => ( 0)), _nullishCoalesce(_nullishCoalesce(col, () => ( _optionalChain([this, 'access', _122 => _122.activeCell, 'optionalAccess', _123 => _123.col]))), () => ( 0)));
1911
- const targetRow = _nullishCoalesce(_optionalChain([this, 'access', _124 => _124.activeCell, 'optionalAccess', _125 => _125.row]), () => ( 0));
1912
- const targetCol = _nullishCoalesce(_optionalChain([this, 'access', _126 => _126.activeCell, 'optionalAccess', _127 => _127.col]), () => ( 0));
1913
- const rowObj = this.document.rows[targetRow];
1914
- const colObj = this.document.columns[targetCol];
1915
- if (!rowObj || !colObj) return;
1916
- this.editingCell = {
1917
- row: targetRow,
1918
- col: targetCol,
1919
- rowId: rowObj.id,
1920
- colId: colObj.id
1921
- };
1922
- const cell = this.getCell(rowObj.id, colObj.id);
1923
- this.draftValue = initialValue !== void 0 ? initialValue : cell.raw;
1924
- }
1925
- commitEditing(valueToCommit) {
1926
- if (!this.editingCell) return;
1927
- const val = valueToCommit !== void 0 ? valueToCommit : this.draftValue;
1928
- if (this.editingCell.rowId && this.editingCell.colId) {
1929
- this.setCellValue(this.editingCell.rowId, this.editingCell.colId, val);
1930
- }
1931
- this.editingCell = null;
1932
- this.draftValue = "";
1933
- }
1934
- cancelEditing() {
1935
- this.editingCell = null;
1936
- this.draftValue = "";
1937
- }
1938
- moveCursor(direction, extendSelection = false) {
1939
- if (!this.activeCell) {
1940
- this.selectCell(0, 0);
1941
- return;
1942
- }
1943
- const { rowOrder, columnOrder } = this.getViewModel();
1944
- let nextRow = this.activeCell.row;
1945
- let nextCol = this.activeCell.col;
1946
- const merge = extendSelection ? null : this.getMergeAt(nextRow, nextCol);
1947
- switch (direction) {
1948
- case "up":
1949
- nextRow = stepInOrder(rowOrder, merge ? merge.startRow : nextRow, -1);
1950
- break;
1951
- case "down":
1952
- nextRow = stepInOrder(rowOrder, merge ? merge.endRow : nextRow, 1);
1953
- break;
1954
- case "left":
1955
- nextCol = stepInOrder(columnOrder, merge ? merge.startCol : nextCol, -1);
1956
- break;
1957
- case "right":
1958
- nextCol = stepInOrder(columnOrder, merge ? merge.endCol : nextCol, 1);
1959
- break;
1960
- }
1961
- this.selectCell(nextRow, nextCol, extendSelection);
1962
- }
1963
- // --- Grid Structure Mutations ---
1964
- insertColumn(index, config) {
1965
- this.recordSnapshot();
1966
- const newId = _optionalChain([config, 'optionalAccess', _128 => _128.id]) || this.generateId("col");
1967
- const newCol = {
1968
- id: newId,
1969
- key: _chunkRIKBJLS2cjs.indexToColName.call(void 0, index),
1970
- title: _optionalChain([config, 'optionalAccess', _129 => _129.title]) || _chunkRIKBJLS2cjs.indexToColName.call(void 0, index),
1971
- width: _optionalChain([config, 'optionalAccess', _130 => _130.width]) || 130,
1972
- type: _optionalChain([config, 'optionalAccess', _131 => _131.type]) || "freeform",
1973
- binding: _optionalChain([config, 'optionalAccess', _132 => _132.binding]),
1974
- formula: _optionalChain([config, 'optionalAccess', _133 => _133.formula]),
1975
- format: _optionalChain([config, 'optionalAccess', _134 => _134.format]) || "text",
1976
- readOnly: _optionalChain([config, 'optionalAccess', _135 => _135.readOnly])
1977
- };
1978
- this.shiftReferences({ axis: "col", index, type: "insert" });
1979
- this.adjustTableForStructuralChange({ axis: "col", index, type: "insert" });
1980
- this.document.columns.splice(index, 0, newCol);
1981
- this.recomputeRowIndices();
1982
- this.recalculateAll();
1983
- this.notifyChange();
1984
- }
1985
- deleteColumn(colId) {
1986
- this.recordSnapshot();
1987
- const colIndex = this.document.columns.findIndex((c) => c.id === colId);
1988
- if (colIndex === -1) return;
1989
- this.shiftReferences({ axis: "col", index: colIndex, type: "delete" });
1990
- this.adjustTableForStructuralChange({ axis: "col", index: colIndex, type: "delete" });
1991
- this.document.columns = this.document.columns.filter((c) => c.id !== colId);
1992
- for (const rowId of Object.keys(this.document.cells)) {
1993
- delete this.document.cells[rowId][colId];
1994
- }
1995
- this.recomputeRowIndices();
1996
- this.recalculateAll();
1997
- this.notifyChange();
1998
- }
1999
- insertRow(index, config) {
2000
- this.recordSnapshot();
2001
- const newId = _optionalChain([config, 'optionalAccess', _136 => _136.id]) || this.generateId("row");
2002
- const newRow = {
2003
- id: newId,
2004
- index,
2005
- height: _optionalChain([config, 'optionalAccess', _137 => _137.height]) || 32,
2006
- recordId: _optionalChain([config, 'optionalAccess', _138 => _138.recordId]),
2007
- type: _optionalChain([config, 'optionalAccess', _139 => _139.type]) || "freeform"
2008
- };
2009
- this.shiftReferences({ axis: "row", index, type: "insert" });
2010
- this.adjustTableForStructuralChange({ axis: "row", index, type: "insert" });
2011
- this.document.rows.splice(index, 0, newRow);
2012
- this.recomputeRowIndices();
2013
- this.recalculateAll();
2014
- this.notifyChange();
2015
- }
2016
- deleteRow(rowId) {
2017
- this.recordSnapshot();
2018
- const rowIndex = this.document.rows.findIndex((r) => r.id === rowId);
2019
- if (rowIndex === -1) return;
2020
- this.shiftReferences({ axis: "row", index: rowIndex, type: "delete" });
2021
- this.adjustTableForStructuralChange({ axis: "row", index: rowIndex, type: "delete" });
2022
- this.document.rows = this.document.rows.filter((r) => r.id !== rowId);
2023
- delete this.document.cells[rowId];
2024
- this.recomputeRowIndices();
2025
- this.recalculateAll();
2026
- this.notifyChange();
2027
- }
2028
- generateId(prefix) {
2029
- return `${prefix}_${Date.now()}_${(nextIdSequence++).toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
2030
- }
2031
- /**
2032
- * Rewrites references in every cell formula (and, for column changes, every
2033
- * column formula) so they keep pointing at the same cells.
2034
- */
2035
- shiftReferences(change) {
2036
- for (const rowCells of Object.values(this.document.cells)) {
2037
- for (const cell of Object.values(rowCells)) {
2038
- if (_optionalChain([cell, 'access', _140 => _140.raw, 'optionalAccess', _141 => _141.startsWith, 'call', _142 => _142("=")])) {
2039
- cell.raw = shiftFormulaReferences(cell.raw, change);
2040
- }
2041
- }
2042
- }
2043
- if (change.axis === "col") {
2044
- for (const col of this.document.columns) {
2045
- if (_optionalChain([col, 'access', _143 => _143.formula, 'optionalAccess', _144 => _144.startsWith, 'call', _145 => _145("=")])) {
2046
- col.formula = shiftFormulaReferences(col.formula, change);
2047
- }
2048
- }
2049
- }
2050
- }
2051
- setColumnWidth(colId, width) {
2052
- const col = this.document.columns.find((c) => c.id === colId);
2053
- if (col) {
2054
- col.width = Math.max(30, width);
2055
- this.notifyChange();
2056
- }
2057
- }
2058
- autoFitColumnWidth(colId, customWidth) {
2059
- if (customWidth !== void 0) {
2060
- this.setColumnWidth(colId, customWidth);
2061
- return;
2062
- }
2063
- const col = this.document.columns.find((c) => c.id === colId);
2064
- if (!col) return;
2065
- let maxLen = (col.title || "").length + (col.key ? col.key.length + 3 : 0);
2066
- for (const row of this.document.rows) {
2067
- const cell = this.getCell(row.id, col.id);
2068
- const valStr = _chunkRIKBJLS2cjs.formatCellDisplay.call(void 0, cell);
2069
- if (valStr.length > maxLen) {
2070
- maxLen = valStr.length;
2071
- }
2072
- }
2073
- const estimatedWidth = Math.min(600, Math.max(50, Math.ceil(maxLen * 8.5 + 32)));
2074
- this.setColumnWidth(colId, estimatedWidth);
2075
- }
2076
- // --- Clipboard TSV/CSV ---
2077
- /**
2078
- * Exports cell values as TSV: the whole sheet, or only `range` (e.g. the
2079
- * current selection) when given.
2080
- */
2081
- exportToTsv(range) {
2082
- const rows = range ? this.document.rows.slice(
2083
- Math.min(range.startRow, range.endRow),
2084
- Math.max(range.startRow, range.endRow) + 1
2085
- ) : this.document.rows;
2086
- const columns = range ? this.document.columns.slice(
2087
- Math.min(range.startCol, range.endCol),
2088
- Math.max(range.startCol, range.endCol) + 1
2089
- ) : this.document.columns;
2090
- return rows.map(
2091
- (row) => columns.map((col) => {
2092
- return _chunkRIKBJLS2cjs.formatCellDisplay.call(void 0, this.getCell(row.id, col.id)).replace(/\t/g, " ");
2093
- }).join(" ")
2094
- ).join("\n");
2095
- }
2096
- /**
2097
- * Pastes tab-separated values starting at (startRow, startCol), adding rows
2098
- * and columns as needed. The whole paste is a single undo step.
2099
- */
2100
- importFromTsv(tsvData, startRow = 0, startCol = 0) {
2101
- const normalized = tsvData.replace(/\r\n?/g, "\n").replace(/\n$/, "");
2102
- if (normalized === "") return;
2103
- this.writeMatrix(normalized.split("\n").map((line) => line.split(" ")), startRow, startCol);
2104
- }
2105
- /** Adds rows/columns at the end until (rowIndex, colIndex) exists. */
2106
- growGrid(rowIndex, colIndex) {
2107
- while (rowIndex >= this.document.rows.length) {
2108
- const index = this.document.rows.length;
2109
- this.document.rows.push({ id: this.generateId("row"), index, height: 32, type: "freeform" });
2110
- }
2111
- while (colIndex >= this.document.columns.length) {
2112
- const key = _chunkRIKBJLS2cjs.indexToColName.call(void 0, this.document.columns.length);
2113
- this.document.columns.push({
2114
- id: this.generateId("col"),
2115
- key,
2116
- title: key,
2117
- width: 130,
2118
- type: "freeform",
2119
- format: "text"
2120
- });
2121
- }
2122
- }
2123
- /**
2124
- * Writes raw inputs starting at (startRow, startCol), growing the grid as
2125
- * needed, as a single undo step with one recalculation.
2126
- */
2127
- writeMatrix(matrix, startRow, startCol) {
2128
- if (matrix.length === 0) return;
2129
- this.runBatch(() => {
2130
- matrix.forEach((line, rIdx) => {
2131
- line.forEach((val, cIdx) => {
2132
- this.growGrid(startRow + rIdx, startCol + cIdx);
2133
- const row = this.document.rows[startRow + rIdx];
2134
- this.setCellValue(row.id, this.document.columns[startCol + cIdx].id, val);
2135
- });
2136
- });
2137
- });
2138
- }
2139
- /**
2140
- * Runs `fn` as one undo step: cell writes inside it are batched and
2141
- * recalculated once, then listeners are notified.
2142
- */
2143
- runBatch(fn) {
2144
- this.recordSnapshot();
2145
- this.batching = true;
2146
- this.pendingCommits = [];
2147
- try {
2148
- fn();
2149
- } finally {
2150
- this.batching = false;
2151
- }
2152
- this.recomputeRowIndices();
2153
- this.recalculateAll();
2154
- this.document.updatedAt = Date.now();
2155
- this.notifyChange();
2156
- const commits = this.pendingCommits;
2157
- this.pendingCommits = [];
2158
- for (const [rowId, colId, raw] of commits) {
2159
- _optionalChain([this, 'access', _146 => _146.onCellCommit, 'optionalCall', _147 => _147(rowId, colId, raw, _optionalChain([this, 'access', _148 => _148.document, 'access', _149 => _149.cells, 'access', _150 => _150[rowId], 'optionalAccess', _151 => _151[colId], 'optionalAccess', _152 => _152.value]))]);
2160
- }
2161
- }
2162
- // --- CSV ---
2163
- /**
2164
- * Exports the sheet (or `range`) as RFC 4180 CSV. Values are exported as
2165
- * displayed unless `formulas` is set.
2166
- */
2167
- exportToCsv(options = {}) {
2168
- const matrix = this.readMatrix(_nullishCoalesce(options.range, () => ( null))).map(
2169
- (row) => row.map((cell) => options.formulas ? cell.raw : _chunkRIKBJLS2cjs.formatCellDisplay.call(void 0, cell))
2170
- );
2171
- return serializeCsv(matrix, {
2172
- delimiter: options.delimiter,
2173
- lineEnding: options.lineEnding,
2174
- bom: options.bom
2175
- });
2176
- }
2177
- /**
2178
- * Imports CSV text at (startRow, startCol) (default A1), growing the grid
2179
- * as needed. The whole import is a single undo step.
2180
- */
2181
- importFromCsv(csv, options = {}) {
2182
- const matrix = parseCsv(csv, { delimiter: options.delimiter });
2183
- this.writeMatrix(matrix, _nullishCoalesce(options.startRow, () => ( 0)), _nullishCoalesce(options.startCol, () => ( 0)));
2184
- }
2185
- /**
2186
- * Writes already-parsed rows of raw inputs (e.g. from an import adapter)
2187
- * starting at (startRow, startCol), as a single undo step.
2188
- */
2189
- importRows(rows, options = {}) {
2190
- this.writeMatrix(rows, _nullishCoalesce(options.startRow, () => ( 0)), _nullishCoalesce(options.startCol, () => ( 0)));
2191
- }
2192
- /** The cells of `range` (or the whole sheet), row by row. */
2193
- readMatrix(range) {
2194
- const r = range ? normalizeRange(range) : null;
2195
- const rows = r ? this.document.rows.slice(r.startRow, r.endRow + 1) : this.document.rows;
2196
- const columns = r ? this.document.columns.slice(r.startCol, r.endCol + 1) : this.document.columns;
2197
- return rows.map((row) => columns.map((col) => this.getCell(row.id, col.id)));
2198
- }
2199
- // --- Spreadsheet clipboard ---
2200
- /**
2201
- * Builds clipboard data for `range` (default: the selection) as TSV,
2202
- * an HTML table and the lossless internal JSON format. Write every entry
2203
- * with `clipboardData.setData(type, data)` in a `copy` event.
2204
- */
2205
- copyToClipboardData(range = this.selectedRange) {
2206
- const r = range ? normalizeRange(range) : { startRow: 0, startCol: 0, endRow: this.document.rows.length - 1, endCol: this.document.columns.length - 1 };
2207
- const cells = this.readMatrix(r).map(
2208
- (row) => row.map((cell) => {
2209
- const value = cell.error || cell.value;
2210
- const text = _chunkRIKBJLS2cjs.formatCellDisplay.call(void 0, cell);
2211
- if (cell.spillAnchor && !this.spillAnchorInRange(cell.spillAnchor, r)) {
2212
- return { raw: text, value, text };
2213
- }
2214
- return { raw: cell.raw, value, text };
2215
- })
2216
- );
2217
- return createSpreadsheetClipboardData(cells, { row: r.startRow, col: r.startCol });
2218
- }
2219
- /**
2220
- * Pastes clipboard data at `target` (default: the selection's top-left).
2221
- * Prefers the internal format (formulas re-based to the target), then an
2222
- * HTML table, then TSV. Returns false when there was nothing to paste.
2223
- */
2224
- pasteClipboardData(source, target) {
2225
- const parsed = readSpreadsheetClipboard(source);
2226
- if (!parsed) return false;
2227
- const at = _nullishCoalesce(target, () => ( this.getSelectionTopLeft()));
2228
- const rows = parsed.origin ? rebaseClipboardRows(parsed.rows, parsed.origin, at) : parsed.rows;
2229
- this.writeMatrix(rows, at.row, at.col);
2230
- const r = normalizeRange({
2231
- startRow: at.row,
2232
- startCol: at.col,
2233
- endRow: at.row + rows.length - 1,
2234
- endCol: at.col + rows.reduce((max, row) => Math.max(max, row.length), 1) - 1
2235
- });
2236
- this.selectRange(r);
2237
- return true;
2238
- }
2239
- spillAnchorInRange(anchor, range) {
2240
- const record = this.spills.get(anchor);
2241
- return !!record && rangeContains(range, record.row, record.col);
2242
- }
2243
- getSelectionTopLeft() {
2244
- if (this.selectedRange) {
2245
- const r = normalizeRange(this.selectedRange);
2246
- return { row: r.startRow, col: r.startCol };
2247
- }
2248
- return { row: _nullishCoalesce(_optionalChain([this, 'access', _153 => _153.activeCell, 'optionalAccess', _154 => _154.row]), () => ( 0)), col: _nullishCoalesce(_optionalChain([this, 'access', _155 => _155.activeCell, 'optionalAccess', _156 => _156.col]), () => ( 0)) };
2249
- }
2250
- // --- Table operations: shared helpers ---
2251
- /** The table settings object, created on first use. */
2252
- get table() {
2253
- if (!this.document.table) this.document.table = {};
2254
- return this.document.table;
2255
- }
2256
- /** Records an undo step, applies `fn`, then notifies listeners. */
2257
- applyTableChange(fn, options = {}) {
2258
- this.recordSnapshot();
2259
- fn();
2260
- if (options.recalculate) {
2261
- this.recomputeRowIndices();
2262
- this.recalculateAll();
2263
- }
2264
- this.document.updatedAt = Date.now();
2265
- this.notifyChange();
2266
- }
2267
- /** First and last (inclusive) body row: rows between header and footer rows. */
2268
- getBodyRowBounds() {
2269
- const { headerRows, footerRows } = getTableSections(this.document);
2270
- return { start: headerRows, end: this.document.rows.length - footerRows - 1 };
2271
- }
2272
- __init24() {this.getCellValueAt = (rowIndex, colIndex) => {
2273
- const row = this.document.rows[rowIndex];
2274
- const col = this.document.columns[colIndex];
2275
- if (!row || !col) return "";
2276
- return this.getCell(row.id, col.id).value;
2277
- }}
2278
- /** Rewrites every formula so references follow cells that moved. */
2279
- remapAllFormulas(moveCell) {
2280
- for (const rowCells of Object.values(this.document.cells)) {
2281
- for (const cell of Object.values(rowCells)) {
2282
- if (_optionalChain([cell, 'access', _157 => _157.raw, 'optionalAccess', _158 => _158.startsWith, 'call', _159 => _159("=")])) {
2283
- cell.raw = remapFormulaReferences(cell.raw, moveCell);
2284
- }
2285
- }
2286
- }
2287
- }
2288
- /**
2289
- * Keeps merges in place when rows/columns are inserted or deleted:
2290
- * merges after the change shift, merges across it grow or shrink, and
2291
- * merges reduced to a single cell are dropped.
2292
- */
2293
- adjustTableForStructuralChange(change) {
2294
- const merges = _optionalChain([this, 'access', _160 => _160.document, 'access', _161 => _161.table, 'optionalAccess', _162 => _162.merges]);
2295
- if (!_optionalChain([merges, 'optionalAccess', _163 => _163.length])) return;
2296
- const startKey = change.axis === "row" ? "startRow" : "startCol";
2297
- const endKey = change.axis === "row" ? "endRow" : "endCol";
2298
- const next = [];
2299
- for (const merge of merges) {
2300
- const m = { ...merge };
2301
- if (change.type === "insert") {
2302
- if (m[startKey] >= change.index) m[startKey]++;
2303
- if (m[endKey] >= change.index) m[endKey]++;
2304
- } else {
2305
- if (m[startKey] === change.index && m[endKey] === change.index) continue;
2306
- if (m[startKey] > change.index) m[startKey]--;
2307
- if (m[endKey] >= change.index) m[endKey]--;
2308
- }
2309
- if (!isSingleCellRange(m)) next.push(m);
2310
- }
2311
- this.document.table.merges = next;
2312
- }
2313
- // --- Sort ---
2314
- /**
2315
- * Sorts rows by one or more columns (stable; numbers before text, blanks
2316
- * last). Without a range (or with a range spanning every column) whole rows
2317
- * move; with a range only the cells inside it move. Header and footer rows
2318
- * never move. References follow the moved cells. Returns false when the
2319
- * sort is rejected (no valid keys, a key outside the range, or merged
2320
- * cells in the sorted area).
2321
- */
2322
- sortRows(keys, options = {}) {
2323
- const colCount = this.document.columns.length;
2324
- const body = this.getBodyRowBounds();
2325
- const range = options.range ? normalizeRange(options.range) : null;
2326
- const startRow = Math.max(_nullishCoalesce(_optionalChain([range, 'optionalAccess', _164 => _164.startRow]), () => ( body.start)), body.start);
2327
- const endRow = Math.min(_nullishCoalesce(_optionalChain([range, 'optionalAccess', _165 => _165.endRow]), () => ( body.end)), body.end);
2328
- const startCol = range ? Math.max(0, range.startCol) : 0;
2329
- const endCol = range ? Math.min(colCount - 1, range.endCol) : colCount - 1;
2330
- if (endRow - startRow < 1 || startCol > endCol) return false;
2331
- const keyCols = keys.map((key) => ({
2332
- index: this.document.columns.findIndex((c) => c.id === key.colId),
2333
- direction: _nullishCoalesce(key.direction, () => ( "asc"))
2334
- }));
2335
- if (keyCols.length === 0 || keyCols.some((k) => k.index < startCol || k.index > endCol)) return false;
2336
- const area = { startRow, startCol, endRow, endCol };
2337
- if ((_nullishCoalesce(_optionalChain([this, 'access', _166 => _166.document, 'access', _167 => _167.table, 'optionalAccess', _168 => _168.merges]), () => ( []))).some((m) => rangesIntersect(m, area))) return false;
2338
- if (this.intersectsSpill(area)) return false;
2339
- const sourceRows = [];
2340
- for (let r = startRow; r <= endRow; r++) sourceRows.push(r);
2341
- const sorted = sourceRows.map((r) => ({ r, values: keyCols.map((k) => this.getCellValueAt(r, k.index)) })).sort((a, b) => {
2342
- for (let i = 0; i < keyCols.length; i++) {
2343
- const result = compareCellValues(a.values[i], b.values[i], keyCols[i].direction);
2344
- if (result !== 0) return result;
2345
- }
2346
- return a.r - b.r;
2347
- }).map((entry) => entry.r);
2348
- if (sorted.every((r, i) => r === sourceRows[i])) return true;
2349
- const newIndexOf = /* @__PURE__ */ new Map();
2350
- sorted.forEach((oldRow, i) => newIndexOf.set(oldRow, startRow + i));
2351
- const wholeRows = startCol === 0 && endCol === colCount - 1;
2352
- this.applyTableChange(() => {
2353
- this.remapAllFormulas(({ row, col }) => {
2354
- const target = newIndexOf.get(row);
2355
- if (target === void 0 || col < startCol || col > endCol) return null;
2356
- return { row: target, col };
2357
- });
2358
- if (wholeRows) {
2359
- const moved = sorted.map((r) => this.document.rows[r]);
2360
- this.document.rows.splice(startRow, moved.length, ...moved);
2361
- } else {
2362
- const colIds = this.document.columns.slice(startCol, endCol + 1).map((c) => c.id);
2363
- const snapshot = sorted.map((oldRow) => {
2364
- const rowId = this.document.rows[oldRow].id;
2365
- return colIds.map((colId) => _optionalChain([this, 'access', _169 => _169.document, 'access', _170 => _170.cells, 'access', _171 => _171[rowId], 'optionalAccess', _172 => _172[colId]]));
2366
- });
2367
- snapshot.forEach((cells, i) => {
2368
- const rowId = this.document.rows[startRow + i].id;
2369
- if (!this.document.cells[rowId]) this.document.cells[rowId] = {};
2370
- colIds.forEach((colId, c) => {
2371
- if (cells[c]) this.document.cells[rowId][colId] = cells[c];
2372
- else delete this.document.cells[rowId][colId];
2373
- });
2374
- });
2375
- }
2376
- }, { recalculate: true });
2377
- return true;
2378
- }
2379
- // --- Filters ---
2380
- getFilterState() {
2381
- return _nullishCoalesce(_optionalChain([this, 'access', _173 => _173.document, 'access', _174 => _174.table, 'optionalAccess', _175 => _175.filter]), () => ( { enabled: false, rules: [] }));
2382
- }
2383
- /** Replaces all filter rules (and turns filters on when there are any). */
2384
- setFilterRules(rules) {
2385
- this.applyTableChange(() => {
2386
- this.table.filter = {
2387
- enabled: rules.length > 0,
2388
- rules: rules.map((rule) => ({ ...rule, id: _nullishCoalesce(rule.id, () => ( this.generateFilterId())) }))
2389
- };
2390
- });
2391
- }
2392
- /** Adds a filter rule (AND-combined with the others) and turns filters on. Returns its id. */
2393
- addFilterRule(rule) {
2394
- const id = _nullishCoalesce(rule.id, () => ( this.generateFilterId()));
2395
- this.applyTableChange(() => {
2396
- const current = this.getFilterState();
2397
- this.table.filter = { enabled: true, rules: [...current.rules, { ...rule, id }] };
2398
- });
2399
- return id;
2400
- }
2401
- updateFilterRule(ruleId, changes) {
2402
- const current = this.getFilterState();
2403
- if (!current.rules.some((r) => r.id === ruleId)) return;
2404
- this.applyTableChange(() => {
2405
- this.table.filter = {
2406
- ...current,
2407
- rules: current.rules.map((r) => r.id === ruleId ? { ...r, ...changes } : r)
2408
- };
2409
- });
2410
- }
2411
- removeFilterRule(ruleId) {
2412
- const current = this.getFilterState();
2413
- if (!current.rules.some((r) => r.id === ruleId)) return;
2414
- this.applyTableChange(() => {
2415
- this.table.filter = { ...current, rules: current.rules.filter((r) => r.id !== ruleId) };
2416
- });
2417
- }
2418
- clearFilters() {
2419
- if (this.getFilterState().rules.length === 0) return;
2420
- this.applyTableChange(() => {
2421
- this.table.filter = { enabled: false, rules: [] };
2422
- });
2423
- }
2424
- /** Turns filtering on or off, keeping the rules. */
2425
- setFiltersEnabled(enabled) {
2426
- const current = this.getFilterState();
2427
- if (current.enabled === enabled) return;
2428
- this.applyTableChange(() => {
2429
- this.table.filter = { ...current, enabled };
2430
- });
2431
- }
2432
- toggleFilters() {
2433
- this.setFiltersEnabled(!this.getFilterState().enabled);
2434
- }
2435
- generateFilterId() {
2436
- return `filter_${Date.now()}_${(nextIdSequence++).toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
2437
- }
2438
- // --- Header, footer and frozen rows/columns ---
2439
- setHeaderRowCount(count) {
2440
- this.setTableCount("headerRowCount", count, this.document.rows.length);
2441
- }
2442
- setFooterRowCount(count) {
2443
- this.setTableCount("footerRowCount", count, this.document.rows.length);
2444
- }
2445
- setHeaderColumnCount(count) {
2446
- this.setTableCount("headerColumnCount", count, this.document.columns.length);
2447
- }
2448
- /** Keeps the first `count` rows visible while scrolling. 0 unfreezes. */
2449
- setFrozenRowCount(count) {
2450
- this.setTableCount("frozenRowCount", count, this.document.rows.length);
2451
- }
2452
- /** Keeps the first `count` columns visible while scrolling. 0 unfreezes. */
2453
- setFrozenColumnCount(count) {
2454
- this.setTableCount("frozenColumnCount", count, this.document.columns.length);
2455
- }
2456
- setTableCount(key, count, max) {
2457
- const value = Math.max(0, Math.min(Math.floor(count), max));
2458
- if ((_nullishCoalesce(_optionalChain([this, 'access', _176 => _176.document, 'access', _177 => _177.table, 'optionalAccess', _178 => _178[key]]), () => ( 0))) === value) return;
2459
- this.applyTableChange(() => {
2460
- this.table[key] = value;
2461
- });
2462
- }
2463
- // --- Hide / unhide ---
2464
- hideRows(indices) {
2465
- this.setHidden("row", indices, true);
2466
- }
2467
- unhideRows(indices) {
2468
- this.setHidden("row", indices, false);
2469
- }
2470
- hideColumns(indices) {
2471
- this.setHidden("col", indices, true);
2472
- }
2473
- unhideColumns(indices) {
2474
- this.setHidden("col", indices, false);
2475
- }
2476
- /** Unhides every hidden row and column (filters are unaffected). */
2477
- unhideAll() {
2478
- const rows = this.document.rows.flatMap((r, i) => r.hidden ? [i] : []);
2479
- const cols = this.document.columns.flatMap((c, i) => c.hidden ? [i] : []);
2480
- if (rows.length === 0 && cols.length === 0) return;
2481
- this.applyTableChange(() => {
2482
- rows.forEach((i) => delete this.document.rows[i].hidden);
2483
- cols.forEach((i) => delete this.document.columns[i].hidden);
2484
- });
2485
- }
2486
- setHidden(axis, indices, hidden) {
2487
- const items = axis === "row" ? this.document.rows : this.document.columns;
2488
- const targets = indices.filter((i) => items[i] && !!items[i].hidden !== hidden);
2489
- if (targets.length === 0) return;
2490
- this.applyTableChange(() => {
2491
- for (const i of targets) {
2492
- if (hidden) items[i].hidden = true;
2493
- else delete items[i].hidden;
2494
- }
2495
- });
2496
- }
2497
- // --- Merged cells ---
2498
- getMerges() {
2499
- return _nullishCoalesce(_optionalChain([this, 'access', _179 => _179.document, 'access', _180 => _180.table, 'optionalAccess', _181 => _181.merges]), () => ( []));
2500
- }
2501
- /** The merge containing (row, col), or null. */
2502
- getMergeAt(row, col) {
2503
- return _nullishCoalesce(this.getMerges().find((m) => rangeContains(m, row, col)), () => ( null));
2504
- }
2505
- /** Grows `range` until no merge crosses its edge. Keeps the drag direction. */
2506
- expandRangeToMerges(range) {
2507
- let r = normalizeRange(range);
2508
- let changed = true;
2509
- while (changed) {
2510
- changed = false;
2511
- for (const m of this.getMerges()) {
2512
- if (rangesIntersect(m, r) && !rangeContainsRange(r, m)) {
2513
- r = {
2514
- startRow: Math.min(r.startRow, m.startRow),
2515
- startCol: Math.min(r.startCol, m.startCol),
2516
- endRow: Math.max(r.endRow, m.endRow),
2517
- endCol: Math.max(r.endCol, m.endCol)
2518
- };
2519
- changed = true;
2520
- }
2521
- }
2522
- }
2523
- const rowsReversed = range.startRow > range.endRow;
2524
- const colsReversed = range.startCol > range.endCol;
2525
- return {
2526
- startRow: rowsReversed ? r.endRow : r.startRow,
2527
- endRow: rowsReversed ? r.startRow : r.endRow,
2528
- startCol: colsReversed ? r.endCol : r.startCol,
2529
- endCol: colsReversed ? r.startCol : r.endCol
2530
- };
2531
- }
2532
- /**
2533
- * Merges `range` (default: the selection) into one cell anchored on its
2534
- * top-left cell; the other cells are cleared. Merges fully inside the range
2535
- * are absorbed. Returns false for single cells and for ranges that
2536
- * partially overlap an existing merge.
2537
- */
2538
- mergeCells(range = this.selectedRange) {
2539
- if (!range) return false;
2540
- const r = normalizeRange(range);
2541
- if (isSingleCellRange(r) || r.startRow < 0 || r.startCol < 0 || r.endRow >= this.document.rows.length || r.endCol >= this.document.columns.length) {
2542
- return false;
2543
- }
2544
- const merges = this.getMerges();
2545
- if (merges.some((m) => rangesIntersect(m, r) && !rangeContainsRange(r, m))) return false;
2546
- if (this.intersectsSpill(r)) return false;
2547
- this.applyTableChange(() => {
2548
- for (let row = r.startRow; row <= r.endRow; row++) {
2549
- const rowId = this.document.rows[row].id;
2550
- for (let col = r.startCol; col <= r.endCol; col++) {
2551
- if (row === r.startRow && col === r.startCol) continue;
2552
- _optionalChainDelete([this, 'access', _182 => _182.document, 'access', _183 => _183.cells, 'access', _184 => _184[rowId], 'optionalAccess', _185 => _185[this.document.columns[col].id]]);
2553
- }
2554
- }
2555
- this.table.merges = [...merges.filter((m) => !rangeContainsRange(r, m)), r];
2556
- }, { recalculate: true });
2557
- this.selectCell(r.startRow, r.startCol);
2558
- return true;
2559
- }
2560
- /** Removes every merge intersecting `range` (default: the selection). */
2561
- unmergeCells(range = this.selectedRange) {
2562
- if (!range) return false;
2563
- const merges = this.getMerges();
2564
- const remaining = merges.filter((m) => !rangesIntersect(m, range));
2565
- if (remaining.length === merges.length) return false;
2566
- this.applyTableChange(() => {
2567
- this.table.merges = remaining;
2568
- }, { recalculate: true });
2569
- return true;
2570
- }
2571
- // --- Autofill ---
2572
- /**
2573
- * Fills `target` from `source` like dragging the fill handle. `target` is
2574
- * the extended range (source included) and must extend `source` down, up,
2575
- * right or left. Numbers and dates continue their series, text ending in a
2576
- * number counts up, other text repeats, and formulas are copied with
2577
- * their relative references shifted (`$` parts stay). Returns false when
2578
- * the ranges don't line up.
2579
- */
2580
- autofill(source, target) {
2581
- const s = normalizeRange(source);
2582
- const t = normalizeRange(target);
2583
- const direction = getFillDirection(s, t);
2584
- if (!direction || t.startRow < 0 || t.startCol < 0) return false;
2585
- if (s.endRow >= this.document.rows.length || s.endCol >= this.document.columns.length) return false;
2586
- const vertical = direction === "down" || direction === "up";
2587
- const forward = direction === "down" || direction === "right";
2588
- const sourceSteps = [];
2589
- const [sLo, sHi] = vertical ? [s.startRow, s.endRow] : [s.startCol, s.endCol];
2590
- for (let i = sLo; i <= sHi; i++) sourceSteps.push(i);
2591
- if (!forward) sourceSteps.reverse();
2592
- const fillSteps = [];
2593
- if (forward) for (let i = sHi + 1; i <= (vertical ? t.endRow : t.endCol); i++) fillSteps.push(i);
2594
- else for (let i = sLo - 1; i >= (vertical ? t.startRow : t.startCol); i--) fillSteps.push(i);
2595
- const [lLo, lHi] = vertical ? [s.startCol, s.endCol] : [s.startRow, s.endRow];
2596
- this.runBatch(() => {
2597
- this.growGrid(vertical ? Math.max(t.endRow, 0) : s.endRow, vertical ? s.endCol : Math.max(t.endCol, 0));
2598
- for (let line = lLo; line <= lHi; line++) {
2599
- const at = (step) => vertical ? { row: step, col: line } : { row: line, col: step };
2600
- const raws = sourceSteps.map((step) => {
2601
- const p = at(step);
2602
- return this.getCell(this.document.rows[p.row].id, this.document.columns[p.col].id).raw;
2603
- });
2604
- const hasFormula = raws.some((raw) => raw.startsWith("="));
2605
- const series = hasFormula ? null : fillSeries(raws, fillSteps.length);
2606
- fillSteps.forEach((step, i) => {
2607
- const p = at(step);
2608
- let raw;
2609
- if (series) {
2610
- raw = series[i];
2611
- } else {
2612
- const from = sourceSteps[i % sourceSteps.length];
2613
- raw = raws[i % raws.length];
2614
- if (raw.startsWith("=")) {
2615
- const delta = step - from;
2616
- raw = vertical ? offsetFormulaReferences(raw, delta, 0) : offsetFormulaReferences(raw, 0, delta);
2617
- }
2618
- }
2619
- this.setCellValue(this.document.rows[p.row].id, this.document.columns[p.col].id, raw);
2620
- });
2621
- }
2622
- });
2623
- this.selectRange(t);
2624
- return true;
2625
- }
2626
- // --- Categories (row groups) ---
2627
- /**
2628
- * Groups body rows by the value in `colId`, with optional per-group
2629
- * summaries (e.g. `[{ colId, aggregate: 'sum' }]`).
2630
- */
2631
- groupRowsBy(colId, summaries = []) {
2632
- if (!this.getColumn(colId)) return;
2633
- this.applyTableChange(() => {
2634
- this.table.grouping = { colId, summaries, collapsed: [] };
2635
- });
2636
- }
2637
- ungroupRows() {
2638
- if (!_optionalChain([this, 'access', _186 => _186.document, 'access', _187 => _187.table, 'optionalAccess', _188 => _188.grouping])) return;
2639
- this.applyTableChange(() => {
2640
- delete this.table.grouping;
2641
- });
2642
- }
2643
- /** Sets (or with `null`, removes) the group summary for a column. */
2644
- setGroupSummary(colId, aggregate) {
2645
- const grouping = _optionalChain([this, 'access', _189 => _189.document, 'access', _190 => _190.table, 'optionalAccess', _191 => _191.grouping]);
2646
- if (!grouping) return;
2647
- this.applyTableChange(() => {
2648
- const others = grouping.summaries.filter((s) => s.colId !== colId);
2649
- this.table.grouping = {
2650
- ...grouping,
2651
- summaries: aggregate ? [...others, { colId, aggregate }] : others
2652
- };
2653
- });
2654
- }
2655
- setGroupCollapsed(key, collapsed) {
2656
- const grouping = _optionalChain([this, 'access', _192 => _192.document, 'access', _193 => _193.table, 'optionalAccess', _194 => _194.grouping]);
2657
- if (!grouping || grouping.collapsed.includes(key) === collapsed) return;
2658
- this.applyTableChange(() => {
2659
- this.table.grouping = {
2660
- ...grouping,
2661
- collapsed: collapsed ? [...grouping.collapsed, key] : grouping.collapsed.filter((k) => k !== key)
2662
- };
2663
- });
2664
- }
2665
- toggleGroupCollapsed(key) {
2666
- const grouping = _optionalChain([this, 'access', _195 => _195.document, 'access', _196 => _196.table, 'optionalAccess', _197 => _197.grouping]);
2667
- if (!grouping) return;
2668
- this.setGroupCollapsed(key, !grouping.collapsed.includes(key));
2669
- }
2670
- /** The current row groups with their summaries (empty when not grouped). */
2671
- getGroups() {
2672
- return computeRowGroups(this.document, this.getCellValueAt);
2673
- }
2674
- // --- Transpose ---
2675
- /**
2676
- * Transposes `range` (default: the selection) in place around its top-left
2677
- * cell: rows become columns. Cells left uncovered are cleared, the grid
2678
- * grows if needed, and references follow the moved cells. Returns false
2679
- * for single cells or when merged cells are involved.
2680
- */
2681
- transposeRange(range = this.selectedRange) {
2682
- if (!range) return false;
2683
- const s = normalizeRange(range);
2684
- if (isSingleCellRange(s)) return false;
2685
- const height = s.endRow - s.startRow + 1;
2686
- const width = s.endCol - s.startCol + 1;
2687
- const t = { startRow: s.startRow, startCol: s.startCol, endRow: s.startRow + width - 1, endCol: s.startCol + height - 1 };
2688
- if (this.getMerges().some((m) => rangesIntersect(m, s) || rangesIntersect(m, t))) return false;
2689
- if (this.intersectsSpill(s) || this.intersectsSpill(t)) return false;
2690
- this.applyTableChange(() => {
2691
- this.growGrid(t.endRow, t.endCol);
2692
- this.remapAllFormulas(({ row, col }) => {
2693
- if (!rangeContains(s, row, col)) return null;
2694
- return { row: s.startRow + (col - s.startCol), col: s.startCol + (row - s.startRow) };
2695
- });
2696
- const rowId = (r) => this.document.rows[r].id;
2697
- const colId = (c) => this.document.columns[c].id;
2698
- const moved = [];
2699
- for (let r = s.startRow; r <= s.endRow; r++) {
2700
- for (let c = s.startCol; c <= s.endCol; c++) {
2701
- moved.push({
2702
- row: s.startRow + (c - s.startCol),
2703
- col: s.startCol + (r - s.startRow),
2704
- cell: _optionalChain([this, 'access', _198 => _198.document, 'access', _199 => _199.cells, 'access', _200 => _200[rowId(r)], 'optionalAccess', _201 => _201[colId(c)]])
2705
- });
2706
- _optionalChainDelete([this, 'access', _202 => _202.document, 'access', _203 => _203.cells, 'access', _204 => _204[rowId(r)], 'optionalAccess', _205 => _205[colId(c)]]);
2707
- }
2708
- }
2709
- for (const { row, col, cell } of moved) {
2710
- if (!cell) continue;
2711
- if (!this.document.cells[rowId(row)]) this.document.cells[rowId(row)] = {};
2712
- this.document.cells[rowId(row)][colId(col)] = cell;
2713
- }
2714
- }, { recalculate: true });
2715
- this.selectRange(t);
2716
- return true;
2717
- }
2718
- // --- View model ---
2719
- /**
2720
- * What to render: visible rows (with group headers) and columns in display
2721
- * order, frozen offsets and merged-cell spans. Filters, hidden rows/columns
2722
- * and collapsed groups only affect this view, never the document.
2723
- */
2724
- getViewModel() {
2725
- return buildSpreadsheetView(this.document, this.getCellValueAt);
2726
- }
2727
- // --- Undo/Redo Snapshots ---
2728
- recordSnapshot() {
2729
- if (this.isApplyingHistory || this.batching) return;
2730
- this.undoStack.push(JSON.stringify(this.document));
2731
- if (this.undoStack.length > 50) this.undoStack.shift();
2732
- this.redoStack = [];
2733
- this.trimHistory();
2734
- }
2735
- /** Drops the oldest snapshots beyond `maxHistoryBytes`, always keeping the newest. */
2736
- trimHistory() {
2737
- if (this.maxHistoryBytes <= 0 || this.undoStack.length <= 1) return;
2738
- let total = 0;
2739
- for (const s of this.undoStack) total += s.length * 2;
2740
- for (const s of this.redoStack) total += s.length * 2;
2741
- while (total > this.maxHistoryBytes && this.undoStack.length > 1) {
2742
- total -= this.undoStack.shift().length * 2;
2743
- }
2744
- }
2745
- /** Approximate bytes held by the undo/redo snapshots (UTF-16 size). */
2746
- getHistoryByteSize() {
2747
- let total = 0;
2748
- for (const s of this.undoStack) total += s.length * 2;
2749
- for (const s of this.redoStack) total += s.length * 2;
2750
- return total;
2751
- }
2752
- /** Number of undo / redo snapshots currently held. */
2753
- getHistoryDepth() {
2754
- return { undo: this.undoStack.length, redo: this.redoStack.length };
2755
- }
2756
- undo() {
2757
- if (this.undoStack.length === 0) return;
2758
- this.isApplyingHistory = true;
2759
- try {
2760
- this.redoStack.push(JSON.stringify(this.document));
2761
- this.document = JSON.parse(this.undoStack.pop());
2762
- this.recalculateAll();
2763
- this.notifyChange();
2764
- } finally {
2765
- this.isApplyingHistory = false;
2766
- }
2767
- }
2768
- redo() {
2769
- if (this.redoStack.length === 0) return;
2770
- this.isApplyingHistory = true;
2771
- try {
2772
- this.undoStack.push(JSON.stringify(this.document));
2773
- this.document = JSON.parse(this.redoStack.pop());
2774
- this.recalculateAll();
2775
- this.notifyChange();
2776
- } finally {
2777
- this.isApplyingHistory = false;
2778
- }
2779
- }
2780
- notifyChange() {
2781
- _optionalChain([this, 'access', _206 => _206.onDocumentChange, 'optionalCall', _207 => _207(this.document)]);
2782
- }
2783
- }, _class3);
2784
- function stepInOrder(order, from, delta) {
2785
- if (order.length === 0) return from;
2786
- const pos = order.indexOf(from);
2787
- if (pos === -1) {
2788
- const candidates = order.filter((i) => delta > 0 ? i > from : i < from);
2789
- if (candidates.length === 0) return from;
2790
- return candidates.reduce((best, i) => delta > 0 ? Math.min(best, i) : Math.max(best, i));
2791
- }
2792
- return order[Math.max(0, Math.min(order.length - 1, pos + delta))];
2793
- }
2794
-
2795
- // src/spreadsheet/menu.ts
2796
- function clampMenuPosition(point, size, viewport, margin = 4) {
2797
- const place = (start, length, limit) => {
2798
- if (start + length <= limit - margin) return Math.max(margin, start);
2799
- const flipped = start - length;
2800
- return Math.max(margin, flipped >= margin ? flipped : limit - margin - length);
2801
- };
2802
- return {
2803
- x: place(point.x, size.width, viewport.width),
2804
- y: place(point.y, size.height, viewport.height)
2805
- };
2806
- }
2807
- var quote = (value) => {
2808
- const text = String(_nullishCoalesce(value, () => ( "")));
2809
- return text.length > 24 ? `"${text.slice(0, 23)}\u2026"` : `"${text}"`;
2810
- };
2811
- function getSpreadsheetMenuItems(controller, cell) {
2812
- const doc = controller.document;
2813
- const column = doc.columns[cell.col];
2814
- const row = doc.rows[cell.row];
2815
- if (!column || !row) return [];
2816
- 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 };
2817
- const colName = _chunkRIKBJLS2cjs.indexToColName.call(void 0, cell.col);
2818
- const cellData = controller.getCell(row.id, column.id);
2819
- const value = cellData.value;
2820
- const table = _nullishCoalesce(doc.table, () => ( {}));
2821
- const filter = controller.getFilterState();
2822
- const grouping = table.grouping;
2823
- const rows = Array.from({ length: selection.endRow - selection.startRow + 1 }, (_, i) => selection.startRow + i);
2824
- const cols = Array.from({ length: selection.endCol - selection.startCol + 1 }, (_, i) => selection.startCol + i);
2825
- const anyHidden = doc.rows.some((r) => r.hidden) || doc.columns.some((c) => c.hidden);
2826
- const hasMerge = controller.getMerges().some((m) => rangesIntersect(m, selection));
2827
- const sortRange = isSingleCellRange(selection) ? null : selection;
2828
- const items = [
2829
- {
2830
- id: "sort-asc",
2831
- label: `Sort ascending by column ${colName}`,
2832
- run: () => controller.sortRows([{ colId: column.id, direction: "asc" }], { range: sortRange })
2833
- },
2834
- {
2835
- id: "sort-desc",
2836
- label: `Sort descending by column ${colName}`,
2837
- run: () => controller.sortRows([{ colId: column.id, direction: "desc" }], { range: sortRange })
2838
- },
2839
- {
2840
- id: "filter-equals",
2841
- label: `Show only rows where ${colName} = ${quote(_chunkRIKBJLS2cjs.formatCellDisplay.call(void 0, cellData))}`,
2842
- separatorBefore: true,
2843
- run: () => controller.addFilterRule({ colId: column.id, operator: "equals", value: String(_nullishCoalesce(value, () => ( ""))) })
2844
- },
2845
- {
2846
- id: "filter-toggle",
2847
- label: filter.enabled ? "Turn filters off" : "Turn filters on",
2848
- disabled: filter.rules.length === 0,
2849
- run: () => controller.toggleFilters()
2850
- },
2851
- {
2852
- id: "filter-clear",
2853
- label: "Clear filters",
2854
- disabled: filter.rules.length === 0,
2855
- run: () => controller.clearFilters()
2856
- },
2857
- {
2858
- id: "freeze-rows",
2859
- label: `Freeze rows 1\u2013${cell.row + 1}`,
2860
- separatorBefore: true,
2861
- run: () => controller.setFrozenRowCount(cell.row + 1)
2862
- },
2863
- {
2864
- id: "freeze-columns",
2865
- label: `Freeze columns A\u2013${colName}`,
2866
- run: () => controller.setFrozenColumnCount(cell.col + 1)
2867
- },
2868
- {
2869
- id: "unfreeze",
2870
- label: "Unfreeze rows and columns",
2871
- disabled: !table.frozenRowCount && !table.frozenColumnCount,
2872
- run: () => {
2873
- controller.setFrozenRowCount(0);
2874
- controller.setFrozenColumnCount(0);
2875
- }
2876
- },
2877
- {
2878
- id: "header-rows",
2879
- label: `Use rows 1\u2013${cell.row + 1} as header rows`,
2880
- run: () => controller.setHeaderRowCount(cell.row + 1)
2881
- },
2882
- {
2883
- id: "footer-rows",
2884
- label: `Use rows ${cell.row + 1}\u2013${doc.rows.length} as footer rows`,
2885
- run: () => controller.setFooterRowCount(doc.rows.length - cell.row)
2886
- },
2887
- {
2888
- id: "hide-rows",
2889
- label: rows.length > 1 ? `Hide ${rows.length} rows` : `Hide row ${cell.row + 1}`,
2890
- separatorBefore: true,
2891
- run: () => controller.hideRows(rows)
2892
- },
2893
- {
2894
- id: "hide-columns",
2895
- label: cols.length > 1 ? `Hide ${cols.length} columns` : `Hide column ${colName}`,
2896
- run: () => controller.hideColumns(cols)
2897
- },
2898
- {
2899
- id: "unhide-all",
2900
- label: "Unhide all rows and columns",
2901
- disabled: !anyHidden,
2902
- run: () => controller.unhideAll()
2903
- },
2904
- {
2905
- id: "merge",
2906
- label: "Merge cells",
2907
- separatorBefore: true,
2908
- disabled: isSingleCellRange(selection),
2909
- run: () => {
2910
- controller.mergeCells(selection);
2911
- }
2912
- },
2913
- {
2914
- id: "unmerge",
2915
- label: "Unmerge cells",
2916
- disabled: !hasMerge,
2917
- run: () => {
2918
- controller.unmergeCells(selection);
2919
- }
2920
- },
2921
- {
2922
- id: "transpose",
2923
- label: "Transpose",
2924
- disabled: isSingleCellRange(selection),
2925
- run: () => {
2926
- controller.transposeRange(selection);
2927
- }
2928
- }
2929
- ];
2930
- if (grouping) {
2931
- items.push(
2932
- {
2933
- id: "ungroup",
2934
- label: "Remove categories",
2935
- separatorBefore: true,
2936
- run: () => controller.ungroupRows()
2937
- },
2938
- ...["sum", "count", "average"].map((aggregate) => ({
2939
- id: `summary-${aggregate}`,
2940
- label: `Summarize column ${colName}: ${aggregate.toUpperCase()}`,
2941
- run: () => controller.setGroupSummary(column.id, aggregate)
2942
- }))
2943
- );
2944
- } else {
2945
- items.push({
2946
- id: "group-by",
2947
- label: `Categorize by column ${colName}`,
2948
- separatorBefore: true,
2949
- run: () => controller.groupRowsBy(column.id)
2950
- });
2951
- }
2952
- return items;
2953
- }
2954
-
2955
- // src/spreadsheet/actions.ts
2956
- var SPREADSHEET_TABLE_ACTIONS = [
2957
- "sortRows",
2958
- "getFilterState",
2959
- "setFilterRules",
2960
- "addFilterRule",
2961
- "updateFilterRule",
2962
- "removeFilterRule",
2963
- "clearFilters",
2964
- "setFiltersEnabled",
2965
- "toggleFilters",
2966
- "setHeaderRowCount",
2967
- "setFooterRowCount",
2968
- "setHeaderColumnCount",
2969
- "setFrozenRowCount",
2970
- "setFrozenColumnCount",
2971
- "hideRows",
2972
- "unhideRows",
2973
- "hideColumns",
2974
- "unhideColumns",
2975
- "unhideAll",
2976
- "getMerges",
2977
- "getMergeAt",
2978
- "mergeCells",
2979
- "unmergeCells",
2980
- "autofill",
2981
- "groupRowsBy",
2982
- "ungroupRows",
2983
- "setGroupSummary",
2984
- "setGroupCollapsed",
2985
- "toggleGroupCollapsed",
2986
- "getGroups",
2987
- "transposeRange",
2988
- "exportToCsv",
2989
- "importFromCsv",
2990
- "copyToClipboardData",
2991
- "pasteClipboardData"
2992
- ];
2993
- function bindSpreadsheetTableActions(controller) {
2994
- const actions = {};
2995
- for (const name of SPREADSHEET_TABLE_ACTIONS) {
2996
- actions[name] = controller[name].bind(controller);
2997
- }
2998
- return actions;
2999
- }
3000
-
3001
- // src/spreadsheet/import.ts
3002
- function delimitedAdapter(id, label, delimiter, mimeTypes, extensions) {
3003
- return {
3004
- id,
3005
- label,
3006
- mimeTypes,
3007
- extensions,
3008
- target: "spreadsheet",
3009
- async import(_input, context) {
3010
- return { rows: parseCsv(await context.readText(), { delimiter }), delimiter };
3011
- }
3012
- };
3013
- }
3014
- var csvImportAdapter = delimitedAdapter(
3015
- "csv",
3016
- "CSV",
3017
- ",",
3018
- ["text/csv", "application/csv", "text/comma-separated-values"],
3019
- [".csv"]
3020
- );
3021
- var tsvImportAdapter = delimitedAdapter(
3022
- "tsv",
3023
- "Tab-separated values",
3024
- " ",
3025
- ["text/tab-separated-values"],
3026
- [".tsv", ".tab"]
3027
- );
3028
- var SPREADSHEET_IMPORT_ADAPTERS = [
3029
- csvImportAdapter,
3030
- tsvImportAdapter
3031
- ];
3032
- function registerSpreadsheetImportAdapters(registry) {
3033
- const unregisters = SPREADSHEET_IMPORT_ADAPTERS.map(
3034
- (adapter) => registry.register(adapter, { replace: true })
3035
- );
3036
- return () => unregisters.forEach((unregister) => unregister());
3037
- }
3038
- async function importFileIntoSpreadsheet(controller, input, options) {
3039
- const { registry, startRow, startCol, ...rest } = options;
3040
- const outcome = await registry.importFile(input, { ...rest, target: "spreadsheet" });
3041
- controller.importRows(outcome.result.rows, { startRow, startCol });
3042
- return outcome;
3043
- }
3044
-
3045
-
3046
-
3047
-
3048
-
3049
-
3050
-
3051
-
3052
-
3053
-
3054
-
3055
-
3056
-
3057
-
3058
-
3059
-
3060
-
3061
-
3062
-
3063
-
3064
-
3065
-
3066
-
3067
-
3068
-
3069
-
3070
-
3071
-
3072
-
3073
-
3074
-
3075
-
3076
-
3077
-
3078
-
3079
-
3080
-
3081
-
3082
-
3083
-
3084
-
3085
-
3086
-
3087
-
3088
-
3089
-
3090
-
3091
-
3092
-
3093
-
3094
-
3095
-
3096
-
3097
-
3098
-
3099
- exports.columnDependencyKey = columnDependencyKey; exports.extractCellDependencies = extractCellDependencies; exports.DependencyGraph = DependencyGraph; exports.shiftFormulaReferences = shiftFormulaReferences; exports.mapFormulaReferences = mapFormulaReferences; exports.offsetFormulaReferences = offsetFormulaReferences; exports.remapFormulaReferences = remapFormulaReferences; exports.getFormulaCaretContext = getFormulaCaretContext; exports.getFormulaHints = getFormulaHints; exports.applyFormulaSuggestion = applyFormulaSuggestion; exports.DataSourceBinder = DataSourceBinder; exports.normalizeRange = normalizeRange; exports.rangeContains = rangeContains; exports.rangesIntersect = rangesIntersect; exports.rangeContainsRange = rangeContainsRange; exports.isSingleCellRange = isSingleCellRange; exports.isBlankValue = isBlankValue; exports.toNumber = toNumber; exports.compareCellValues = compareCellValues; exports.matchesFilterRule = matchesFilterRule; exports.aggregateValues = aggregateValues; exports.roundFloat = roundFloat; exports.fillSeries = fillSeries; exports.computeFillTarget = computeFillTarget; exports.getFillDirection = getFillDirection; exports.parseCsv = parseCsv; exports.escapeCsvField = escapeCsvField; exports.serializeCsv = serializeCsv; exports.parseTsv = parseTsv; exports.serializeTsv = serializeTsv; exports.SPREADSHEET_CLIPBOARD_MIME = SPREADSHEET_CLIPBOARD_MIME; exports.serializeHtmlTable = serializeHtmlTable; exports.parseHtmlTable = parseHtmlTable; exports.createSpreadsheetClipboardData = createSpreadsheetClipboardData; exports.readSpreadsheetClipboard = readSpreadsheetClipboard; exports.rebaseClipboardRows = rebaseClipboardRows; exports.DEFAULT_ROW_HEIGHT = DEFAULT_ROW_HEIGHT; exports.DEFAULT_COLUMN_WIDTH = DEFAULT_COLUMN_WIDTH; exports.getTableSections = getTableSections; exports.getRowSection = getRowSection; exports.rowPassesFilters = rowPassesFilters; exports.computeRowGroups = computeRowGroups; exports.buildSpreadsheetView = buildSpreadsheetView; exports.SpreadsheetController = SpreadsheetController; exports.clampMenuPosition = clampMenuPosition; exports.getSpreadsheetMenuItems = getSpreadsheetMenuItems; exports.SPREADSHEET_TABLE_ACTIONS = SPREADSHEET_TABLE_ACTIONS; exports.bindSpreadsheetTableActions = bindSpreadsheetTableActions; exports.csvImportAdapter = csvImportAdapter; exports.tsvImportAdapter = tsvImportAdapter; exports.SPREADSHEET_IMPORT_ADAPTERS = SPREADSHEET_IMPORT_ADAPTERS; exports.registerSpreadsheetImportAdapters = registerSpreadsheetImportAdapters; exports.importFileIntoSpreadsheet = importFileIntoSpreadsheet;
3100
- //# sourceMappingURL=chunk-XDMXXC4Z.cjs.map