@particle-academy/fancy-sheets 0.4.5 → 0.4.6
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 +27 -438
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/styles.css.map +1 -1
- package/docs/Spreadsheet.md +216 -0
- package/docs/csv.md +75 -0
- package/docs/formulas.md +161 -0
- package/package.json +63 -56
package/dist/styles.css.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/styles.css"],"sourcesContent":["/* @particle-academy/fancy-sheets — spreadsheet styles */\
|
|
1
|
+
{"version":3,"sources":["../src/styles.css"],"sourcesContent":["/* @particle-academy/fancy-sheets — spreadsheet styles */\n\n/* Grid cell borders */\n[data-fancy-sheets-grid] {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n}\n\n/* Cell editor positioning */\n[data-fancy-sheets-cell-editor] {\n box-sizing: border-box;\n}\n\n/* Scrollbar styling */\n[data-fancy-sheets-grid]::-webkit-scrollbar {\n width: 10px;\n height: 10px;\n}\n\n[data-fancy-sheets-grid]::-webkit-scrollbar-track {\n background: transparent;\n}\n\n[data-fancy-sheets-grid]::-webkit-scrollbar-thumb {\n background: #a1a1aa;\n border-radius: 5px;\n}\n\n.dark [data-fancy-sheets-grid]::-webkit-scrollbar-thumb {\n background: #52525b;\n}\n"],"mappings":";AAGA,CAAC;AACC;AAAA,IAAa,aAAa;AAAA,IAAE,kBAAkB;AAAA,IAAE,UAAU;AAAA,IAAE,MAAM;AAAA,IAAE;AACtE;AAGA,CAAC;AACC,cAAY;AACd;AAGA,CAAC,uBAAuB;AACtB,SAAO;AACP,UAAQ;AACV;AAEA,CAAC,uBAAuB;AACtB,cAAY;AACd;AAEA,CAAC,uBAAuB;AACtB,cAAY;AACZ,iBAAe;AACjB;AAEA,CAAC,KAAK,CAAC,uBAAuB;AAC5B,cAAY;AACd;","names":[]}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Spreadsheet
|
|
2
|
+
|
|
3
|
+
A full-featured spreadsheet component with formulas, formatting, selection, multi-sheet workbooks, clipboard, CSV import/export, and undo/redo. Compound API.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Spreadsheet, useSpreadsheet } from "@particle-academy/fancy-sheets";
|
|
9
|
+
import "@particle-academy/fancy-sheets/styles.css";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Basic Usage
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
<div style={{ height: 600 }}>
|
|
16
|
+
<Spreadsheet>
|
|
17
|
+
<Spreadsheet.Toolbar />
|
|
18
|
+
<Spreadsheet.Grid />
|
|
19
|
+
<Spreadsheet.SheetTabs />
|
|
20
|
+
</Spreadsheet>
|
|
21
|
+
</div>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The component fills its container — wrap it in a sized element (height is required).
|
|
25
|
+
|
|
26
|
+
## Props
|
|
27
|
+
|
|
28
|
+
### Spreadsheet (root)
|
|
29
|
+
|
|
30
|
+
| Prop | Type | Default | Description |
|
|
31
|
+
|------|------|---------|-------------|
|
|
32
|
+
| data | `WorkbookData` | - | Controlled workbook |
|
|
33
|
+
| defaultData | `WorkbookData` | empty workbook | Initial workbook (uncontrolled) |
|
|
34
|
+
| onChange | `(data: WorkbookData) => void` | - | Called on any data change |
|
|
35
|
+
| columnCount | `number` | `26` | Number of columns |
|
|
36
|
+
| rowCount | `number` | `100` | Number of rows |
|
|
37
|
+
| defaultColumnWidth | `number` | `100` | Default column width in px |
|
|
38
|
+
| rowHeight | `number` | `28` | Row height in px |
|
|
39
|
+
| readOnly | `boolean` | `false` | Disable editing, formatting, and structural changes |
|
|
40
|
+
| className | `string` | - | Additional CSS classes |
|
|
41
|
+
|
|
42
|
+
### Spreadsheet.Toolbar
|
|
43
|
+
|
|
44
|
+
Default toolbar with format buttons (bold, italic, align), undo/redo, and freeze controls. Pass `children` to replace entirely.
|
|
45
|
+
|
|
46
|
+
| Prop | Type | Description |
|
|
47
|
+
|------|------|-------------|
|
|
48
|
+
| children | `ReactNode` | Replace defaults |
|
|
49
|
+
| className | `string` | Additional CSS classes |
|
|
50
|
+
|
|
51
|
+
### Spreadsheet.Grid
|
|
52
|
+
|
|
53
|
+
The main editable cell grid. Handles navigation, selection, editing, keyboard shortcuts, and clipboard.
|
|
54
|
+
|
|
55
|
+
| Prop | Type | Description |
|
|
56
|
+
|------|------|-------------|
|
|
57
|
+
| className | `string` | Additional CSS classes |
|
|
58
|
+
|
|
59
|
+
### Spreadsheet.SheetTabs
|
|
60
|
+
|
|
61
|
+
Bottom tab bar for multi-sheet workbooks. Supports add/rename/delete/switch.
|
|
62
|
+
|
|
63
|
+
| Prop | Type | Description |
|
|
64
|
+
|------|------|-------------|
|
|
65
|
+
| className | `string` | Additional CSS classes |
|
|
66
|
+
|
|
67
|
+
## Data Model
|
|
68
|
+
|
|
69
|
+
### WorkbookData
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
interface WorkbookData {
|
|
73
|
+
sheets: SheetData[];
|
|
74
|
+
activeSheetId: string;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### SheetData
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
interface SheetData {
|
|
82
|
+
id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
cells: Record<CellAddress, CellData>; // sparse — only non-empty cells
|
|
85
|
+
columnWidths: Record<number, number>; // sparse overrides
|
|
86
|
+
mergedRegions: MergedRegion[];
|
|
87
|
+
columnFilters: Record<number, string>;
|
|
88
|
+
sortColumn?: number;
|
|
89
|
+
sortDirection?: "asc" | "desc";
|
|
90
|
+
frozenRows: number;
|
|
91
|
+
frozenCols: number;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### CellData
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
type CellAddress = string; // "A1", "AA99", ...
|
|
99
|
+
type CellValue = string | number | boolean | null;
|
|
100
|
+
|
|
101
|
+
interface CellData {
|
|
102
|
+
value: CellValue; // raw entered value
|
|
103
|
+
formula?: string; // original formula string (e.g. "=SUM(A1:A10)")
|
|
104
|
+
computedValue?: CellValue; // evaluated result
|
|
105
|
+
format?: CellFormat;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### CellFormat
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
interface CellFormat {
|
|
113
|
+
bold?: boolean;
|
|
114
|
+
italic?: boolean;
|
|
115
|
+
textAlign?: "left" | "center" | "right";
|
|
116
|
+
displayFormat?: "auto" | "date" | "datetime" | "number" | "percent";
|
|
117
|
+
decimals?: number;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Helpers
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { createEmptyWorkbook, createEmptySheet } from "@particle-academy/fancy-sheets";
|
|
125
|
+
|
|
126
|
+
const workbook = createEmptyWorkbook();
|
|
127
|
+
const newSheet = createEmptySheet("sheet-2", "Summary");
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## useSpreadsheet Hook
|
|
131
|
+
|
|
132
|
+
Access full workbook state and actions from any child of `<Spreadsheet>`.
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { useSpreadsheet } from "@particle-academy/fancy-sheets";
|
|
136
|
+
|
|
137
|
+
function ExportButton() {
|
|
138
|
+
const { workbook } = useSpreadsheet();
|
|
139
|
+
return (
|
|
140
|
+
<button onClick={() => download(workbookToCSV(workbook))}>
|
|
141
|
+
Export CSV
|
|
142
|
+
</button>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
See `SpreadsheetContextValue` in source for the full shape — key properties:
|
|
148
|
+
|
|
149
|
+
| Property | Description |
|
|
150
|
+
|----------|-------------|
|
|
151
|
+
| `workbook` | Current WorkbookData |
|
|
152
|
+
| `activeSheet` | Currently displayed SheetData |
|
|
153
|
+
| `selection` | `{ activeCell, ranges }` |
|
|
154
|
+
| `setCellValue(addr, val)` | Update a single cell |
|
|
155
|
+
| `setCellFormat(addrs[], fmt)` | Apply formatting to a list of cells |
|
|
156
|
+
| `navigate(dir, extend?)` | Move active cell (arrow-key equivalent) |
|
|
157
|
+
| `startEdit(val?) / confirmEdit() / cancelEdit()` | Edit lifecycle |
|
|
158
|
+
| `addSheet() / renameSheet() / deleteSheet() / setActiveSheet()` | Sheet management |
|
|
159
|
+
| `setFrozenRows(n) / setFrozenCols(n)` | Freeze controls |
|
|
160
|
+
| `undo() / redo()` | History navigation (50-step stack) |
|
|
161
|
+
| `canUndo / canRedo` | Booleans |
|
|
162
|
+
|
|
163
|
+
## Controlled Usage
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
const [data, setData] = useState<WorkbookData>(createEmptyWorkbook());
|
|
167
|
+
|
|
168
|
+
<Spreadsheet data={data} onChange={setData}>
|
|
169
|
+
<Spreadsheet.Toolbar />
|
|
170
|
+
<Spreadsheet.Grid />
|
|
171
|
+
<Spreadsheet.SheetTabs />
|
|
172
|
+
</Spreadsheet>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Read-Only Display
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<Spreadsheet defaultData={reportData} readOnly>
|
|
179
|
+
<Spreadsheet.Grid />
|
|
180
|
+
</Spreadsheet>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Keyboard Shortcuts
|
|
184
|
+
|
|
185
|
+
| Keys | Action |
|
|
186
|
+
|------|--------|
|
|
187
|
+
| Arrow keys | Move active cell |
|
|
188
|
+
| Shift + arrows | Extend selection |
|
|
189
|
+
| Ctrl/Cmd + arrows | Jump to edge |
|
|
190
|
+
| Enter / F2 | Start editing |
|
|
191
|
+
| Esc | Cancel edit |
|
|
192
|
+
| Tab / Shift+Tab | Move right/left |
|
|
193
|
+
| Ctrl/Cmd + C / X / V | Copy / Cut / Paste |
|
|
194
|
+
| Ctrl/Cmd + Z / Y | Undo / Redo |
|
|
195
|
+
| Ctrl/Cmd + B / I | Bold / Italic |
|
|
196
|
+
|
|
197
|
+
## Data Attributes
|
|
198
|
+
|
|
199
|
+
| Attribute | Element |
|
|
200
|
+
|-----------|---------|
|
|
201
|
+
| `data-fancy-sheets` | Root container |
|
|
202
|
+
| `data-fancy-sheets-toolbar` | Toolbar |
|
|
203
|
+
| `data-fancy-sheets-formula-bar` | Formula bar |
|
|
204
|
+
| `data-fancy-sheets-grid` | Grid container |
|
|
205
|
+
| `data-fancy-sheets-column-headers` | Column header row |
|
|
206
|
+
| `data-fancy-sheets-row-header` | Row header cell |
|
|
207
|
+
| `data-fancy-sheets-cell` | Individual cell |
|
|
208
|
+
| `data-fancy-sheets-cell-editor` | Active edit input |
|
|
209
|
+
| `data-fancy-sheets-selection` | Selection overlay |
|
|
210
|
+
| `data-fancy-sheets-resize-handle` | Column resize handle |
|
|
211
|
+
| `data-fancy-sheets-tabs` | Sheet tabs |
|
|
212
|
+
|
|
213
|
+
## See Also
|
|
214
|
+
|
|
215
|
+
- [Formulas](./formulas.md) — all 80+ built-in functions and how to register custom ones
|
|
216
|
+
- [CSV Import/Export](./csv.md) — `csvToWorkbook`, `workbookToCSV`, `parseCSV`, `stringifyCSV`
|
package/docs/csv.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# CSV Import / Export
|
|
2
|
+
|
|
3
|
+
Four utility functions for moving data between CSV strings and workbook/sheet structures. Handles quoted fields, escaped quotes, and newlines inside quoted values.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import {
|
|
9
|
+
parseCSV,
|
|
10
|
+
stringifyCSV,
|
|
11
|
+
csvToWorkbook,
|
|
12
|
+
workbookToCSV,
|
|
13
|
+
} from "@particle-academy/fancy-sheets";
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Low-Level: parseCSV / stringifyCSV
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
function parseCSV(csv: string): string[][];
|
|
20
|
+
function stringifyCSV(rows: string[][]): string;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
const rows = parseCSV(`name,age\nAlice,30\nBob,25`);
|
|
25
|
+
// [["name", "age"], ["Alice", "30"], ["Bob", "25"]]
|
|
26
|
+
|
|
27
|
+
const csv = stringifyCSV(rows);
|
|
28
|
+
// "name,age\nAlice,30\nBob,25"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Quoted strings are preserved, embedded commas/newlines survive the round-trip, and `""` is decoded as a literal `"`.
|
|
32
|
+
|
|
33
|
+
## High-Level: csvToWorkbook / workbookToCSV
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
function csvToWorkbook(csv: string, sheetName?: string): WorkbookData;
|
|
37
|
+
function workbookToCSV(workbook: WorkbookData): string;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Use these for full workbook ↔ file conversions:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// Load a CSV file into the editor
|
|
44
|
+
const text = await file.text();
|
|
45
|
+
const workbook = csvToWorkbook(text, file.name.replace(/\.csv$/i, ""));
|
|
46
|
+
setData(workbook);
|
|
47
|
+
|
|
48
|
+
// Export current workbook to CSV
|
|
49
|
+
const csv = workbookToCSV(workbook);
|
|
50
|
+
downloadFile(csv, "export.csv", "text/csv");
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`workbookToCSV` concatenates all sheets separated by a blank line and `# <sheet name>` header lines. For single-sheet export, slice the returned string or pre-filter the workbook to one sheet.
|
|
54
|
+
|
|
55
|
+
## Example: File Upload
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
function Importer() {
|
|
59
|
+
const { workbook } = useSpreadsheet();
|
|
60
|
+
|
|
61
|
+
const handleFile = async (e: ChangeEvent<HTMLInputElement>) => {
|
|
62
|
+
const file = e.target.files?.[0];
|
|
63
|
+
if (!file) return;
|
|
64
|
+
const text = await file.text();
|
|
65
|
+
const imported = csvToWorkbook(text, file.name);
|
|
66
|
+
// merge into existing workbook or replace...
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return <input type="file" accept=".csv" onChange={handleFile} />;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Clipboard
|
|
74
|
+
|
|
75
|
+
Clipboard paste into the grid already uses TSV (tab-separated) internally — paste a CSV from Excel / Sheets and it lands in the cells correctly. Use the CSV helpers for file-based import/export, not clipboard.
|
package/docs/formulas.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Formulas
|
|
2
|
+
|
|
3
|
+
The engine evaluates Excel/Sheets-style formulas entered with a leading `=`. Parser and evaluator are custom — no third-party dependency.
|
|
4
|
+
|
|
5
|
+
## Basics
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
=A1+B1 addition
|
|
9
|
+
=SUM(A1:A10) range aggregate
|
|
10
|
+
=IF(A1>100, "hi", "lo") logic
|
|
11
|
+
=Sheet2!B3 cross-sheet reference
|
|
12
|
+
=A1 & " " & B1 string concatenation
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Operators
|
|
16
|
+
|
|
17
|
+
| Operator | Meaning |
|
|
18
|
+
|----------|---------|
|
|
19
|
+
| `+` `-` `*` `/` `^` | Arithmetic (`^` = power) |
|
|
20
|
+
| `=` `<>` `<` `<=` `>` `>=` | Comparison |
|
|
21
|
+
| `&` | String concatenation |
|
|
22
|
+
| `%` | Percentage (`50%` = 0.5) |
|
|
23
|
+
| `()` | Grouping |
|
|
24
|
+
|
|
25
|
+
## Error Values
|
|
26
|
+
|
|
27
|
+
| Value | Cause |
|
|
28
|
+
|-------|-------|
|
|
29
|
+
| `#ERROR!` | Syntax error in formula |
|
|
30
|
+
| `#VALUE!` | Wrong argument type |
|
|
31
|
+
| `#DIV/0!` | Division by zero |
|
|
32
|
+
| `#NAME?` | Unknown function |
|
|
33
|
+
| `#CIRC!` | Circular reference |
|
|
34
|
+
|
|
35
|
+
## Reference Forms
|
|
36
|
+
|
|
37
|
+
- `A1` — single cell
|
|
38
|
+
- `A1:B5` — range
|
|
39
|
+
- `Sheet2!A1` — cell on another sheet
|
|
40
|
+
- `Sheet2!A1:B5` — range on another sheet
|
|
41
|
+
|
|
42
|
+
## Built-in Functions (80+)
|
|
43
|
+
|
|
44
|
+
### Math (25)
|
|
45
|
+
|
|
46
|
+
| Function | Purpose |
|
|
47
|
+
|----------|---------|
|
|
48
|
+
| `SUM(range)` | Sum of numbers |
|
|
49
|
+
| `AVERAGE(range)` | Arithmetic mean |
|
|
50
|
+
| `MEDIAN(range)` | Median value |
|
|
51
|
+
| `MIN(range)` / `MAX(range)` | Min / max |
|
|
52
|
+
| `COUNT(range)` | Count of numeric cells |
|
|
53
|
+
| `PRODUCT(range)` | Product of numbers |
|
|
54
|
+
| `ROUND(n, digits)` | Round to `digits` decimals |
|
|
55
|
+
| `ABS(n)` | Absolute value |
|
|
56
|
+
| `SQRT(n)` | Square root |
|
|
57
|
+
| `POWER(base, exp)` | `base^exp` |
|
|
58
|
+
| `MOD(a, b)` | Remainder |
|
|
59
|
+
| `INT(n)` / `TRUNC(n)` | Truncate to int |
|
|
60
|
+
| `FLOOR(n)` / `CEILING(n)` | Round down / up |
|
|
61
|
+
| `SIGN(n)` | `-1`, `0`, or `1` |
|
|
62
|
+
| `FACT(n)` | Factorial |
|
|
63
|
+
| `PI()` | 3.14159… |
|
|
64
|
+
| `EXP(n)` | `e^n` |
|
|
65
|
+
| `LN(n)` / `LOG(n, base?)` / `LOG10(n)` | Logarithms |
|
|
66
|
+
| `RAND()` | 0 ≤ x < 1 |
|
|
67
|
+
| `RANDBETWEEN(lo, hi)` | Integer in range |
|
|
68
|
+
|
|
69
|
+
### Text (19)
|
|
70
|
+
|
|
71
|
+
| Function | Purpose |
|
|
72
|
+
|----------|---------|
|
|
73
|
+
| `UPPER(s)` / `LOWER(s)` / `PROPER(s)` | Case conversion |
|
|
74
|
+
| `LEN(s)` | String length |
|
|
75
|
+
| `TRIM(s)` | Strip leading/trailing whitespace |
|
|
76
|
+
| `LEFT(s, n)` / `RIGHT(s, n)` / `MID(s, start, n)` | Substrings |
|
|
77
|
+
| `FIND(needle, haystack, start?)` | Case-sensitive position |
|
|
78
|
+
| `SEARCH(needle, haystack, start?)` | Case-insensitive position |
|
|
79
|
+
| `SUBSTITUTE(s, old, new, occurrence?)` | Replace |
|
|
80
|
+
| `REPLACE(s, start, count, new)` | Positional replace |
|
|
81
|
+
| `CONCAT(...args)` | Join strings |
|
|
82
|
+
| `REPT(s, n)` | Repeat |
|
|
83
|
+
| `EXACT(a, b)` | Case-sensitive equality |
|
|
84
|
+
| `VALUE(s)` | String → number |
|
|
85
|
+
| `TEXT(n, fmt)` | Number → formatted string |
|
|
86
|
+
| `CHAR(code)` / `CODE(s)` | Code ↔ character |
|
|
87
|
+
|
|
88
|
+
### Logic (8)
|
|
89
|
+
|
|
90
|
+
| Function | Purpose |
|
|
91
|
+
|----------|---------|
|
|
92
|
+
| `IF(cond, then, else)` | Conditional |
|
|
93
|
+
| `AND(...args)` / `OR(...args)` / `NOT(a)` | Boolean |
|
|
94
|
+
| `IFERROR(val, fallback)` | Catch errors |
|
|
95
|
+
| `IFBLANK(val, fallback)` | Blank-cell fallback |
|
|
96
|
+
| `SWITCH(expr, case1, val1, ..., default?)` | Multi-branch |
|
|
97
|
+
| `CHOOSE(index, v1, v2, ...)` | Pick by 1-based index |
|
|
98
|
+
|
|
99
|
+
### Conditional Aggregates (8)
|
|
100
|
+
|
|
101
|
+
| Function | Purpose |
|
|
102
|
+
|----------|---------|
|
|
103
|
+
| `SUMIF(range, criterion, sumRange?)` | Sum matching |
|
|
104
|
+
| `SUMIFS(sumRange, r1, c1, ...)` | Multi-criteria sum |
|
|
105
|
+
| `COUNTIF(range, criterion)` | Count matching |
|
|
106
|
+
| `COUNTIFS(r1, c1, ...)` | Multi-criteria count |
|
|
107
|
+
| `AVERAGEIF(range, criterion, avgRange?)` | Average matching |
|
|
108
|
+
| `AVERAGEIFS(avgRange, r1, c1, ...)` | Multi-criteria average |
|
|
109
|
+
| `MINIFS(minRange, r1, c1, ...)` | Min matching |
|
|
110
|
+
| `MAXIFS(maxRange, r1, c1, ...)` | Max matching |
|
|
111
|
+
|
|
112
|
+
Criteria support operators as strings: `">100"`, `"<=50"`, `"<>"`, `"=apple"`.
|
|
113
|
+
|
|
114
|
+
### Lookup (8)
|
|
115
|
+
|
|
116
|
+
| Function | Purpose |
|
|
117
|
+
|----------|---------|
|
|
118
|
+
| `VLOOKUP(value, range, colIndex, exact?)` | Vertical lookup |
|
|
119
|
+
| `HLOOKUP(value, range, rowIndex, exact?)` | Horizontal lookup |
|
|
120
|
+
| `INDEX(range, row, col?)` | Index into a range |
|
|
121
|
+
| `MATCH(value, range, type?)` | Find position |
|
|
122
|
+
| `ROWS(range)` / `COLUMNS(range)` | Dimensions |
|
|
123
|
+
| `ROW(ref?)` / `COLUMN(ref?)` | Position of a ref |
|
|
124
|
+
|
|
125
|
+
### Date / Time (12)
|
|
126
|
+
|
|
127
|
+
| Function | Purpose |
|
|
128
|
+
|----------|---------|
|
|
129
|
+
| `TODAY()` | Current date |
|
|
130
|
+
| `NOW()` | Current date + time |
|
|
131
|
+
| `DATE(y, m, d)` | Build a date |
|
|
132
|
+
| `YEAR(date)` / `MONTH(date)` / `DAY(date)` | Extract parts |
|
|
133
|
+
| `HOUR(t)` / `MINUTE(t)` / `SECOND(t)` | Extract time parts |
|
|
134
|
+
| `WEEKDAY(date, type?)` | Day of week |
|
|
135
|
+
| `DATEDIF(start, end, unit)` | Difference (`"Y"`, `"M"`, `"D"`) |
|
|
136
|
+
| `EDATE(start, months)` | Offset by months |
|
|
137
|
+
|
|
138
|
+
### Info (6)
|
|
139
|
+
|
|
140
|
+
| Function | Purpose |
|
|
141
|
+
|----------|---------|
|
|
142
|
+
| `ISBLANK(ref)` | Cell is empty |
|
|
143
|
+
| `ISNUMBER(val)` / `ISTEXT(val)` / `ISLOGICAL(val)` | Type checks |
|
|
144
|
+
| `ISERROR(val)` | Is an error value |
|
|
145
|
+
| `TYPE(val)` | Type code (1=number, 2=text, 4=logical, 16=error) |
|
|
146
|
+
|
|
147
|
+
## Registering Custom Functions
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
import { registerFunction } from "@particle-academy/fancy-sheets";
|
|
151
|
+
|
|
152
|
+
registerFunction("GREET", (name: string) => `Hello, ${name}`);
|
|
153
|
+
|
|
154
|
+
// In a cell: =GREET("world") → "Hello, world"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Custom functions are called with resolved arguments (cell values, not tokens). Return any `CellValue`. Throwing is allowed — the value becomes `#ERROR!` in the grid.
|
|
158
|
+
|
|
159
|
+
## Dependency Tracking
|
|
160
|
+
|
|
161
|
+
The evaluator builds a dependency graph between cells. When a cell changes, only transitively dependent cells are re-evaluated, in topological order. Circular references are detected and reported as `#CIRC!`.
|
package/package.json
CHANGED
|
@@ -1,56 +1,63 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@particle-academy/fancy-sheets",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Spreadsheet editor with formula engine, multi-sheet tabs, and full cell editing",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"react": "^
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@particle-academy/fancy-sheets",
|
|
3
|
+
"version": "0.4.6",
|
|
4
|
+
"description": "Spreadsheet editor with formula engine, multi-sheet tabs, and full cell editing",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Particle-Academy/fancy-sheets.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/Particle-Academy/fancy-sheets#readme",
|
|
10
|
+
"bugs": "https://github.com/Particle-Academy/fancy-sheets/issues",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"require": {
|
|
22
|
+
"types": "./dist/index.d.cts",
|
|
23
|
+
"default": "./dist/index.cjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"./styles.css": "./dist/styles.css"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"docs",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup",
|
|
35
|
+
"dev": "tsup --watch",
|
|
36
|
+
"lint": "tsc --noEmit",
|
|
37
|
+
"clean": "rm -rf dist",
|
|
38
|
+
"prepublishOnly": "tsup"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
42
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
43
|
+
"@particle-academy/react-fancy": "^1.5.0"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@particle-academy/react-fancy": "*",
|
|
48
|
+
"@types/react": "^19.0.0",
|
|
49
|
+
"@types/react-dom": "^19.0.0",
|
|
50
|
+
"react": "^19.0.0",
|
|
51
|
+
"react-dom": "^19.0.0",
|
|
52
|
+
"tsup": "^8.5.0",
|
|
53
|
+
"typescript": "^5.8.0"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"sideEffects": [
|
|
60
|
+
"**/*.css",
|
|
61
|
+
"src/engine/formula/functions/*.ts"
|
|
62
|
+
]
|
|
63
|
+
}
|