@particle-academy/fancy-sheets 0.7.5 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/index.cjs +49 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +90 -1
- package/dist/index.d.ts +90 -1
- package/dist/index.js +45 -31
- package/dist/index.js.map +1 -1
- package/docs/recipes/csv-roundtrip.md +53 -0
- package/docs/recipes/custom-functions.md +56 -0
- package/docs/recipes/external-state-sync.md +71 -0
- package/docs/recipes/headless-recalc.md +67 -0
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -308,6 +308,52 @@ declare namespace SheetWorkbook {
|
|
|
308
308
|
var displayName: string;
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
type FormulaTokenType = "number" | "string" | "cellRef" | "rangeRef" | "sheetCellRef" | "sheetRangeRef" | "function" | "operator" | "paren" | "comma" | "boolean";
|
|
312
|
+
interface FormulaToken {
|
|
313
|
+
type: FormulaTokenType;
|
|
314
|
+
value: string;
|
|
315
|
+
position: number;
|
|
316
|
+
}
|
|
317
|
+
type FormulaASTNode = {
|
|
318
|
+
type: "number";
|
|
319
|
+
value: number;
|
|
320
|
+
} | {
|
|
321
|
+
type: "string";
|
|
322
|
+
value: string;
|
|
323
|
+
} | {
|
|
324
|
+
type: "boolean";
|
|
325
|
+
value: boolean;
|
|
326
|
+
} | {
|
|
327
|
+
type: "cellRef";
|
|
328
|
+
address: string;
|
|
329
|
+
} | {
|
|
330
|
+
type: "rangeRef";
|
|
331
|
+
start: string;
|
|
332
|
+
end: string;
|
|
333
|
+
} | {
|
|
334
|
+
type: "sheetCellRef";
|
|
335
|
+
sheet: string;
|
|
336
|
+
address: string;
|
|
337
|
+
} | {
|
|
338
|
+
type: "sheetRangeRef";
|
|
339
|
+
sheet: string;
|
|
340
|
+
start: string;
|
|
341
|
+
end: string;
|
|
342
|
+
} | {
|
|
343
|
+
type: "functionCall";
|
|
344
|
+
name: string;
|
|
345
|
+
args: FormulaASTNode[];
|
|
346
|
+
} | {
|
|
347
|
+
type: "binaryOp";
|
|
348
|
+
operator: string;
|
|
349
|
+
left: FormulaASTNode;
|
|
350
|
+
right: FormulaASTNode;
|
|
351
|
+
} | {
|
|
352
|
+
type: "unaryOp";
|
|
353
|
+
operator: string;
|
|
354
|
+
operand: FormulaASTNode;
|
|
355
|
+
};
|
|
356
|
+
|
|
311
357
|
/** Convert 0-based column index to letter(s): 0="A", 25="Z", 26="AA" */
|
|
312
358
|
declare function columnToLetter(col: number): string;
|
|
313
359
|
/** Convert column letter(s) to 0-based index: "A"=0, "Z"=25, "AA"=26 */
|
|
@@ -332,4 +378,47 @@ declare function workbookToCSV(workbook: WorkbookData, sheetId?: string): string
|
|
|
332
378
|
type FormulaRangeFunction = (args: CellValue[][]) => CellValue;
|
|
333
379
|
declare function registerFunction(name: string, fn: FormulaRangeFunction): void;
|
|
334
380
|
|
|
335
|
-
|
|
381
|
+
/**
|
|
382
|
+
* Recalculate every formula cell in a workbook, resolving cross-sheet
|
|
383
|
+
* references. Pure — no React, no DOM — so it runs headless in Node for SSR,
|
|
384
|
+
* snapshots, export pipelines, and tests. Returns a new workbook with each
|
|
385
|
+
* formula cell's `computedValue` populated.
|
|
386
|
+
*/
|
|
387
|
+
declare function recalculateWorkbook(workbook: WorkbookData): WorkbookData;
|
|
388
|
+
/**
|
|
389
|
+
* Recalculate all formula cells in a single sheet, with optional cross-sheet
|
|
390
|
+
* reference support when `allSheets` is supplied. Pure.
|
|
391
|
+
*/
|
|
392
|
+
declare function recalculateSheet(sheet: SheetData, allSheets?: SheetData[]): SheetData;
|
|
393
|
+
|
|
394
|
+
declare function lexFormula(input: string): FormulaToken[];
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Recursive descent parser for spreadsheet formulas.
|
|
398
|
+
* Operator precedence (low to high):
|
|
399
|
+
* 1. Comparison: =, <>, <, >, <=, >=
|
|
400
|
+
* 2. String concatenation: &
|
|
401
|
+
* 3. Addition/subtraction: +, -
|
|
402
|
+
* 4. Multiplication/division: *, /
|
|
403
|
+
* 5. Exponentiation: ^
|
|
404
|
+
* 6. Unary: -, +
|
|
405
|
+
* 7. Atoms: number, string, boolean, cellRef, rangeRef, function call, (expr)
|
|
406
|
+
*/
|
|
407
|
+
declare function parseFormula(tokens: FormulaToken[]): FormulaASTNode;
|
|
408
|
+
|
|
409
|
+
type CellValueGetter = (address: string) => CellValue;
|
|
410
|
+
type RangeValueGetter = (start: string, end: string) => CellValue[];
|
|
411
|
+
type SheetCellValueGetter = (sheetName: string, address: string) => CellValue;
|
|
412
|
+
type SheetRangeValueGetter = (sheetName: string, start: string, end: string) => CellValue[];
|
|
413
|
+
interface EvaluatorContext {
|
|
414
|
+
getCellValue: CellValueGetter;
|
|
415
|
+
getRangeValues: RangeValueGetter;
|
|
416
|
+
getSheetCellValue?: SheetCellValueGetter;
|
|
417
|
+
getSheetRangeValues?: SheetRangeValueGetter;
|
|
418
|
+
}
|
|
419
|
+
declare function evaluateAST(node: FormulaASTNode, getCellValue: CellValueGetter, getRangeValues: RangeValueGetter, ctx?: {
|
|
420
|
+
getSheetCellValue?: SheetCellValueGetter;
|
|
421
|
+
getSheetRangeValues?: SheetRangeValueGetter;
|
|
422
|
+
}): CellValue;
|
|
423
|
+
|
|
424
|
+
export { type CellAddress, type CellComment, type CellData, type CellFormat, type CellHighlight, type CellHighlightMap, type CellMap, type CellRange, type CellValue, type CellValueGetter, type ColumnWidths, type EvaluatorContext, type FormulaASTNode, type FormulaRangeFunction, type FormulaToken, type FormulaTokenType, type MergedRegion, type RangeValueGetter, type SelectionState, Sheet, type SheetCellValueGetter, type SheetData, type SheetProps, type SheetRangeValueGetter, SheetWorkbook, type SheetWorkbookProps, Spreadsheet, type SpreadsheetContextMenuItem, type SpreadsheetContextValue, type SpreadsheetProps, type TextAlign, type ToolbarButton, type WorkbookData, columnToLetter, createEmptySheet, createEmptyWorkbook, csvToWorkbook, evaluateAST, letterToColumn, lexFormula, parseAddress, parseCSV, parseFormula, recalculateSheet, recalculateWorkbook, registerFunction, stringifyCSV, toAddress, useSpreadsheet, workbookToCSV };
|
package/dist/index.d.ts
CHANGED
|
@@ -308,6 +308,52 @@ declare namespace SheetWorkbook {
|
|
|
308
308
|
var displayName: string;
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
type FormulaTokenType = "number" | "string" | "cellRef" | "rangeRef" | "sheetCellRef" | "sheetRangeRef" | "function" | "operator" | "paren" | "comma" | "boolean";
|
|
312
|
+
interface FormulaToken {
|
|
313
|
+
type: FormulaTokenType;
|
|
314
|
+
value: string;
|
|
315
|
+
position: number;
|
|
316
|
+
}
|
|
317
|
+
type FormulaASTNode = {
|
|
318
|
+
type: "number";
|
|
319
|
+
value: number;
|
|
320
|
+
} | {
|
|
321
|
+
type: "string";
|
|
322
|
+
value: string;
|
|
323
|
+
} | {
|
|
324
|
+
type: "boolean";
|
|
325
|
+
value: boolean;
|
|
326
|
+
} | {
|
|
327
|
+
type: "cellRef";
|
|
328
|
+
address: string;
|
|
329
|
+
} | {
|
|
330
|
+
type: "rangeRef";
|
|
331
|
+
start: string;
|
|
332
|
+
end: string;
|
|
333
|
+
} | {
|
|
334
|
+
type: "sheetCellRef";
|
|
335
|
+
sheet: string;
|
|
336
|
+
address: string;
|
|
337
|
+
} | {
|
|
338
|
+
type: "sheetRangeRef";
|
|
339
|
+
sheet: string;
|
|
340
|
+
start: string;
|
|
341
|
+
end: string;
|
|
342
|
+
} | {
|
|
343
|
+
type: "functionCall";
|
|
344
|
+
name: string;
|
|
345
|
+
args: FormulaASTNode[];
|
|
346
|
+
} | {
|
|
347
|
+
type: "binaryOp";
|
|
348
|
+
operator: string;
|
|
349
|
+
left: FormulaASTNode;
|
|
350
|
+
right: FormulaASTNode;
|
|
351
|
+
} | {
|
|
352
|
+
type: "unaryOp";
|
|
353
|
+
operator: string;
|
|
354
|
+
operand: FormulaASTNode;
|
|
355
|
+
};
|
|
356
|
+
|
|
311
357
|
/** Convert 0-based column index to letter(s): 0="A", 25="Z", 26="AA" */
|
|
312
358
|
declare function columnToLetter(col: number): string;
|
|
313
359
|
/** Convert column letter(s) to 0-based index: "A"=0, "Z"=25, "AA"=26 */
|
|
@@ -332,4 +378,47 @@ declare function workbookToCSV(workbook: WorkbookData, sheetId?: string): string
|
|
|
332
378
|
type FormulaRangeFunction = (args: CellValue[][]) => CellValue;
|
|
333
379
|
declare function registerFunction(name: string, fn: FormulaRangeFunction): void;
|
|
334
380
|
|
|
335
|
-
|
|
381
|
+
/**
|
|
382
|
+
* Recalculate every formula cell in a workbook, resolving cross-sheet
|
|
383
|
+
* references. Pure — no React, no DOM — so it runs headless in Node for SSR,
|
|
384
|
+
* snapshots, export pipelines, and tests. Returns a new workbook with each
|
|
385
|
+
* formula cell's `computedValue` populated.
|
|
386
|
+
*/
|
|
387
|
+
declare function recalculateWorkbook(workbook: WorkbookData): WorkbookData;
|
|
388
|
+
/**
|
|
389
|
+
* Recalculate all formula cells in a single sheet, with optional cross-sheet
|
|
390
|
+
* reference support when `allSheets` is supplied. Pure.
|
|
391
|
+
*/
|
|
392
|
+
declare function recalculateSheet(sheet: SheetData, allSheets?: SheetData[]): SheetData;
|
|
393
|
+
|
|
394
|
+
declare function lexFormula(input: string): FormulaToken[];
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Recursive descent parser for spreadsheet formulas.
|
|
398
|
+
* Operator precedence (low to high):
|
|
399
|
+
* 1. Comparison: =, <>, <, >, <=, >=
|
|
400
|
+
* 2. String concatenation: &
|
|
401
|
+
* 3. Addition/subtraction: +, -
|
|
402
|
+
* 4. Multiplication/division: *, /
|
|
403
|
+
* 5. Exponentiation: ^
|
|
404
|
+
* 6. Unary: -, +
|
|
405
|
+
* 7. Atoms: number, string, boolean, cellRef, rangeRef, function call, (expr)
|
|
406
|
+
*/
|
|
407
|
+
declare function parseFormula(tokens: FormulaToken[]): FormulaASTNode;
|
|
408
|
+
|
|
409
|
+
type CellValueGetter = (address: string) => CellValue;
|
|
410
|
+
type RangeValueGetter = (start: string, end: string) => CellValue[];
|
|
411
|
+
type SheetCellValueGetter = (sheetName: string, address: string) => CellValue;
|
|
412
|
+
type SheetRangeValueGetter = (sheetName: string, start: string, end: string) => CellValue[];
|
|
413
|
+
interface EvaluatorContext {
|
|
414
|
+
getCellValue: CellValueGetter;
|
|
415
|
+
getRangeValues: RangeValueGetter;
|
|
416
|
+
getSheetCellValue?: SheetCellValueGetter;
|
|
417
|
+
getSheetRangeValues?: SheetRangeValueGetter;
|
|
418
|
+
}
|
|
419
|
+
declare function evaluateAST(node: FormulaASTNode, getCellValue: CellValueGetter, getRangeValues: RangeValueGetter, ctx?: {
|
|
420
|
+
getSheetCellValue?: SheetCellValueGetter;
|
|
421
|
+
getSheetRangeValues?: SheetRangeValueGetter;
|
|
422
|
+
}): CellValue;
|
|
423
|
+
|
|
424
|
+
export { type CellAddress, type CellComment, type CellData, type CellFormat, type CellHighlight, type CellHighlightMap, type CellMap, type CellRange, type CellValue, type CellValueGetter, type ColumnWidths, type EvaluatorContext, type FormulaASTNode, type FormulaRangeFunction, type FormulaToken, type FormulaTokenType, type MergedRegion, type RangeValueGetter, type SelectionState, Sheet, type SheetCellValueGetter, type SheetData, type SheetProps, type SheetRangeValueGetter, SheetWorkbook, type SheetWorkbookProps, Spreadsheet, type SpreadsheetContextMenuItem, type SpreadsheetContextValue, type SpreadsheetProps, type TextAlign, type ToolbarButton, type WorkbookData, columnToLetter, createEmptySheet, createEmptyWorkbook, csvToWorkbook, evaluateAST, letterToColumn, lexFormula, parseAddress, parseCSV, parseFormula, recalculateSheet, recalculateWorkbook, registerFunction, stringifyCSV, toAddress, useSpreadsheet, workbookToCSV };
|
package/dist/index.js
CHANGED
|
@@ -1249,7 +1249,7 @@ function getRecalculationOrder(graph) {
|
|
|
1249
1249
|
return order;
|
|
1250
1250
|
}
|
|
1251
1251
|
|
|
1252
|
-
// src/
|
|
1252
|
+
// src/engine/recalc.ts
|
|
1253
1253
|
function recalculateWorkbook(workbook) {
|
|
1254
1254
|
const recalculated = [];
|
|
1255
1255
|
for (const sheet of workbook.sheets) {
|
|
@@ -1261,33 +1261,6 @@ function recalculateWorkbook(workbook) {
|
|
|
1261
1261
|
}
|
|
1262
1262
|
return { ...workbook, sheets: finalSheets };
|
|
1263
1263
|
}
|
|
1264
|
-
function createInitialState(data) {
|
|
1265
|
-
const workbook = data ?? createEmptyWorkbook();
|
|
1266
|
-
return {
|
|
1267
|
-
workbook: recalculateWorkbook(workbook),
|
|
1268
|
-
selection: { activeCell: "A1", ranges: [{ start: "A1", end: "A1" }] },
|
|
1269
|
-
editingCell: null,
|
|
1270
|
-
editValue: "",
|
|
1271
|
-
undoStack: [],
|
|
1272
|
-
redoStack: []
|
|
1273
|
-
};
|
|
1274
|
-
}
|
|
1275
|
-
function getActiveSheet(state) {
|
|
1276
|
-
return state.workbook.sheets.find((s) => s.id === state.workbook.activeSheetId);
|
|
1277
|
-
}
|
|
1278
|
-
function updateActiveSheet(state, updater) {
|
|
1279
|
-
return {
|
|
1280
|
-
...state.workbook,
|
|
1281
|
-
sheets: state.workbook.sheets.map(
|
|
1282
|
-
(s) => s.id === state.workbook.activeSheetId ? updater(s) : s
|
|
1283
|
-
)
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
|
-
function pushUndo(state) {
|
|
1287
|
-
const stack = [...state.undoStack, state.workbook];
|
|
1288
|
-
if (stack.length > 50) stack.shift();
|
|
1289
|
-
return { undoStack: stack, redoStack: [] };
|
|
1290
|
-
}
|
|
1291
1264
|
function recalculateSheet(sheet, allSheets) {
|
|
1292
1265
|
const graph = buildDependencyGraph(sheet.cells);
|
|
1293
1266
|
if (graph.size === 0) return sheet;
|
|
@@ -1344,6 +1317,35 @@ function recalculateSheet(sheet, allSheets) {
|
|
|
1344
1317
|
}
|
|
1345
1318
|
return { ...sheet, cells };
|
|
1346
1319
|
}
|
|
1320
|
+
|
|
1321
|
+
// src/hooks/use-spreadsheet-store.ts
|
|
1322
|
+
function createInitialState(data) {
|
|
1323
|
+
const workbook = data ?? createEmptyWorkbook();
|
|
1324
|
+
return {
|
|
1325
|
+
workbook: recalculateWorkbook(workbook),
|
|
1326
|
+
selection: { activeCell: "A1", ranges: [{ start: "A1", end: "A1" }] },
|
|
1327
|
+
editingCell: null,
|
|
1328
|
+
editValue: "",
|
|
1329
|
+
undoStack: [],
|
|
1330
|
+
redoStack: []
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
function getActiveSheet(state) {
|
|
1334
|
+
return state.workbook.sheets.find((s) => s.id === state.workbook.activeSheetId);
|
|
1335
|
+
}
|
|
1336
|
+
function updateActiveSheet(state, updater) {
|
|
1337
|
+
return {
|
|
1338
|
+
...state.workbook,
|
|
1339
|
+
sheets: state.workbook.sheets.map(
|
|
1340
|
+
(s) => s.id === state.workbook.activeSheetId ? updater(s) : s
|
|
1341
|
+
)
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
function pushUndo(state) {
|
|
1345
|
+
const stack = [...state.undoStack, state.workbook];
|
|
1346
|
+
if (stack.length > 50) stack.shift();
|
|
1347
|
+
return { undoStack: stack, redoStack: [] };
|
|
1348
|
+
}
|
|
1347
1349
|
function getCellDisplayValue(cell) {
|
|
1348
1350
|
if (!cell) return "";
|
|
1349
1351
|
if (cell.computedValue !== void 0) return String(cell.computedValue);
|
|
@@ -1555,7 +1557,7 @@ function reducer(state, action) {
|
|
|
1555
1557
|
};
|
|
1556
1558
|
}
|
|
1557
1559
|
case "SET_WORKBOOK":
|
|
1558
|
-
return { ...state, workbook: action.workbook };
|
|
1560
|
+
return { ...state, workbook: recalculateWorkbook(action.workbook) };
|
|
1559
1561
|
default:
|
|
1560
1562
|
return state;
|
|
1561
1563
|
}
|
|
@@ -2254,7 +2256,13 @@ function SpreadsheetGrid({ className }) {
|
|
|
2254
2256
|
return /* @__PURE__ */ jsxs(
|
|
2255
2257
|
"div",
|
|
2256
2258
|
{
|
|
2257
|
-
className:
|
|
2259
|
+
className: cn(
|
|
2260
|
+
"flex",
|
|
2261
|
+
// Frozen rows need a solid background so scrolling content
|
|
2262
|
+
// doesn't bleed through any cell-level transparency
|
|
2263
|
+
// (highlight overlays, alpha format colors, etc.).
|
|
2264
|
+
isFrozenRow && "bg-white dark:bg-zinc-900"
|
|
2265
|
+
),
|
|
2258
2266
|
style: isFrozenRow ? {
|
|
2259
2267
|
position: "sticky",
|
|
2260
2268
|
top: rowHeight + rowIdx * rowHeight,
|
|
@@ -2268,6 +2276,12 @@ function SpreadsheetGrid({ className }) {
|
|
|
2268
2276
|
return /* @__PURE__ */ jsx(
|
|
2269
2277
|
"div",
|
|
2270
2278
|
{
|
|
2279
|
+
className: cn(
|
|
2280
|
+
// Same solid-background guarantee for frozen
|
|
2281
|
+
// columns — keeps the column opaque while content
|
|
2282
|
+
// scrolls horizontally underneath.
|
|
2283
|
+
isFrozenCol && "bg-white dark:bg-zinc-900"
|
|
2284
|
+
),
|
|
2271
2285
|
style: isFrozenCol ? {
|
|
2272
2286
|
position: "sticky",
|
|
2273
2287
|
left: 48 + Array.from({ length: colIdx }, (_3, c) => getColumnWidth(c)).reduce((a, b) => a + b, 0),
|
|
@@ -2847,6 +2861,6 @@ function workbookToCSV(workbook, sheetId) {
|
|
|
2847
2861
|
return stringifyCSV(data);
|
|
2848
2862
|
}
|
|
2849
2863
|
|
|
2850
|
-
export { Sheet, SheetWorkbook, Spreadsheet, columnToLetter, createEmptySheet, createEmptyWorkbook, csvToWorkbook, letterToColumn, parseAddress, parseCSV, registerFunction, stringifyCSV, toAddress, useSpreadsheet, workbookToCSV };
|
|
2864
|
+
export { Sheet, SheetWorkbook, Spreadsheet, columnToLetter, createEmptySheet, createEmptyWorkbook, csvToWorkbook, evaluateAST, letterToColumn, lexFormula, parseAddress, parseCSV, parseFormula, recalculateSheet, recalculateWorkbook, registerFunction, stringifyCSV, toAddress, useSpreadsheet, workbookToCSV };
|
|
2851
2865
|
//# sourceMappingURL=index.js.map
|
|
2852
2866
|
//# sourceMappingURL=index.js.map
|