@pixerate/editor 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-NNJRJJSC.js → chunk-AIZUKWTL.js} +542 -117
- package/dist/chunk-AIZUKWTL.js.map +1 -0
- package/dist/{chunk-CYKQLDYH.cjs → chunk-YQCI4CCN.cjs} +577 -152
- package/dist/chunk-YQCI4CCN.cjs.map +1 -0
- package/dist/index.cjs +12 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/spreadsheet/index.cjs +12 -2
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +76 -3
- package/dist/spreadsheet/index.d.ts +76 -3
- package/dist/spreadsheet/index.js +11 -1
- package/package.json +1 -1
- package/dist/chunk-CYKQLDYH.cjs.map +0 -1
- package/dist/chunk-NNJRJJSC.js.map +0 -1
|
@@ -72,7 +72,7 @@ interface SpreadsheetDataSource {
|
|
|
72
72
|
updateRecord(recordId: string, field: string, value: any): Promise<void> | void;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
type FormulaTokenType = 'NUMBER' | 'STRING' | 'BOOLEAN' | 'CELL_REF' | 'RANGE' | 'PROPERTY_REF' | 'IDENTIFIER' | 'OPERATOR' | 'LPAREN' | 'RPAREN' | 'COMMA' | 'EOF';
|
|
75
|
+
type FormulaTokenType = 'NUMBER' | 'STRING' | 'BOOLEAN' | 'ERROR' | 'CELL_REF' | 'RANGE' | 'PROPERTY_REF' | 'IDENTIFIER' | 'OPERATOR' | 'LPAREN' | 'RPAREN' | 'COMMA' | 'EOF';
|
|
76
76
|
interface FormulaToken {
|
|
77
77
|
type: FormulaTokenType;
|
|
78
78
|
value: string;
|
|
@@ -80,6 +80,8 @@ interface FormulaToken {
|
|
|
80
80
|
}
|
|
81
81
|
declare function tokenizeFormula(formula: string): FormulaToken[];
|
|
82
82
|
|
|
83
|
+
/** Row number used for the open end of whole-column ranges such as `A:A`. */
|
|
84
|
+
declare const WHOLE_COLUMN_END_ROW = 1048576;
|
|
83
85
|
type ASTNode = LiteralNode | CellRefNode | RangeNode | PropertyRefNode | BinaryOpNode | UnaryOpNode | FunctionCallNode;
|
|
84
86
|
interface LiteralNode {
|
|
85
87
|
type: 'Literal';
|
|
@@ -153,6 +155,9 @@ interface EvaluationContext {
|
|
|
153
155
|
resolveRange?: (startCol: string, startRow: number, endCol: string, endRow: number) => any[];
|
|
154
156
|
resolveProperty?: (property: string) => any;
|
|
155
157
|
}
|
|
158
|
+
/** Spreadsheet error values. They propagate through operators and functions. */
|
|
159
|
+
declare const FORMULA_ERRORS: Set<string>;
|
|
160
|
+
declare function isFormulaError(value: unknown): value is string;
|
|
156
161
|
declare function colNameToIndex(col: string): number;
|
|
157
162
|
declare function indexToColName(index: number): string;
|
|
158
163
|
declare class FormulaEvaluator {
|
|
@@ -167,11 +172,18 @@ interface CellDep {
|
|
|
167
172
|
col: string;
|
|
168
173
|
row: number;
|
|
169
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Dependency key for "every cell in column `col`", used by whole-column ranges
|
|
177
|
+
* (`A:A`) instead of expanding a million cell keys.
|
|
178
|
+
*/
|
|
179
|
+
declare const columnDependencyKey: (col: string) => string;
|
|
170
180
|
declare function extractCellDependencies(node: ASTNode): CellDep[];
|
|
171
181
|
declare class DependencyGraph {
|
|
172
182
|
private dependencies;
|
|
173
183
|
private dependents;
|
|
174
184
|
setCellFormula(cellKey: string, formulaStr: string | null): void;
|
|
185
|
+
/** Removes every registered formula and dependency. */
|
|
186
|
+
clear(): void;
|
|
175
187
|
getDependencies(cellKey: string): string[];
|
|
176
188
|
getDependents(cellKey: string): string[];
|
|
177
189
|
/**
|
|
@@ -185,8 +197,39 @@ declare class DependencyGraph {
|
|
|
185
197
|
order: string[];
|
|
186
198
|
hasCycle: boolean;
|
|
187
199
|
};
|
|
200
|
+
/**
|
|
201
|
+
* Plans a recalculation over `cellKeys` (and, with `includeDependents`, every
|
|
202
|
+
* cell that transitively depends on them). Returns the keys in evaluation
|
|
203
|
+
* order (dependencies first) and the set of keys that are part of a cycle,
|
|
204
|
+
* which must be marked `#CYCLE!` instead of evaluated. Every member of a
|
|
205
|
+
* cycle is reported, not just the cell that closed it.
|
|
206
|
+
*/
|
|
207
|
+
getEvaluationPlan(cellKeys: Iterable<string>, options?: {
|
|
208
|
+
includeDependents?: boolean;
|
|
209
|
+
}): {
|
|
210
|
+
order: string[];
|
|
211
|
+
cyclic: Set<string>;
|
|
212
|
+
};
|
|
188
213
|
}
|
|
189
214
|
|
|
215
|
+
/**
|
|
216
|
+
* A row or column insertion/deletion. `index` is the 0-based position of the
|
|
217
|
+
* inserted or deleted row/column.
|
|
218
|
+
*/
|
|
219
|
+
interface StructuralChange {
|
|
220
|
+
axis: 'row' | 'col';
|
|
221
|
+
index: number;
|
|
222
|
+
type: 'insert' | 'delete';
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Rewrites the cell and range references in `formula` after a row or column
|
|
226
|
+
* is inserted or deleted, the way spreadsheet apps do: references after the
|
|
227
|
+
* change shift, ranges grow or shrink, and references to a deleted row/column
|
|
228
|
+
* become `#REF!`. Absolute (`$`) markers are kept. String literals, property
|
|
229
|
+
* references and function names are left untouched.
|
|
230
|
+
*/
|
|
231
|
+
declare function shiftFormulaReferences(formula: string, change: StructuralChange): string;
|
|
232
|
+
|
|
190
233
|
declare class DataSourceBinder {
|
|
191
234
|
private dataSources;
|
|
192
235
|
register(dataSource: SpreadsheetDataSource): void;
|
|
@@ -230,6 +273,9 @@ declare class SpreadsheetController {
|
|
|
230
273
|
private undoStack;
|
|
231
274
|
private redoStack;
|
|
232
275
|
private isApplyingHistory;
|
|
276
|
+
private batching;
|
|
277
|
+
private pendingCommits;
|
|
278
|
+
private evaluatingRowIndex;
|
|
233
279
|
constructor(options?: SpreadsheetControllerOptions);
|
|
234
280
|
loadDocument(document: SpreadsheetDocument): void;
|
|
235
281
|
registerDataSource(ds: SpreadsheetDataSource): void;
|
|
@@ -239,10 +285,23 @@ declare class SpreadsheetController {
|
|
|
239
285
|
getColumn(colIdOrKey: string): SpreadsheetColumn | undefined;
|
|
240
286
|
getRow(rowIdOrIndex: string | number): SpreadsheetRow | undefined;
|
|
241
287
|
getCell(rowId: string, colId: string): CellData;
|
|
288
|
+
/** Value of a cell with no stored data: bound record value, column formula, or blank. */
|
|
289
|
+
private getDerivedCell;
|
|
242
290
|
setCellValue(rowId: string, colId: string, raw: string): void;
|
|
243
291
|
private getCellKey;
|
|
292
|
+
/**
|
|
293
|
+
* Recalculates a cell and everything that depends on it, in dependency
|
|
294
|
+
* order. Every cell in a reference cycle is marked `#CYCLE!`.
|
|
295
|
+
*/
|
|
244
296
|
recalculateCell(rowId: string, colId: string): void;
|
|
297
|
+
/**
|
|
298
|
+
* Rebuilds the dependency graph from scratch and recalculates every formula
|
|
299
|
+
* in dependency order.
|
|
300
|
+
*/
|
|
245
301
|
recalculateAll(): void;
|
|
302
|
+
private runEvaluationPlan;
|
|
303
|
+
private locateCellKey;
|
|
304
|
+
private markCycle;
|
|
246
305
|
private evalSingleCellByKey;
|
|
247
306
|
private resolveCellByCoord;
|
|
248
307
|
private resolveRangeByCoords;
|
|
@@ -257,9 +316,23 @@ declare class SpreadsheetController {
|
|
|
257
316
|
deleteColumn(colId: string): void;
|
|
258
317
|
insertRow(index: number, config?: Partial<SpreadsheetRow>): void;
|
|
259
318
|
deleteRow(rowId: string): void;
|
|
319
|
+
private generateId;
|
|
320
|
+
/**
|
|
321
|
+
* Rewrites references in every cell formula (and, for column changes, every
|
|
322
|
+
* column formula) so they keep pointing at the same cells.
|
|
323
|
+
*/
|
|
324
|
+
private shiftReferences;
|
|
260
325
|
setColumnWidth(colId: string, width: number): void;
|
|
261
326
|
autoFitColumnWidth(colId: string, customWidth?: number): void;
|
|
262
|
-
|
|
327
|
+
/**
|
|
328
|
+
* Exports cell values as TSV: the whole sheet, or only `range` (e.g. the
|
|
329
|
+
* current selection) when given.
|
|
330
|
+
*/
|
|
331
|
+
exportToTsv(range?: CellRange | null): string;
|
|
332
|
+
/**
|
|
333
|
+
* Pastes tab-separated values starting at (startRow, startCol), adding rows
|
|
334
|
+
* and columns as needed. The whole paste is a single undo step.
|
|
335
|
+
*/
|
|
263
336
|
importFromTsv(tsvData: string, startRow?: number, startCol?: number): void;
|
|
264
337
|
private recordSnapshot;
|
|
265
338
|
undo(): void;
|
|
@@ -267,4 +340,4 @@ declare class SpreadsheetController {
|
|
|
267
340
|
private notifyChange;
|
|
268
341
|
}
|
|
269
342
|
|
|
270
|
-
export { type ASTNode, type BinaryOpNode, type CellCoordinate, type CellData, type CellDep, type CellFormat, type CellRange, type CellRefNode, type ColumnBinding, type ColumnType, DataSourceBinder, DependencyGraph, type EvaluationContext, FormulaEvaluator, FormulaParser, type FormulaToken, type FormulaTokenType, type FunctionCallNode, type LiteralNode, type PropertyRefNode, type RangeNode, type RowType, type SpreadsheetColumn, SpreadsheetController, type SpreadsheetControllerOptions, type SpreadsheetDataSource, type SpreadsheetDataSourceField, type SpreadsheetDocument, type SpreadsheetRow, type UnaryOpNode, colNameToIndex, extractCellDependencies, indexToColName, parseCellReference, parseFormula, parseRange, tokenizeFormula };
|
|
343
|
+
export { type ASTNode, type BinaryOpNode, type CellCoordinate, type CellData, type CellDep, type CellFormat, type CellRange, type CellRefNode, type ColumnBinding, type ColumnType, DataSourceBinder, DependencyGraph, type EvaluationContext, FORMULA_ERRORS, FormulaEvaluator, FormulaParser, type FormulaToken, type FormulaTokenType, type FunctionCallNode, type LiteralNode, type PropertyRefNode, type RangeNode, type RowType, type SpreadsheetColumn, SpreadsheetController, type SpreadsheetControllerOptions, type SpreadsheetDataSource, type SpreadsheetDataSourceField, type SpreadsheetDocument, type SpreadsheetRow, type StructuralChange, type UnaryOpNode, WHOLE_COLUMN_END_ROW, colNameToIndex, columnDependencyKey, extractCellDependencies, indexToColName, isFormulaError, parseCellReference, parseFormula, parseRange, shiftFormulaReferences, tokenizeFormula };
|
|
@@ -72,7 +72,7 @@ interface SpreadsheetDataSource {
|
|
|
72
72
|
updateRecord(recordId: string, field: string, value: any): Promise<void> | void;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
type FormulaTokenType = 'NUMBER' | 'STRING' | 'BOOLEAN' | 'CELL_REF' | 'RANGE' | 'PROPERTY_REF' | 'IDENTIFIER' | 'OPERATOR' | 'LPAREN' | 'RPAREN' | 'COMMA' | 'EOF';
|
|
75
|
+
type FormulaTokenType = 'NUMBER' | 'STRING' | 'BOOLEAN' | 'ERROR' | 'CELL_REF' | 'RANGE' | 'PROPERTY_REF' | 'IDENTIFIER' | 'OPERATOR' | 'LPAREN' | 'RPAREN' | 'COMMA' | 'EOF';
|
|
76
76
|
interface FormulaToken {
|
|
77
77
|
type: FormulaTokenType;
|
|
78
78
|
value: string;
|
|
@@ -80,6 +80,8 @@ interface FormulaToken {
|
|
|
80
80
|
}
|
|
81
81
|
declare function tokenizeFormula(formula: string): FormulaToken[];
|
|
82
82
|
|
|
83
|
+
/** Row number used for the open end of whole-column ranges such as `A:A`. */
|
|
84
|
+
declare const WHOLE_COLUMN_END_ROW = 1048576;
|
|
83
85
|
type ASTNode = LiteralNode | CellRefNode | RangeNode | PropertyRefNode | BinaryOpNode | UnaryOpNode | FunctionCallNode;
|
|
84
86
|
interface LiteralNode {
|
|
85
87
|
type: 'Literal';
|
|
@@ -153,6 +155,9 @@ interface EvaluationContext {
|
|
|
153
155
|
resolveRange?: (startCol: string, startRow: number, endCol: string, endRow: number) => any[];
|
|
154
156
|
resolveProperty?: (property: string) => any;
|
|
155
157
|
}
|
|
158
|
+
/** Spreadsheet error values. They propagate through operators and functions. */
|
|
159
|
+
declare const FORMULA_ERRORS: Set<string>;
|
|
160
|
+
declare function isFormulaError(value: unknown): value is string;
|
|
156
161
|
declare function colNameToIndex(col: string): number;
|
|
157
162
|
declare function indexToColName(index: number): string;
|
|
158
163
|
declare class FormulaEvaluator {
|
|
@@ -167,11 +172,18 @@ interface CellDep {
|
|
|
167
172
|
col: string;
|
|
168
173
|
row: number;
|
|
169
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Dependency key for "every cell in column `col`", used by whole-column ranges
|
|
177
|
+
* (`A:A`) instead of expanding a million cell keys.
|
|
178
|
+
*/
|
|
179
|
+
declare const columnDependencyKey: (col: string) => string;
|
|
170
180
|
declare function extractCellDependencies(node: ASTNode): CellDep[];
|
|
171
181
|
declare class DependencyGraph {
|
|
172
182
|
private dependencies;
|
|
173
183
|
private dependents;
|
|
174
184
|
setCellFormula(cellKey: string, formulaStr: string | null): void;
|
|
185
|
+
/** Removes every registered formula and dependency. */
|
|
186
|
+
clear(): void;
|
|
175
187
|
getDependencies(cellKey: string): string[];
|
|
176
188
|
getDependents(cellKey: string): string[];
|
|
177
189
|
/**
|
|
@@ -185,8 +197,39 @@ declare class DependencyGraph {
|
|
|
185
197
|
order: string[];
|
|
186
198
|
hasCycle: boolean;
|
|
187
199
|
};
|
|
200
|
+
/**
|
|
201
|
+
* Plans a recalculation over `cellKeys` (and, with `includeDependents`, every
|
|
202
|
+
* cell that transitively depends on them). Returns the keys in evaluation
|
|
203
|
+
* order (dependencies first) and the set of keys that are part of a cycle,
|
|
204
|
+
* which must be marked `#CYCLE!` instead of evaluated. Every member of a
|
|
205
|
+
* cycle is reported, not just the cell that closed it.
|
|
206
|
+
*/
|
|
207
|
+
getEvaluationPlan(cellKeys: Iterable<string>, options?: {
|
|
208
|
+
includeDependents?: boolean;
|
|
209
|
+
}): {
|
|
210
|
+
order: string[];
|
|
211
|
+
cyclic: Set<string>;
|
|
212
|
+
};
|
|
188
213
|
}
|
|
189
214
|
|
|
215
|
+
/**
|
|
216
|
+
* A row or column insertion/deletion. `index` is the 0-based position of the
|
|
217
|
+
* inserted or deleted row/column.
|
|
218
|
+
*/
|
|
219
|
+
interface StructuralChange {
|
|
220
|
+
axis: 'row' | 'col';
|
|
221
|
+
index: number;
|
|
222
|
+
type: 'insert' | 'delete';
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Rewrites the cell and range references in `formula` after a row or column
|
|
226
|
+
* is inserted or deleted, the way spreadsheet apps do: references after the
|
|
227
|
+
* change shift, ranges grow or shrink, and references to a deleted row/column
|
|
228
|
+
* become `#REF!`. Absolute (`$`) markers are kept. String literals, property
|
|
229
|
+
* references and function names are left untouched.
|
|
230
|
+
*/
|
|
231
|
+
declare function shiftFormulaReferences(formula: string, change: StructuralChange): string;
|
|
232
|
+
|
|
190
233
|
declare class DataSourceBinder {
|
|
191
234
|
private dataSources;
|
|
192
235
|
register(dataSource: SpreadsheetDataSource): void;
|
|
@@ -230,6 +273,9 @@ declare class SpreadsheetController {
|
|
|
230
273
|
private undoStack;
|
|
231
274
|
private redoStack;
|
|
232
275
|
private isApplyingHistory;
|
|
276
|
+
private batching;
|
|
277
|
+
private pendingCommits;
|
|
278
|
+
private evaluatingRowIndex;
|
|
233
279
|
constructor(options?: SpreadsheetControllerOptions);
|
|
234
280
|
loadDocument(document: SpreadsheetDocument): void;
|
|
235
281
|
registerDataSource(ds: SpreadsheetDataSource): void;
|
|
@@ -239,10 +285,23 @@ declare class SpreadsheetController {
|
|
|
239
285
|
getColumn(colIdOrKey: string): SpreadsheetColumn | undefined;
|
|
240
286
|
getRow(rowIdOrIndex: string | number): SpreadsheetRow | undefined;
|
|
241
287
|
getCell(rowId: string, colId: string): CellData;
|
|
288
|
+
/** Value of a cell with no stored data: bound record value, column formula, or blank. */
|
|
289
|
+
private getDerivedCell;
|
|
242
290
|
setCellValue(rowId: string, colId: string, raw: string): void;
|
|
243
291
|
private getCellKey;
|
|
292
|
+
/**
|
|
293
|
+
* Recalculates a cell and everything that depends on it, in dependency
|
|
294
|
+
* order. Every cell in a reference cycle is marked `#CYCLE!`.
|
|
295
|
+
*/
|
|
244
296
|
recalculateCell(rowId: string, colId: string): void;
|
|
297
|
+
/**
|
|
298
|
+
* Rebuilds the dependency graph from scratch and recalculates every formula
|
|
299
|
+
* in dependency order.
|
|
300
|
+
*/
|
|
245
301
|
recalculateAll(): void;
|
|
302
|
+
private runEvaluationPlan;
|
|
303
|
+
private locateCellKey;
|
|
304
|
+
private markCycle;
|
|
246
305
|
private evalSingleCellByKey;
|
|
247
306
|
private resolveCellByCoord;
|
|
248
307
|
private resolveRangeByCoords;
|
|
@@ -257,9 +316,23 @@ declare class SpreadsheetController {
|
|
|
257
316
|
deleteColumn(colId: string): void;
|
|
258
317
|
insertRow(index: number, config?: Partial<SpreadsheetRow>): void;
|
|
259
318
|
deleteRow(rowId: string): void;
|
|
319
|
+
private generateId;
|
|
320
|
+
/**
|
|
321
|
+
* Rewrites references in every cell formula (and, for column changes, every
|
|
322
|
+
* column formula) so they keep pointing at the same cells.
|
|
323
|
+
*/
|
|
324
|
+
private shiftReferences;
|
|
260
325
|
setColumnWidth(colId: string, width: number): void;
|
|
261
326
|
autoFitColumnWidth(colId: string, customWidth?: number): void;
|
|
262
|
-
|
|
327
|
+
/**
|
|
328
|
+
* Exports cell values as TSV: the whole sheet, or only `range` (e.g. the
|
|
329
|
+
* current selection) when given.
|
|
330
|
+
*/
|
|
331
|
+
exportToTsv(range?: CellRange | null): string;
|
|
332
|
+
/**
|
|
333
|
+
* Pastes tab-separated values starting at (startRow, startCol), adding rows
|
|
334
|
+
* and columns as needed. The whole paste is a single undo step.
|
|
335
|
+
*/
|
|
263
336
|
importFromTsv(tsvData: string, startRow?: number, startCol?: number): void;
|
|
264
337
|
private recordSnapshot;
|
|
265
338
|
undo(): void;
|
|
@@ -267,4 +340,4 @@ declare class SpreadsheetController {
|
|
|
267
340
|
private notifyChange;
|
|
268
341
|
}
|
|
269
342
|
|
|
270
|
-
export { type ASTNode, type BinaryOpNode, type CellCoordinate, type CellData, type CellDep, type CellFormat, type CellRange, type CellRefNode, type ColumnBinding, type ColumnType, DataSourceBinder, DependencyGraph, type EvaluationContext, FormulaEvaluator, FormulaParser, type FormulaToken, type FormulaTokenType, type FunctionCallNode, type LiteralNode, type PropertyRefNode, type RangeNode, type RowType, type SpreadsheetColumn, SpreadsheetController, type SpreadsheetControllerOptions, type SpreadsheetDataSource, type SpreadsheetDataSourceField, type SpreadsheetDocument, type SpreadsheetRow, type UnaryOpNode, colNameToIndex, extractCellDependencies, indexToColName, parseCellReference, parseFormula, parseRange, tokenizeFormula };
|
|
343
|
+
export { type ASTNode, type BinaryOpNode, type CellCoordinate, type CellData, type CellDep, type CellFormat, type CellRange, type CellRefNode, type ColumnBinding, type ColumnType, DataSourceBinder, DependencyGraph, type EvaluationContext, FORMULA_ERRORS, FormulaEvaluator, FormulaParser, type FormulaToken, type FormulaTokenType, type FunctionCallNode, type LiteralNode, type PropertyRefNode, type RangeNode, type RowType, type SpreadsheetColumn, SpreadsheetController, type SpreadsheetControllerOptions, type SpreadsheetDataSource, type SpreadsheetDataSourceField, type SpreadsheetDocument, type SpreadsheetRow, type StructuralChange, type UnaryOpNode, WHOLE_COLUMN_END_ROW, colNameToIndex, columnDependencyKey, extractCellDependencies, indexToColName, isFormulaError, parseCellReference, parseFormula, parseRange, shiftFormulaReferences, tokenizeFormula };
|
|
@@ -1,29 +1,39 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DataSourceBinder,
|
|
3
3
|
DependencyGraph,
|
|
4
|
+
FORMULA_ERRORS,
|
|
4
5
|
FormulaEvaluator,
|
|
5
6
|
FormulaParser,
|
|
6
7
|
SpreadsheetController,
|
|
8
|
+
WHOLE_COLUMN_END_ROW,
|
|
7
9
|
colNameToIndex,
|
|
10
|
+
columnDependencyKey,
|
|
8
11
|
extractCellDependencies,
|
|
9
12
|
indexToColName,
|
|
13
|
+
isFormulaError,
|
|
10
14
|
parseCellReference,
|
|
11
15
|
parseFormula,
|
|
12
16
|
parseRange,
|
|
17
|
+
shiftFormulaReferences,
|
|
13
18
|
tokenizeFormula
|
|
14
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-AIZUKWTL.js";
|
|
15
20
|
export {
|
|
16
21
|
DataSourceBinder,
|
|
17
22
|
DependencyGraph,
|
|
23
|
+
FORMULA_ERRORS,
|
|
18
24
|
FormulaEvaluator,
|
|
19
25
|
FormulaParser,
|
|
20
26
|
SpreadsheetController,
|
|
27
|
+
WHOLE_COLUMN_END_ROW,
|
|
21
28
|
colNameToIndex,
|
|
29
|
+
columnDependencyKey,
|
|
22
30
|
extractCellDependencies,
|
|
23
31
|
indexToColName,
|
|
32
|
+
isFormulaError,
|
|
24
33
|
parseCellReference,
|
|
25
34
|
parseFormula,
|
|
26
35
|
parseRange,
|
|
36
|
+
shiftFormulaReferences,
|
|
27
37
|
tokenizeFormula
|
|
28
38
|
};
|
|
29
39
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-CYKQLDYH.cjs","../src/spreadsheet/formula/lexer.ts","../src/spreadsheet/formula/parser.ts","../src/spreadsheet/formula/evaluator.ts","../src/spreadsheet/formula/dependency-graph.ts","../src/spreadsheet/datasource/index.ts","../src/spreadsheet/controller.ts"],"names":[],"mappings":"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","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-CYKQLDYH.cjs","sourcesContent":[null,"export type FormulaTokenType =\n | 'NUMBER'\n | 'STRING'\n | 'BOOLEAN'\n | 'CELL_REF'\n | 'RANGE'\n | 'PROPERTY_REF'\n | 'IDENTIFIER'\n | 'OPERATOR'\n | 'LPAREN'\n | 'RPAREN'\n | 'COMMA'\n | 'EOF';\n\nexport interface FormulaToken {\n type: FormulaTokenType;\n value: string;\n position: number;\n}\n\nexport function tokenizeFormula(formula: string): FormulaToken[] {\n const tokens: FormulaToken[] = [];\n // Strip leading '=' if present\n let input = formula.trim();\n let offset = 0;\n if (input.startsWith('=')) {\n input = input.slice(1);\n offset = 1;\n }\n\n let i = 0;\n while (i < input.length) {\n const char = input[i];\n\n // Skip whitespace\n if (/\\s/.test(char)) {\n i++;\n continue;\n }\n\n // Number literal (int or float)\n if (/\\d/.test(char) || (char === '.' && i + 1 < input.length && /\\d/.test(input[i + 1]))) {\n const start = i;\n let hasDot = char === '.';\n i++;\n while (i < input.length && (/\\d/.test(input[i]) || (input[i] === '.' && !hasDot))) {\n if (input[i] === '.') hasDot = true;\n i++;\n }\n tokens.push({\n type: 'NUMBER',\n value: input.slice(start, i),\n position: start + offset\n });\n continue;\n }\n\n // String literal (\"...\" or '...')\n if (char === '\"' || char === \"'\") {\n const quote = char;\n const start = i;\n i++;\n let str = '';\n while (i < input.length && input[i] !== quote) {\n if (input[i] === '\\\\' && i + 1 < input.length) {\n i++;\n }\n str += input[i];\n i++;\n }\n if (i < input.length && input[i] === quote) {\n i++; // skip closing quote\n }\n tokens.push({\n type: 'STRING',\n value: str,\n position: start + offset\n });\n continue;\n }\n\n // Property reference: [property_name] e.g. [status], [title], [effortHours]\n if (char === '[') {\n const start = i;\n i++;\n let prop = '';\n while (i < input.length && input[i] !== ']') {\n prop += input[i];\n i++;\n }\n if (i < input.length && input[i] === ']') {\n i++;\n }\n tokens.push({\n type: 'PROPERTY_REF',\n value: prop.trim(),\n position: start + offset\n });\n continue;\n }\n\n // Single character delimiters\n if (char === '(') {\n tokens.push({ type: 'LPAREN', value: '(', position: i + offset });\n i++;\n continue;\n }\n if (char === ')') {\n tokens.push({ type: 'RPAREN', value: ')', position: i + offset });\n i++;\n continue;\n }\n if (char === ',') {\n tokens.push({ type: 'COMMA', value: ',', position: i + offset });\n i++;\n continue;\n }\n\n // Multi-character comparison operators: <=, >=, <>, !=\n const twoChars = input.slice(i, i + 2);\n if (['<=', '>=', '<>', '!='].includes(twoChars)) {\n tokens.push({\n type: 'OPERATOR',\n value: twoChars === '!=' ? '<>' : twoChars,\n position: i + offset\n });\n i += 2;\n continue;\n }\n\n // Single character operators: +, -, *, /, ^, &, =, <, >\n if (['+', '-', '*', '/', '^', '&', '=', '<', '>'].includes(char)) {\n tokens.push({ type: 'OPERATOR', value: char, position: i + offset });\n i++;\n continue;\n }\n\n // Identifiers, Cell references, and Ranges (e.g. SUM, A1, B2:B10, A:A)\n if (/[a-zA-Z_$]/.test(char)) {\n const start = i;\n while (i < input.length && /[a-zA-Z0-9_$.]/.test(input[i])) {\n i++;\n }\n const rawWord = input.slice(start, i);\n\n // Check if followed by colon indicating a range e.g. A1:B10 or A:A\n if (i < input.length && input[i] === ':') {\n const colonPos = i;\n i++; // skip colon\n const secondStart = i;\n while (i < input.length && /[a-zA-Z0-9_$.]/.test(input[i])) {\n i++;\n }\n const secondWord = input.slice(secondStart, i);\n if (secondWord.length > 0) {\n tokens.push({\n type: 'RANGE',\n value: `${rawWord}:${secondWord}`.toUpperCase(),\n position: start + offset\n });\n continue;\n } else {\n // Revert back\n i = colonPos;\n }\n }\n\n // Check for boolean literal\n const upper = rawWord.toUpperCase();\n if (upper === 'TRUE' || upper === 'FALSE') {\n tokens.push({\n type: 'BOOLEAN',\n value: upper,\n position: start + offset\n });\n continue;\n }\n\n // Check for Cell Reference (e.g. A1, $B$12, AA100)\n if (/^\\$?[A-Za-z]+\\$?[0-9]+$/.test(rawWord)) {\n tokens.push({\n type: 'CELL_REF',\n value: upper.replace(/\\$/g, ''),\n position: start + offset\n });\n continue;\n }\n\n // Function or generic identifier\n tokens.push({\n type: 'IDENTIFIER',\n value: upper,\n position: start + offset\n });\n continue;\n }\n\n // Skip unknown character\n i++;\n }\n\n tokens.push({ type: 'EOF', value: '', position: input.length + offset });\n return tokens;\n}\n","import { FormulaToken, tokenizeFormula, FormulaTokenType } from './lexer';\n\nexport type ASTNode =\n | LiteralNode\n | CellRefNode\n | RangeNode\n | PropertyRefNode\n | BinaryOpNode\n | UnaryOpNode\n | FunctionCallNode;\n\nexport interface LiteralNode {\n type: 'Literal';\n value: number | string | boolean;\n}\n\nexport interface CellRefNode {\n type: 'CellRef';\n ref: string;\n col: string;\n row: number;\n}\n\nexport interface RangeNode {\n type: 'Range';\n raw: string;\n startCol: string;\n startRow: number;\n endCol: string;\n endRow: number;\n}\n\nexport interface PropertyRefNode {\n type: 'PropertyRef';\n property: string;\n}\n\nexport interface BinaryOpNode {\n type: 'BinaryOp';\n op: string;\n left: ASTNode;\n right: ASTNode;\n}\n\nexport interface UnaryOpNode {\n type: 'UnaryOp';\n op: string;\n argument: ASTNode;\n}\n\nexport interface FunctionCallNode {\n type: 'FunctionCall';\n name: string;\n args: ASTNode[];\n}\n\nexport function parseCellReference(ref: string): { col: string; row: number } {\n const match = ref.match(/^([A-Za-z]+)(\\d+)$/);\n if (!match) {\n throw new Error(`Invalid cell reference: ${ref}`);\n }\n return {\n col: match[1].toUpperCase(),\n row: parseInt(match[2], 10)\n };\n}\n\nexport function parseRange(rangeStr: string): RangeNode {\n const [start, end] = rangeStr.split(':');\n const startRef = parseCellReference(start);\n const endRef = parseCellReference(end);\n return {\n type: 'Range',\n raw: rangeStr.toUpperCase(),\n startCol: startRef.col,\n startRow: startRef.row,\n endCol: endRef.col,\n endRow: endRef.row\n };\n}\n\nexport class FormulaParser {\n private tokens: FormulaToken[] = [];\n private current = 0;\n\n constructor(tokens: FormulaToken[]) {\n this.tokens = tokens;\n }\n\n public parse(): ASTNode {\n if (this.peek().type === 'EOF') {\n return { type: 'Literal', value: '' };\n }\n const expr = this.expression();\n if (this.peek().type !== 'EOF') {\n throw new Error(`Unexpected token at position ${this.peek().position}: ${this.peek().value}`);\n }\n return expr;\n }\n\n private expression(): ASTNode {\n return this.comparison();\n }\n\n private comparison(): ASTNode {\n let expr = this.concatenation();\n\n while (\n this.matchOperator('=', '<>', '<', '>', '<=', '>=')\n ) {\n const op = this.previous().value;\n const right = this.concatenation();\n expr = { type: 'BinaryOp', op, left: expr, right };\n }\n\n return expr;\n }\n\n private concatenation(): ASTNode {\n let expr = this.addition();\n\n while (this.matchOperator('&')) {\n const op = this.previous().value;\n const right = this.addition();\n expr = { type: 'BinaryOp', op, left: expr, right };\n }\n\n return expr;\n }\n\n private addition(): ASTNode {\n let expr = this.multiplication();\n\n while (this.matchOperator('+', '-')) {\n const op = this.previous().value;\n const right = this.multiplication();\n expr = { type: 'BinaryOp', op, left: expr, right };\n }\n\n return expr;\n }\n\n private multiplication(): ASTNode {\n let expr = this.exponentiation();\n\n while (this.matchOperator('*', '/')) {\n const op = this.previous().value;\n const right = this.exponentiation();\n expr = { type: 'BinaryOp', op, left: expr, right };\n }\n\n return expr;\n }\n\n private exponentiation(): ASTNode {\n let expr = this.unary();\n\n while (this.matchOperator('^')) {\n const op = this.previous().value;\n const right = this.unary();\n expr = { type: 'BinaryOp', op, left: expr, right };\n }\n\n return expr;\n }\n\n private unary(): ASTNode {\n if (this.matchOperator('-', '+')) {\n const op = this.previous().value;\n const argument = this.unary();\n return { type: 'UnaryOp', op, argument };\n }\n return this.primary();\n }\n\n private primary(): ASTNode {\n const token = this.peek();\n\n if (token.type === 'NUMBER') {\n this.advance();\n return { type: 'Literal', value: parseFloat(token.value) };\n }\n\n if (token.type === 'STRING') {\n this.advance();\n return { type: 'Literal', value: token.value };\n }\n\n if (token.type === 'BOOLEAN') {\n this.advance();\n return { type: 'Literal', value: token.value === 'TRUE' };\n }\n\n if (token.type === 'CELL_REF') {\n this.advance();\n const parsed = parseCellReference(token.value);\n return {\n type: 'CellRef',\n ref: token.value,\n col: parsed.col,\n row: parsed.row\n };\n }\n\n if (token.type === 'RANGE') {\n this.advance();\n return parseRange(token.value);\n }\n\n if (token.type === 'PROPERTY_REF') {\n this.advance();\n return {\n type: 'PropertyRef',\n property: token.value\n };\n }\n\n // Function call e.g. SUM(...)\n if (token.type === 'IDENTIFIER') {\n const name = token.value;\n this.advance();\n\n if (this.check('LPAREN')) {\n this.consume('LPAREN', `Expected '(' after function ${name}`);\n const args: ASTNode[] = [];\n if (!this.check('RPAREN')) {\n do {\n args.push(this.expression());\n } while (this.match('COMMA'));\n }\n this.consume('RPAREN', `Expected ')' after function arguments`);\n return {\n type: 'FunctionCall',\n name,\n args\n };\n }\n\n // Standalone identifier treated as string literal or reference\n return { type: 'Literal', value: name };\n }\n\n if (this.match('LPAREN')) {\n const expr = this.expression();\n this.consume('RPAREN', `Expected ')' after expression`);\n return expr;\n }\n\n throw new Error(`Unexpected token at position ${token.position}: ${token.value || token.type}`);\n }\n\n private match(...types: string[]): boolean {\n for (const type of types) {\n if (this.check(type)) {\n this.advance();\n return true;\n }\n }\n return false;\n }\n\n private matchOperator(...ops: string[]): boolean {\n if (this.check('OPERATOR') && ops.includes(this.peek().value)) {\n this.advance();\n return true;\n }\n return false;\n }\n\n private check(type: string): boolean {\n if (this.isAtEnd()) return false;\n return this.peek().type === type;\n }\n\n private advance(): FormulaToken {\n if (!this.isAtEnd()) this.current++;\n return this.previous();\n }\n\n private isAtEnd(): boolean {\n return this.peek().type === 'EOF';\n }\n\n private peek(): FormulaToken {\n return this.tokens[this.current];\n }\n\n private previous(): FormulaToken {\n return this.tokens[this.current - 1];\n }\n\n private consume(type: string, message: string): FormulaToken {\n if (this.check(type)) return this.advance();\n throw new Error(message);\n }\n}\n\nexport function parseFormula(formula: string): ASTNode {\n const tokens = tokenizeFormula(formula);\n const parser = new FormulaParser(tokens);\n return parser.parse();\n}\n","import { ASTNode, parseFormula } from './parser';\n\nexport interface EvaluationContext {\n resolveCell?: (col: string, row: number) => any;\n resolveRange?: (startCol: string, startRow: number, endCol: string, endRow: number) => any[];\n resolveProperty?: (property: string) => any;\n}\n\nexport function colNameToIndex(col: string): number {\n let index = 0;\n const upper = col.toUpperCase();\n for (let i = 0; i < upper.length; i++) {\n index = index * 26 + (upper.charCodeAt(i) - 65 + 1);\n }\n return index - 1;\n}\n\nexport function indexToColName(index: number): string {\n let num = index + 1;\n let name = '';\n while (num > 0) {\n const mod = (num - 1) % 26;\n name = String.fromCharCode(65 + mod) + name;\n num = Math.floor((num - mod) / 26);\n }\n return name;\n}\n\nexport class FormulaEvaluator {\n private context: EvaluationContext;\n\n constructor(context: EvaluationContext = {}) {\n this.context = context;\n }\n\n public evaluate(astOrFormula: ASTNode | string): any {\n const ast = typeof astOrFormula === 'string' ? parseFormula(astOrFormula) : astOrFormula;\n return this.evaluateNode(ast);\n }\n\n private evaluateNode(node: ASTNode): any {\n switch (node.type) {\n case 'Literal':\n return node.value;\n\n case 'CellRef': {\n if (!this.context.resolveCell) {\n throw new Error(`Cannot resolve cell reference ${node.ref}: no cell resolver provided`);\n }\n return this.context.resolveCell(node.col, node.row);\n }\n\n case 'Range': {\n if (!this.context.resolveRange) {\n throw new Error(`Cannot resolve range ${node.raw}: no range resolver provided`);\n }\n return this.context.resolveRange(node.startCol, node.startRow, node.endCol, node.endRow);\n }\n\n case 'PropertyRef': {\n if (!this.context.resolveProperty) {\n throw new Error(`Cannot resolve property [${node.property}]: no property resolver provided`);\n }\n return this.context.resolveProperty(node.property);\n }\n\n case 'UnaryOp': {\n const val = this.evaluateNode(node.argument);\n if (node.op === '-') return -Number(val);\n if (node.op === '+') return +Number(val);\n throw new Error(`Unsupported unary operator: ${node.op}`);\n }\n\n case 'BinaryOp': {\n const left = this.evaluateNode(node.left);\n const right = this.evaluateNode(node.right);\n\n switch (node.op) {\n case '+':\n return Number(left) + Number(right);\n case '-':\n return Number(left) - Number(right);\n case '*':\n return Number(left) * Number(right);\n case '/': {\n const denom = Number(right);\n if (denom === 0) return '#DIV/0!';\n return Number(left) / denom;\n }\n case '^':\n return Math.pow(Number(left), Number(right));\n case '&':\n return `${left ?? ''}${right ?? ''}`;\n case '=':\n // Relaxed equality\n return String(left).toLowerCase() === String(right).toLowerCase();\n case '<>':\n return String(left).toLowerCase() !== String(right).toLowerCase();\n case '<':\n return Number(left) < Number(right);\n case '<=':\n return Number(left) <= Number(right);\n case '>':\n return Number(left) > Number(right);\n case '>=':\n return Number(left) >= Number(right);\n default:\n throw new Error(`Unsupported binary operator: ${node.op}`);\n }\n }\n\n case 'FunctionCall': {\n return this.callFunction(node.name, node.args);\n }\n\n default:\n throw new Error(`Unknown AST node: ${(node as any).type}`);\n }\n }\n\n private callFunction(name: string, args: ASTNode[]): any {\n const fnName = name.toUpperCase();\n\n // Flatten helper for range/array args\n const evaluateAndFlatten = (): any[] => {\n const items: any[] = [];\n for (const arg of args) {\n const val = this.evaluateNode(arg);\n if (Array.isArray(val)) {\n items.push(...val);\n } else {\n items.push(val);\n }\n }\n return items;\n };\n\n switch (fnName) {\n case 'SUM': {\n const items = evaluateAndFlatten();\n let total = 0;\n for (const item of items) {\n const num = Number(item);\n if (!isNaN(num)) total += num;\n }\n return total;\n }\n\n case 'AVERAGE': {\n const items = evaluateAndFlatten();\n let total = 0;\n let count = 0;\n for (const item of items) {\n const num = Number(item);\n if (!isNaN(num) && item !== '' && item !== null && item !== undefined) {\n total += num;\n count++;\n }\n }\n return count === 0 ? 0 : total / count;\n }\n\n case 'COUNT': {\n const items = evaluateAndFlatten();\n let count = 0;\n for (const item of items) {\n const num = Number(item);\n if (!isNaN(num) && item !== '' && item !== null && item !== undefined) {\n count++;\n }\n }\n return count;\n }\n\n case 'COUNTA': {\n const items = evaluateAndFlatten();\n let count = 0;\n for (const item of items) {\n if (item !== '' && item !== null && item !== undefined) {\n count++;\n }\n }\n return count;\n }\n\n case 'MIN': {\n const items = evaluateAndFlatten().map(Number).filter((n) => !isNaN(n));\n return items.length === 0 ? 0 : Math.min(...items);\n }\n\n case 'MAX': {\n const items = evaluateAndFlatten().map(Number).filter((n) => !isNaN(n));\n return items.length === 0 ? 0 : Math.max(...items);\n }\n\n case 'IF': {\n if (args.length < 2) throw new Error('IF requires at least 2 arguments');\n const condition = Boolean(this.evaluateNode(args[0]));\n if (condition) {\n return this.evaluateNode(args[1]);\n } else {\n return args.length > 2 ? this.evaluateNode(args[2]) : false;\n }\n }\n\n case 'CONCAT': {\n const items = evaluateAndFlatten();\n return items.map((i) => (i === null || i === undefined ? '' : String(i))).join('');\n }\n\n case 'UPPER': {\n if (args.length !== 1) throw new Error('UPPER requires 1 argument');\n const val = this.evaluateNode(args[0]);\n return String(val ?? '').toUpperCase();\n }\n\n case 'LOWER': {\n if (args.length !== 1) throw new Error('LOWER requires 1 argument');\n const val = this.evaluateNode(args[0]);\n return String(val ?? '').toLowerCase();\n }\n\n case 'TRIM': {\n if (args.length !== 1) throw new Error('TRIM requires 1 argument');\n const val = this.evaluateNode(args[0]);\n return String(val ?? '').trim();\n }\n\n case 'LEN': {\n if (args.length !== 1) throw new Error('LEN requires 1 argument');\n const val = this.evaluateNode(args[0]);\n return String(val ?? '').length;\n }\n\n case 'ROUND': {\n if (args.length < 1) throw new Error('ROUND requires at least 1 argument');\n const num = Number(this.evaluateNode(args[0]));\n const decimals = args.length > 1 ? Number(this.evaluateNode(args[1])) : 0;\n const factor = Math.pow(10, decimals);\n return Math.round(num * factor) / factor;\n }\n\n case 'ABS': {\n if (args.length !== 1) throw new Error('ABS requires 1 argument');\n return Math.abs(Number(this.evaluateNode(args[0])));\n }\n\n case 'TODAY': {\n return new Date().toISOString().split('T')[0];\n }\n\n case 'DAYS': {\n if (args.length !== 2) throw new Error('DAYS requires 2 arguments (end_date, start_date)');\n const d1 = new Date(this.evaluateNode(args[0]));\n const d2 = new Date(this.evaluateNode(args[1]));\n const diffTime = d1.getTime() - d2.getTime();\n return Math.round(diffTime / (1000 * 60 * 60 * 24));\n }\n\n default:\n throw new Error(`Unknown function: ${fnName}`);\n }\n }\n}\n","import { ASTNode, parseFormula } from './parser';\nimport { colNameToIndex, indexToColName } from './evaluator';\n\nexport interface CellDep {\n col: string;\n row: number;\n}\n\nexport function extractCellDependencies(node: ASTNode): CellDep[] {\n const deps: CellDep[] = [];\n\n const visit = (curr: ASTNode) => {\n if (curr.type === 'CellRef') {\n deps.push({ col: curr.col, row: curr.row });\n } else if (curr.type === 'Range') {\n const startC = colNameToIndex(curr.startCol);\n const endC = colNameToIndex(curr.endCol);\n const minC = Math.min(startC, endC);\n const maxC = Math.max(startC, endC);\n const minR = Math.min(curr.startRow, curr.endRow);\n const maxR = Math.max(curr.startRow, curr.endRow);\n\n for (let c = minC; c <= maxC; c++) {\n for (let r = minR; r <= maxR; r++) {\n deps.push({ col: indexToColName(c), row: r });\n }\n }\n } else if (curr.type === 'BinaryOp') {\n visit(curr.left);\n visit(curr.right);\n } else if (curr.type === 'UnaryOp') {\n visit(curr.argument);\n } else if (curr.type === 'FunctionCall') {\n curr.args.forEach(visit);\n }\n };\n\n visit(node);\n return deps;\n}\n\nexport class DependencyGraph {\n // Map of cellKey (e.g. \"B2\") -> Set of cellKeys that this cell depends ON\n private dependencies = new Map<string, Set<string>>();\n // Map of cellKey (e.g. \"A1\") -> Set of cellKeys that depend ON this cell (dependents)\n private dependents = new Map<string, Set<string>>();\n\n public setCellFormula(cellKey: string, formulaStr: string | null): void {\n const key = cellKey.toUpperCase();\n\n // Clear existing dependencies\n const oldDeps = this.dependencies.get(key) || new Set();\n for (const dep of oldDeps) {\n this.dependents.get(dep)?.delete(key);\n }\n this.dependencies.delete(key);\n\n if (!formulaStr || !formulaStr.startsWith('=')) {\n return;\n }\n\n try {\n const ast = parseFormula(formulaStr);\n const deps = extractCellDependencies(ast);\n const newDeps = new Set<string>();\n\n for (const d of deps) {\n const depKey = `${d.col}${d.row}`.toUpperCase();\n newDeps.add(depKey);\n\n if (!this.dependents.has(depKey)) {\n this.dependents.set(depKey, new Set());\n }\n this.dependents.get(depKey)!.add(key);\n }\n\n this.dependencies.set(key, newDeps);\n } catch {\n // Syntax errors in formula have no valid dependencies\n }\n }\n\n public getDependencies(cellKey: string): string[] {\n return Array.from(this.dependencies.get(cellKey.toUpperCase()) || []);\n }\n\n public getDependents(cellKey: string): string[] {\n return Array.from(this.dependents.get(cellKey.toUpperCase()) || []);\n }\n\n /**\n * Detects if there is a cycle containing cellKey.\n */\n public hasCycle(cellKey: string): boolean {\n const key = cellKey.toUpperCase();\n const visited = new Set<string>();\n const recStack = new Set<string>();\n\n const dfs = (node: string): boolean => {\n visited.add(node);\n recStack.add(node);\n\n const deps = this.dependencies.get(node) || new Set();\n for (const neighbor of deps) {\n if (!visited.has(neighbor)) {\n if (dfs(neighbor)) return true;\n } else if (recStack.has(neighbor)) {\n return true;\n }\n }\n\n recStack.delete(node);\n return false;\n };\n\n return dfs(key);\n }\n\n /**\n * Returns cells affected by updating cellKey in topological order.\n */\n public getEvaluationOrder(startCellKey: string): { order: string[]; hasCycle: boolean } {\n const start = startCellKey.toUpperCase();\n const visited = new Set<string>();\n const recStack = new Set<string>();\n const order: string[] = [];\n let cycleDetected = false;\n\n // Collect all downstream affected cells (reachability via dependents)\n const affected = new Set<string>();\n const collectAffected = (node: string) => {\n affected.add(node);\n const directDependents = this.dependents.get(node) || new Set();\n for (const dep of directDependents) {\n if (!affected.has(dep)) {\n collectAffected(dep);\n }\n }\n };\n collectAffected(start);\n\n // Topological sort among affected cells\n const visit = (node: string) => {\n if (recStack.has(node)) {\n cycleDetected = true;\n return;\n }\n if (visited.has(node)) return;\n\n recStack.add(node);\n visited.add(node);\n\n // Visit nodes that this node depends on, IF they are in the affected set\n const deps = this.dependencies.get(node) || new Set();\n for (const neighbor of deps) {\n if (affected.has(neighbor)) {\n visit(neighbor);\n }\n }\n\n recStack.delete(node);\n order.push(node);\n };\n\n for (const node of affected) {\n if (!visited.has(node)) {\n visit(node);\n }\n }\n\n return { order, hasCycle: cycleDetected };\n }\n}\n","import { SpreadsheetDataSource, SpreadsheetRow, SpreadsheetColumn } from '../types';\n\nexport class DataSourceBinder {\n private dataSources = new Map<string, SpreadsheetDataSource>();\n\n public register(dataSource: SpreadsheetDataSource): void {\n this.dataSources.set(dataSource.id, dataSource);\n }\n\n public unregister(dataSourceId: string): void {\n this.dataSources.delete(dataSourceId);\n }\n\n public getDataSource(id: string): SpreadsheetDataSource | undefined {\n return this.dataSources.get(id);\n }\n\n /**\n * Generates or reconciles spreadsheet rows from a primary data source.\n */\n public syncRows(\n currentRows: SpreadsheetRow[],\n dataSourceId: string\n ): SpreadsheetRow[] {\n const ds = this.dataSources.get(dataSourceId);\n if (!ds) return currentRows;\n\n const records = ds.getRecords();\n const existingRecordRowMap = new Map<string, SpreadsheetRow>();\n const freeformRows: SpreadsheetRow[] = [];\n\n for (const row of currentRows) {\n if (row.type === 'record' && row.recordId) {\n existingRecordRowMap.set(row.recordId, row);\n } else {\n freeformRows.push(row);\n }\n }\n\n const nextRows: SpreadsheetRow[] = [];\n let rowIndex = 0;\n\n for (const record of records) {\n const existing = existingRecordRowMap.get(record.id);\n nextRows.push({\n id: existing?.id || `row_${record.id}`,\n index: rowIndex++,\n height: existing?.height || 32,\n recordId: record.id,\n type: 'record'\n });\n existingRecordRowMap.delete(record.id);\n }\n\n // Append preserved freeform rows\n for (const freeform of freeformRows) {\n nextRows.push({\n ...freeform,\n index: rowIndex++\n });\n }\n\n return nextRows;\n }\n\n /**\n * Resolves a record's bound field value.\n */\n public resolveBoundValue(\n dataSourceId: string,\n recordId: string,\n field: string\n ): any {\n const ds = this.dataSources.get(dataSourceId);\n if (!ds) return undefined;\n\n const records = ds.getRecords();\n const record = records.find((r) => r.id === recordId);\n return record ? record[field] : undefined;\n }\n\n /**\n * Dispatches an update to the underlying data source.\n */\n public async commitBoundValue(\n dataSourceId: string,\n recordId: string,\n field: string,\n value: any\n ): Promise<void> {\n const ds = this.dataSources.get(dataSourceId);\n if (!ds) return;\n await ds.updateRecord(recordId, field, value);\n }\n}\n","import {\n SpreadsheetDocument,\n SpreadsheetColumn,\n SpreadsheetRow,\n CellCoordinate,\n CellRange,\n CellData,\n SpreadsheetDataSource\n} from './types';\nimport {\n FormulaEvaluator,\n colNameToIndex,\n indexToColName,\n DependencyGraph\n} from './formula';\nimport { DataSourceBinder } from './datasource';\n\nexport interface SpreadsheetControllerOptions {\n document?: Partial<SpreadsheetDocument>;\n dataSources?: SpreadsheetDataSource[];\n primaryDataSourceId?: string;\n onDocumentChange?: (doc: SpreadsheetDocument) => void;\n onSelectionChange?: (activeCell: CellCoordinate | null, range: CellRange | null) => void;\n onCellCommit?: (rowId: string, colId: string, raw: string, value: any) => void;\n}\n\nexport class SpreadsheetController {\n public document: SpreadsheetDocument;\n public activeCell: CellCoordinate | null = null;\n public selectedRange: CellRange | null = null;\n public editingCell: CellCoordinate | null = null;\n public draftValue = '';\n\n public readonly binder = new DataSourceBinder();\n public readonly depGraph = new DependencyGraph();\n public readonly evaluator: FormulaEvaluator;\n\n private primaryDataSourceId?: string;\n private onDocumentChange?: (doc: SpreadsheetDocument) => void;\n private onSelectionChange?: (activeCell: CellCoordinate | null, range: CellRange | null) => void;\n private onCellCommit?: (rowId: string, colId: string, raw: string, value: any) => void;\n\n // History stack for undo/redo\n private undoStack: string[] = [];\n private redoStack: string[] = [];\n private isApplyingHistory = false;\n\n constructor(options: SpreadsheetControllerOptions = {}) {\n const defaultCols: SpreadsheetColumn[] = [\n { id: 'col_a', key: 'A', title: 'A', width: 120, type: 'freeform' },\n { id: 'col_b', key: 'B', title: 'B', width: 120, type: 'freeform' },\n { id: 'col_c', key: 'C', title: 'C', width: 120, type: 'freeform' },\n { id: 'col_d', key: 'D', title: 'D', width: 120, type: 'freeform' }\n ];\n\n const defaultRows: SpreadsheetRow[] = options.primaryDataSourceId ? [] : Array.from({ length: 10 }, (_, i) => ({\n id: `row_${i + 1}`,\n index: i,\n height: 32,\n type: 'freeform'\n }));\n\n this.document = {\n id: options.document?.id || `sheet_${Date.now()}`,\n name: options.document?.name || 'Sheet 1',\n columns: options.document?.columns || defaultCols,\n rows: options.document?.rows || defaultRows,\n cells: options.document?.cells || {},\n createdAt: options.document?.createdAt || Date.now(),\n updatedAt: options.document?.updatedAt || Date.now()\n };\n\n this.primaryDataSourceId = options.primaryDataSourceId;\n this.onDocumentChange = options.onDocumentChange;\n this.onSelectionChange = options.onSelectionChange;\n this.onCellCommit = options.onCellCommit;\n\n if (options.dataSources) {\n options.dataSources.forEach((ds) => this.binder.register(ds));\n }\n\n this.evaluator = new FormulaEvaluator({\n resolveCell: (colStr, rowNum) => this.resolveCellByCoord(colStr, rowNum),\n resolveRange: (sCol, sRow, eCol, eRow) => this.resolveRangeByCoords(sCol, sRow, eCol, eRow),\n resolveProperty: (prop) => this.resolveCurrentRowProperty(prop)\n });\n\n this.syncWithDataSource();\n this.recalculateAll();\n }\n\n public loadDocument(document: SpreadsheetDocument): void {\n this.document = JSON.parse(JSON.stringify(document));\n this.activeCell = null;\n this.selectedRange = null;\n this.editingCell = null;\n this.draftValue = '';\n this.undoStack = [];\n this.redoStack = [];\n this.syncWithDataSource();\n this.recalculateAll();\n this.notifyChange();\n }\n\n public registerDataSource(ds: SpreadsheetDataSource): void {\n this.binder.register(ds);\n this.syncWithDataSource();\n this.recalculateAll();\n }\n\n public setPrimaryDataSource(dataSourceId: string | undefined): void {\n this.primaryDataSourceId = dataSourceId;\n this.syncWithDataSource();\n this.recalculateAll();\n }\n\n public syncWithDataSource(): void {\n if (!this.primaryDataSourceId) return;\n this.document.rows = this.binder.syncRows(this.document.rows, this.primaryDataSourceId);\n this.recomputeRowIndices();\n }\n\n private recomputeRowIndices(): void {\n this.document.rows.forEach((r, idx) => {\n r.index = idx;\n });\n this.document.columns.forEach((c, idx) => {\n c.key = indexToColName(idx);\n });\n }\n\n public getColumn(colIdOrKey: string): SpreadsheetColumn | undefined {\n return this.document.columns.find(\n (c) => c.id === colIdOrKey || c.key.toUpperCase() === colIdOrKey.toUpperCase()\n );\n }\n\n public getRow(rowIdOrIndex: string | number): SpreadsheetRow | undefined {\n if (typeof rowIdOrIndex === 'number') {\n return this.document.rows[rowIdOrIndex];\n }\n return this.document.rows.find((r) => r.id === rowIdOrIndex);\n }\n\n public getCell(rowId: string, colId: string): CellData {\n const existing = this.document.cells[rowId]?.[colId];\n if (existing) return existing;\n\n // Check if column is bound to record\n const col = this.document.columns.find((c) => c.id === colId);\n const row = this.document.rows.find((r) => r.id === rowId);\n\n if (col && row && col.type === 'bound' && col.binding && row.recordId) {\n const boundVal = this.binder.resolveBoundValue(\n col.binding.dataSource,\n row.recordId,\n col.binding.field\n );\n return {\n raw: boundVal !== undefined ? String(boundVal) : '',\n value: boundVal ?? '',\n format: col.format\n };\n }\n\n // Check if column has a column-level formula\n if (col && col.type === 'formula' && col.formula) {\n return {\n raw: col.formula,\n value: '',\n format: col.format\n };\n }\n\n return { raw: '', value: '', format: col?.format };\n }\n\n public setCellValue(rowId: string, colId: string, raw: string): void {\n this.recordSnapshot();\n\n if (!this.document.cells[rowId]) {\n this.document.cells[rowId] = {};\n }\n\n const col = this.document.columns.find((c) => c.id === colId);\n const row = this.document.rows.find((r) => r.id === rowId);\n\n // If this is a bound column and row is bound to record, dispatch back to data source\n if (col?.type === 'bound' && col.binding && row?.recordId) {\n this.binder.commitBoundValue(\n col.binding.dataSource,\n row.recordId,\n col.binding.field,\n raw\n );\n }\n\n const cellKey = this.getCellKey(rowId, colId);\n this.depGraph.setCellFormula(cellKey, raw.startsWith('=') ? raw : null);\n\n this.document.cells[rowId][colId] = {\n raw,\n value: raw,\n format: col?.format\n };\n\n this.document.updatedAt = Date.now();\n this.recalculateCell(rowId, colId);\n this.notifyChange();\n\n const evaluatedVal = this.document.cells[rowId][colId]?.value;\n this.onCellCommit?.(rowId, colId, raw, evaluatedVal);\n }\n\n private getCellKey(rowId: string, colId: string): string {\n const row = this.document.rows.find((r) => r.id === rowId);\n const col = this.document.columns.find((c) => c.id === colId);\n if (!row || !col) return `${colId}_${rowId}`;\n return `${col.key}${row.index + 1}`.toUpperCase();\n }\n\n public recalculateCell(rowId: string, colId: string): void {\n const cellKey = this.getCellKey(rowId, colId);\n const { order, hasCycle } = this.depGraph.getEvaluationOrder(cellKey);\n\n if (hasCycle) {\n if (this.document.cells[rowId]?.[colId]) {\n this.document.cells[rowId][colId].error = '#CYCLE!';\n this.document.cells[rowId][colId].value = '#CYCLE!';\n }\n return;\n }\n\n for (const key of order) {\n this.evalSingleCellByKey(key);\n }\n }\n\n public recalculateAll(): void {\n for (const row of this.document.rows) {\n for (const col of this.document.columns) {\n const cell = this.getCell(row.id, col.id);\n const cellKey = `${col.key}${row.index + 1}`.toUpperCase();\n if (cell.raw.startsWith('=')) {\n this.depGraph.setCellFormula(cellKey, cell.raw);\n }\n }\n }\n\n for (const row of this.document.rows) {\n for (const col of this.document.columns) {\n const cellKey = `${col.key}${row.index + 1}`.toUpperCase();\n this.evalSingleCellByKey(cellKey);\n }\n }\n }\n\n private evalSingleCellByKey(cellKey: string): void {\n const match = cellKey.match(/^([A-Za-z]+)(\\d+)$/);\n if (!match) return;\n const colName = match[1].toUpperCase();\n const rowNum = parseInt(match[2], 10);\n\n const col = this.document.columns.find((c) => c.key.toUpperCase() === colName);\n const row = this.document.rows[rowNum - 1];\n if (!col || !row) return;\n\n const cell = this.getCell(row.id, col.id);\n if (!cell.raw.startsWith('=')) {\n if (cell.error === '#CYCLE!') {\n cell.error = undefined;\n }\n return;\n }\n\n try {\n const computed = this.evaluator.evaluate(cell.raw);\n if (!this.document.cells[row.id]) {\n this.document.cells[row.id] = {};\n }\n this.document.cells[row.id][col.id] = {\n ...cell,\n value: computed,\n error: undefined\n };\n } catch (err: any) {\n if (!this.document.cells[row.id]) {\n this.document.cells[row.id] = {};\n }\n this.document.cells[row.id][col.id] = {\n ...cell,\n value: '#ERROR!',\n error: err?.message || 'Formula error'\n };\n }\n }\n\n private resolveCellByCoord(colStr: string, rowNum: number): any {\n const col = this.document.columns.find((c) => c.key.toUpperCase() === colStr.toUpperCase());\n const row = this.document.rows[rowNum - 1];\n if (!col || !row) return 0;\n\n const cell = this.getCell(row.id, col.id);\n return cell.value !== undefined && cell.value !== '' ? cell.value : 0;\n }\n\n private resolveRangeByCoords(sCol: string, sRow: number, eCol: string, eRow: number): any[] {\n const startC = colNameToIndex(sCol);\n const endC = colNameToIndex(eCol);\n const minC = Math.min(startC, endC);\n const maxC = Math.max(startC, endC);\n const minR = Math.min(sRow, eRow);\n const maxR = Math.max(sRow, eRow);\n\n const results: any[] = [];\n for (let c = minC; c <= maxC; c++) {\n const colName = indexToColName(c);\n const col = this.document.columns.find((colDef) => colDef.key.toUpperCase() === colName);\n if (!col) continue;\n\n for (let r = minR; r <= maxR; r++) {\n const row = this.document.rows[r - 1];\n if (!row) continue;\n const cell = this.getCell(row.id, col.id);\n results.push(cell.value !== undefined && cell.value !== '' ? cell.value : 0);\n }\n }\n return results;\n }\n\n private resolveCurrentRowProperty(prop: string): any {\n if (!this.activeCell) return 0;\n const row = this.document.rows[this.activeCell.row];\n if (!row || !row.recordId || !this.primaryDataSourceId) return 0;\n return this.binder.resolveBoundValue(this.primaryDataSourceId, row.recordId, prop) ?? 0;\n }\n\n // --- Selection and Navigation ---\n\n public selectCell(row: number, col: number, extendSelection = false): void {\n const clampedRow = Math.max(0, Math.min(row, this.document.rows.length - 1));\n const clampedCol = Math.max(0, Math.min(col, this.document.columns.length - 1));\n\n const rowObj = this.document.rows[clampedRow];\n const colObj = this.document.columns[clampedCol];\n\n this.activeCell = {\n row: clampedRow,\n col: clampedCol,\n rowId: rowObj?.id,\n colId: colObj?.id\n };\n\n if (extendSelection && this.selectedRange) {\n this.selectedRange = {\n startRow: this.selectedRange.startRow,\n startCol: this.selectedRange.startCol,\n endRow: clampedRow,\n endCol: clampedCol\n };\n } else {\n this.selectedRange = {\n startRow: clampedRow,\n startCol: clampedCol,\n endRow: clampedRow,\n endCol: clampedCol\n };\n }\n\n this.onSelectionChange?.(this.activeCell, this.selectedRange);\n }\n\n public selectRange(range: CellRange): void {\n this.selectedRange = range;\n this.onSelectionChange?.(this.activeCell, this.selectedRange);\n }\n\n public startEditing(row?: number, col?: number, initialValue?: string): void {\n const targetRow = row ?? this.activeCell?.row ?? 0;\n const targetCol = col ?? this.activeCell?.col ?? 0;\n this.selectCell(targetRow, targetCol);\n\n const rowObj = this.document.rows[targetRow];\n const colObj = this.document.columns[targetCol];\n if (!rowObj || !colObj) return;\n\n this.editingCell = {\n row: targetRow,\n col: targetCol,\n rowId: rowObj.id,\n colId: colObj.id\n };\n\n const cell = this.getCell(rowObj.id, colObj.id);\n this.draftValue = initialValue !== undefined ? initialValue : cell.raw;\n }\n\n public commitEditing(valueToCommit?: string): void {\n if (!this.editingCell) return;\n const val = valueToCommit !== undefined ? valueToCommit : this.draftValue;\n if (this.editingCell.rowId && this.editingCell.colId) {\n this.setCellValue(this.editingCell.rowId, this.editingCell.colId, val);\n }\n this.editingCell = null;\n this.draftValue = '';\n }\n\n public cancelEditing(): void {\n this.editingCell = null;\n this.draftValue = '';\n }\n\n public moveCursor(\n direction: 'up' | 'down' | 'left' | 'right',\n extendSelection = false\n ): void {\n if (!this.activeCell) {\n this.selectCell(0, 0);\n return;\n }\n\n let nextRow = this.activeCell.row;\n let nextCol = this.activeCell.col;\n\n switch (direction) {\n case 'up':\n nextRow = Math.max(0, nextRow - 1);\n break;\n case 'down':\n nextRow = Math.min(this.document.rows.length - 1, nextRow + 1);\n break;\n case 'left':\n nextCol = Math.max(0, nextCol - 1);\n break;\n case 'right':\n nextCol = Math.min(this.document.columns.length - 1, nextCol + 1);\n break;\n }\n\n this.selectCell(nextRow, nextCol, extendSelection);\n }\n\n // --- Grid Structure Mutations ---\n\n public insertColumn(index: number, config?: Partial<SpreadsheetColumn>): void {\n this.recordSnapshot();\n const newId = config?.id || `col_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;\n const newCol: SpreadsheetColumn = {\n id: newId,\n key: indexToColName(index),\n title: config?.title || indexToColName(index),\n width: config?.width || 130,\n type: config?.type || 'freeform',\n binding: config?.binding,\n formula: config?.formula,\n format: config?.format || 'text',\n readOnly: config?.readOnly\n };\n\n this.document.columns.splice(index, 0, newCol);\n this.recomputeRowIndices();\n this.recalculateAll();\n this.notifyChange();\n }\n\n public deleteColumn(colId: string): void {\n this.recordSnapshot();\n this.document.columns = this.document.columns.filter((c) => c.id !== colId);\n for (const rowId of Object.keys(this.document.cells)) {\n delete this.document.cells[rowId][colId];\n }\n this.recomputeRowIndices();\n this.recalculateAll();\n this.notifyChange();\n }\n\n public insertRow(index: number, config?: Partial<SpreadsheetRow>): void {\n this.recordSnapshot();\n const newId = config?.id || `row_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;\n const newRow: SpreadsheetRow = {\n id: newId,\n index,\n height: config?.height || 32,\n recordId: config?.recordId,\n type: config?.type || 'freeform'\n };\n\n this.document.rows.splice(index, 0, newRow);\n this.recomputeRowIndices();\n this.recalculateAll();\n this.notifyChange();\n }\n\n public deleteRow(rowId: string): void {\n this.recordSnapshot();\n this.document.rows = this.document.rows.filter((r) => r.id !== rowId);\n delete this.document.cells[rowId];\n this.recomputeRowIndices();\n this.recalculateAll();\n this.notifyChange();\n }\n\n public setColumnWidth(colId: string, width: number): void {\n const col = this.document.columns.find((c) => c.id === colId);\n if (col) {\n col.width = Math.max(30, width);\n this.notifyChange();\n }\n }\n\n public autoFitColumnWidth(colId: string, customWidth?: number): void {\n if (customWidth !== undefined) {\n this.setColumnWidth(colId, customWidth);\n return;\n }\n const col = this.document.columns.find((c) => c.id === colId);\n if (!col) return;\n\n let maxLen = (col.title || '').length + (col.key ? col.key.length + 3 : 0);\n for (const row of this.document.rows) {\n const cell = this.getCell(row.id, col.id);\n const valStr = cell.value != null ? String(cell.value) : '';\n if (valStr.length > maxLen) {\n maxLen = valStr.length;\n }\n }\n const estimatedWidth = Math.min(600, Math.max(50, Math.ceil(maxLen * 8.5 + 32)));\n this.setColumnWidth(colId, estimatedWidth);\n }\n\n // --- Clipboard TSV/CSV ---\n\n public exportToTsv(): string {\n return this.document.rows\n .map((row) =>\n this.document.columns\n .map((col) => {\n const cell = this.getCell(row.id, col.id);\n return String(cell.value ?? '').replace(/\\t/g, ' ');\n })\n .join('\\t')\n )\n .join('\\n');\n }\n\n public importFromTsv(tsvData: string, startRow = 0, startCol = 0): void {\n this.recordSnapshot();\n const lines = tsvData.trim().split('\\n');\n\n lines.forEach((line, rIdx) => {\n const cells = line.split('\\t');\n const targetR = startRow + rIdx;\n\n while (targetR >= this.document.rows.length) {\n this.insertRow(this.document.rows.length);\n }\n const row = this.document.rows[targetR];\n\n cells.forEach((val, cIdx) => {\n const targetC = startCol + cIdx;\n while (targetC >= this.document.columns.length) {\n this.insertColumn(this.document.columns.length);\n }\n const col = this.document.columns[targetC];\n this.setCellValue(row.id, col.id, val);\n });\n });\n\n this.recalculateAll();\n this.notifyChange();\n }\n\n // --- Undo/Redo Snapshots ---\n\n private recordSnapshot(): void {\n if (this.isApplyingHistory) return;\n this.undoStack.push(JSON.stringify(this.document));\n if (this.undoStack.length > 50) this.undoStack.shift();\n this.redoStack = [];\n }\n\n public undo(): void {\n if (this.undoStack.length === 0) return;\n this.isApplyingHistory = true;\n this.redoStack.push(JSON.stringify(this.document));\n const previous = JSON.parse(this.undoStack.pop()!);\n this.document = previous;\n this.recalculateAll();\n this.notifyChange();\n this.isApplyingHistory = false;\n }\n\n public redo(): void {\n if (this.redoStack.length === 0) return;\n this.isApplyingHistory = true;\n this.undoStack.push(JSON.stringify(this.document));\n const next = JSON.parse(this.redoStack.pop()!);\n this.document = next;\n this.recalculateAll();\n this.notifyChange();\n this.isApplyingHistory = false;\n }\n\n private notifyChange(): void {\n this.onDocumentChange?.(this.document);\n }\n}\n"]}
|