@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/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
- export { type CellAddress, type CellComment, type CellData, type CellFormat, type CellHighlight, type CellHighlightMap, type CellMap, type CellRange, type CellValue, type ColumnWidths, type FormulaRangeFunction, type MergedRegion, type SelectionState, Sheet, type SheetData, type SheetProps, SheetWorkbook, type SheetWorkbookProps, Spreadsheet, type SpreadsheetContextMenuItem, type SpreadsheetContextValue, type SpreadsheetProps, type TextAlign, type ToolbarButton, type WorkbookData, columnToLetter, createEmptySheet, createEmptyWorkbook, csvToWorkbook, letterToColumn, parseAddress, parseCSV, registerFunction, stringifyCSV, toAddress, useSpreadsheet, workbookToCSV };
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
- export { type CellAddress, type CellComment, type CellData, type CellFormat, type CellHighlight, type CellHighlightMap, type CellMap, type CellRange, type CellValue, type ColumnWidths, type FormulaRangeFunction, type MergedRegion, type SelectionState, Sheet, type SheetData, type SheetProps, SheetWorkbook, type SheetWorkbookProps, Spreadsheet, type SpreadsheetContextMenuItem, type SpreadsheetContextValue, type SpreadsheetProps, type TextAlign, type ToolbarButton, type WorkbookData, columnToLetter, createEmptySheet, createEmptyWorkbook, csvToWorkbook, letterToColumn, parseAddress, parseCSV, registerFunction, stringifyCSV, toAddress, useSpreadsheet, workbookToCSV };
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/hooks/use-spreadsheet-store.ts
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: "flex",
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