@particle-academy/fancy-sheets 0.7.1 → 0.7.3

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 CHANGED
@@ -66,6 +66,19 @@ pnpm --filter @particle-academy/fancy-sheets clean # Remove dist/
66
66
  - **Features** — editing, navigation, selection (single, range, multi-range), formatting (bold/italic/align), clipboard (copy/cut/paste with TSV), column resize, freeze rows/cols, undo/redo (50 steps), drag-to-select
67
67
  - **Zero third-party dependencies** — custom lexer, parser, evaluator, and dependency graph
68
68
 
69
+ ## Inertia.js integration
70
+
71
+ Spreadsheet uses canvas grid measurement and is **not SSR-safe**. In an Inertia app, wrap with [`<FancyClientOnly>`](https://github.com/Particle-Academy/fancy-inertia/blob/main/docs/USAGE.md#fancyclientonly) from `@particle-academy/fancy-inertia`:
72
+
73
+ ```tsx
74
+ import { FancyClientOnly } from "@particle-academy/fancy-inertia";
75
+ import { Spreadsheet } from "@particle-academy/fancy-sheets";
76
+
77
+ <FancyClientOnly fallback={<div className="h-96 animate-pulse rounded bg-zinc-100" />}>
78
+ <Spreadsheet rows={100} cols={26} />
79
+ </FancyClientOnly>
80
+ ```
81
+
69
82
  ## License
70
83
 
71
84
  MIT
package/dist/index.cjs CHANGED
@@ -214,6 +214,7 @@ function lexFormula(input) {
214
214
  }
215
215
 
216
216
  // src/engine/formula/parser.ts
217
+ var MAX_DEPTH = 64;
217
218
  function parseFormula(tokens) {
218
219
  let pos = 0;
219
220
  function peek() {
@@ -229,63 +230,75 @@ function parseFormula(tokens) {
229
230
  }
230
231
  return t;
231
232
  }
232
- function parseExpression() {
233
- return parseComparison();
233
+ function checkDepth(depth) {
234
+ if (depth > MAX_DEPTH) {
235
+ throw new Error(`Formula nested too deep (max ${MAX_DEPTH} levels)`);
236
+ }
237
+ }
238
+ function parseExpression(depth = 0) {
239
+ checkDepth(depth);
240
+ return parseComparison(depth + 1);
234
241
  }
235
- function parseComparison() {
236
- let left = parseConcatenation();
242
+ function parseComparison(depth) {
243
+ let left = parseConcatenation(depth + 1);
237
244
  while (peek() && peek().type === "operator" && ["=", "<>", "<", ">", "<=", ">="].includes(peek().value)) {
238
245
  const op = advance().value;
239
- const right = parseConcatenation();
246
+ const right = parseConcatenation(depth + 1);
240
247
  left = { type: "binaryOp", operator: op, left, right };
241
248
  }
242
249
  return left;
243
250
  }
244
- function parseConcatenation() {
245
- let left = parseAddition();
251
+ function parseConcatenation(depth) {
252
+ checkDepth(depth);
253
+ let left = parseAddition(depth + 1);
246
254
  while (peek() && peek().type === "operator" && peek().value === "&") {
247
255
  advance();
248
- const right = parseAddition();
256
+ const right = parseAddition(depth + 1);
249
257
  left = { type: "binaryOp", operator: "&", left, right };
250
258
  }
251
259
  return left;
252
260
  }
253
- function parseAddition() {
254
- let left = parseMultiplication();
261
+ function parseAddition(depth) {
262
+ checkDepth(depth);
263
+ let left = parseMultiplication(depth + 1);
255
264
  while (peek() && peek().type === "operator" && (peek().value === "+" || peek().value === "-")) {
256
265
  const op = advance().value;
257
- const right = parseMultiplication();
266
+ const right = parseMultiplication(depth + 1);
258
267
  left = { type: "binaryOp", operator: op, left, right };
259
268
  }
260
269
  return left;
261
270
  }
262
- function parseMultiplication() {
263
- let left = parseExponentiation();
271
+ function parseMultiplication(depth) {
272
+ checkDepth(depth);
273
+ let left = parseExponentiation(depth + 1);
264
274
  while (peek() && peek().type === "operator" && (peek().value === "*" || peek().value === "/")) {
265
275
  const op = advance().value;
266
- const right = parseExponentiation();
276
+ const right = parseExponentiation(depth + 1);
267
277
  left = { type: "binaryOp", operator: op, left, right };
268
278
  }
269
279
  return left;
270
280
  }
271
- function parseExponentiation() {
272
- let left = parseUnary();
281
+ function parseExponentiation(depth) {
282
+ checkDepth(depth);
283
+ let left = parseUnary(depth + 1);
273
284
  while (peek() && peek().type === "operator" && peek().value === "^") {
274
285
  advance();
275
- const right = parseUnary();
286
+ const right = parseUnary(depth + 1);
276
287
  left = { type: "binaryOp", operator: "^", left, right };
277
288
  }
278
289
  return left;
279
290
  }
280
- function parseUnary() {
291
+ function parseUnary(depth) {
292
+ checkDepth(depth);
281
293
  if (peek() && peek().type === "operator" && (peek().value === "-" || peek().value === "+")) {
282
294
  const op = advance().value;
283
- const operand = parseUnary();
295
+ const operand = parseUnary(depth + 1);
284
296
  return { type: "unaryOp", operator: op, operand };
285
297
  }
286
- return parseAtom();
298
+ return parseAtom(depth + 1);
287
299
  }
288
- function parseAtom() {
300
+ function parseAtom(depth) {
301
+ checkDepth(depth);
289
302
  const t = peek();
290
303
  if (!t) throw new Error("Unexpected end of formula");
291
304
  if (t.type === "number") {
@@ -323,10 +336,10 @@ function parseFormula(tokens) {
323
336
  expect("paren", "(");
324
337
  const args = [];
325
338
  if (peek() && !(peek().type === "paren" && peek().value === ")")) {
326
- args.push(parseExpression());
339
+ args.push(parseExpression(depth + 1));
327
340
  while (peek() && peek().type === "comma") {
328
341
  advance();
329
- args.push(parseExpression());
342
+ args.push(parseExpression(depth + 1));
330
343
  }
331
344
  }
332
345
  expect("paren", ")");
@@ -338,13 +351,13 @@ function parseFormula(tokens) {
338
351
  }
339
352
  if (t.type === "paren" && t.value === "(") {
340
353
  advance();
341
- const expr = parseExpression();
354
+ const expr = parseExpression(depth + 1);
342
355
  expect("paren", ")");
343
356
  return expr;
344
357
  }
345
358
  throw new Error(`Unexpected token '${t.value}' at position ${t.position}`);
346
359
  }
347
- const ast = parseExpression();
360
+ const ast = parseExpression(0);
348
361
  return ast;
349
362
  }
350
363
 
@@ -866,8 +879,9 @@ registerFunction("VLOOKUP", (args) => {
866
879
  const colIdx = Number(flat[2] ?? flat[args[1]?.length ?? 0]);
867
880
  if (key === null || isNaN(colIdx)) return "#VALUE!";
868
881
  for (let i = 0; i < range.length; i++) {
869
- if (range[i] === key || typeof range[i] === "string" && typeof key === "string" && range[i].toLowerCase() === key.toLowerCase()) {
870
- return range[i] ?? "#N/A";
882
+ const v = range[i];
883
+ if (v === key || typeof v === "string" && typeof key === "string" && v.toLowerCase() === key.toLowerCase()) {
884
+ return v ?? "#N/A";
871
885
  }
872
886
  }
873
887
  return "#N/A";
@@ -877,8 +891,9 @@ registerFunction("HLOOKUP", (args) => {
877
891
  const key = flat[0];
878
892
  const range = args[1] ?? [];
879
893
  for (let i = 0; i < range.length; i++) {
880
- if (range[i] === key || typeof range[i] === "string" && typeof key === "string" && range[i].toLowerCase() === key.toLowerCase()) {
881
- return range[i] ?? "#N/A";
894
+ const v = range[i];
895
+ if (v === key || typeof v === "string" && typeof key === "string" && v.toLowerCase() === key.toLowerCase()) {
896
+ return v ?? "#N/A";
882
897
  }
883
898
  }
884
899
  return "#N/A";
@@ -895,10 +910,11 @@ registerFunction("MATCH", (args) => {
895
910
  const key = flat[0];
896
911
  const range = args[1] ?? [];
897
912
  for (let i = 0; i < range.length; i++) {
898
- if (range[i] === key || typeof range[i] === "number" && typeof key === "number" && range[i] === key) {
913
+ const v = range[i];
914
+ if (v === key || typeof v === "number" && typeof key === "number" && v === key) {
899
915
  return i + 1;
900
916
  }
901
- if (typeof range[i] === "string" && typeof key === "string" && range[i].toLowerCase() === key.toLowerCase()) {
917
+ if (typeof v === "string" && typeof key === "string" && v.toLowerCase() === key.toLowerCase()) {
902
918
  return i + 1;
903
919
  }
904
920
  }