@particle-academy/fancy-sheets 0.4.1 → 0.4.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/dist/index.d.cts CHANGED
@@ -16,6 +16,8 @@ interface CellFormat {
16
16
  textAlign?: TextAlign;
17
17
  /** Display format — controls how the value is rendered */
18
18
  displayFormat?: CellDisplayFormat;
19
+ /** Number of decimal places to display (for number/currency/percentage) */
20
+ decimals?: number;
19
21
  }
20
22
  /** A single cell's complete data */
21
23
  interface CellData {
package/dist/index.d.ts CHANGED
@@ -16,6 +16,8 @@ interface CellFormat {
16
16
  textAlign?: TextAlign;
17
17
  /** Display format — controls how the value is rendered */
18
18
  displayFormat?: CellDisplayFormat;
19
+ /** Number of decimal places to display (for number/currency/percentage) */
20
+ decimals?: number;
19
21
  }
20
22
  /** A single cell's complete data */
21
23
  interface CellData {
package/dist/index.js CHANGED
@@ -1235,10 +1235,15 @@ function getRecalculationOrder(graph) {
1235
1235
 
1236
1236
  // src/hooks/use-spreadsheet-store.ts
1237
1237
  function recalculateWorkbook(workbook) {
1238
- return {
1239
- ...workbook,
1240
- sheets: workbook.sheets.map((s) => recalculateSheet(s, workbook.sheets))
1241
- };
1238
+ const recalculated = [];
1239
+ for (const sheet of workbook.sheets) {
1240
+ recalculated.push(recalculateSheet(sheet, recalculated));
1241
+ }
1242
+ const finalSheets = [];
1243
+ for (const sheet of recalculated) {
1244
+ finalSheets.push(recalculateSheet(sheet, recalculated));
1245
+ }
1246
+ return { ...workbook, sheets: finalSheets };
1242
1247
  }
1243
1248
  function createInitialState(data) {
1244
1249
  const workbook = data ?? createEmptyWorkbook();
@@ -1338,10 +1343,11 @@ function reducer(state, action) {
1338
1343
  const sheet = getActiveSheet(state);
1339
1344
  const existing = sheet.cells[action.address];
1340
1345
  if (existing?.format) cellData.format = existing.format;
1341
- const workbook = updateActiveSheet(state, (s) => {
1342
- const updated = { ...s, cells: { ...s.cells, [action.address]: cellData } };
1343
- return recalculateSheet(updated, state.workbook.sheets);
1344
- });
1346
+ const updatedWorkbook = updateActiveSheet(state, (s) => ({
1347
+ ...s,
1348
+ cells: { ...s.cells, [action.address]: cellData }
1349
+ }));
1350
+ const workbook = recalculateWorkbook(updatedWorkbook);
1345
1351
  return { ...state, workbook, ...history };
1346
1352
  }
1347
1353
  case "SET_CELL_FORMAT": {
@@ -1599,7 +1605,7 @@ function ColumnResizeHandle({ colIndex }) {
1599
1605
  }
1600
1606
  ColumnResizeHandle.displayName = "ColumnResizeHandle";
1601
1607
  function ColumnHeaders() {
1602
- const { columnCount, rowCount, rowHeight, getColumnWidth, selection, selectRange, _isDragging } = useSpreadsheet();
1608
+ const { columnCount, rowCount, rowHeight, getColumnWidth, selection, selectRange, _isDragging, isCellSelected } = useSpreadsheet();
1603
1609
  const handleColumnMouseDown = useCallback(
1604
1610
  (colIdx, e) => {
1605
1611
  if (e.button !== 0) return;
@@ -1643,28 +1649,35 @@ function ColumnHeaders() {
1643
1649
  style: { width: 48, minWidth: 48 }
1644
1650
  }
1645
1651
  ),
1646
- Array.from({ length: columnCount }, (_, i) => /* @__PURE__ */ jsxs(
1647
- "div",
1648
- {
1649
- className: "relative flex shrink-0 cursor-pointer items-center justify-center border-r border-zinc-300 text-[11px] font-medium text-zinc-500 select-none hover:bg-zinc-200 dark:border-zinc-600 dark:text-zinc-400 dark:hover:bg-zinc-700",
1650
- style: { width: getColumnWidth(i), minWidth: getColumnWidth(i) },
1651
- onMouseDown: (e) => handleColumnMouseDown(i, e),
1652
- onMouseEnter: () => handleColumnMouseEnter(i),
1653
- onMouseUp: handleMouseUp,
1654
- children: [
1655
- columnToLetter(i),
1656
- /* @__PURE__ */ jsx(ColumnResizeHandle, { colIndex: i })
1657
- ]
1658
- },
1659
- i
1660
- ))
1652
+ Array.from({ length: columnCount }, (_, i) => {
1653
+ const isColSelected = isCellSelected(toAddress(0, i));
1654
+ return /* @__PURE__ */ jsxs(
1655
+ "div",
1656
+ {
1657
+ className: cn(
1658
+ "relative flex shrink-0 cursor-pointer items-center justify-center border-r border-zinc-300 text-[11px] font-medium select-none hover:bg-zinc-200 dark:border-zinc-600 dark:hover:bg-zinc-700",
1659
+ isColSelected ? "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300" : "text-zinc-500 dark:text-zinc-400"
1660
+ ),
1661
+ style: { width: getColumnWidth(i), minWidth: getColumnWidth(i) },
1662
+ onMouseDown: (e) => handleColumnMouseDown(i, e),
1663
+ onMouseEnter: () => handleColumnMouseEnter(i),
1664
+ onMouseUp: handleMouseUp,
1665
+ children: [
1666
+ columnToLetter(i),
1667
+ /* @__PURE__ */ jsx(ColumnResizeHandle, { colIndex: i })
1668
+ ]
1669
+ },
1670
+ i
1671
+ );
1672
+ })
1661
1673
  ]
1662
1674
  }
1663
1675
  );
1664
1676
  }
1665
1677
  ColumnHeaders.displayName = "ColumnHeaders";
1666
1678
  function RowHeader({ rowIndex }) {
1667
- const { rowHeight, columnCount, selection, selectRange, _isDragging } = useSpreadsheet();
1679
+ const { rowHeight, columnCount, selection, selectRange, _isDragging, isCellSelected } = useSpreadsheet();
1680
+ const isRowSelected = isCellSelected(toAddress(rowIndex, 0));
1668
1681
  const handleMouseDown = useCallback(
1669
1682
  (e) => {
1670
1683
  if (e.button !== 0) return;
@@ -1695,7 +1708,10 @@ function RowHeader({ rowIndex }) {
1695
1708
  "div",
1696
1709
  {
1697
1710
  "data-fancy-sheets-row-header": "",
1698
- className: "flex shrink-0 cursor-pointer items-center justify-center border-r border-b border-zinc-300 bg-zinc-100 text-[11px] font-medium text-zinc-500 select-none hover:bg-zinc-200 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-400 dark:hover:bg-zinc-700",
1711
+ className: cn(
1712
+ "flex shrink-0 cursor-pointer items-center justify-center border-r border-b border-zinc-300 text-[11px] font-medium select-none hover:bg-zinc-200 dark:border-zinc-600 dark:hover:bg-zinc-700",
1713
+ isRowSelected ? "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300" : "bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400"
1714
+ ),
1699
1715
  style: { width: 48, minWidth: 48, height: rowHeight },
1700
1716
  onMouseDown: handleMouseDown,
1701
1717
  onMouseEnter: handleMouseEnter,
@@ -1724,21 +1740,24 @@ function serialToDateTimeStr(serial) {
1724
1740
  }
1725
1741
  function isDateFormula(formula) {
1726
1742
  if (!formula) return false;
1727
- const f = formula.toUpperCase();
1728
- return /^(TODAY|NOW|DATE|EDATE)\b/.test(f) || /\b(TODAY|NOW|DATE|EDATE)\s*\(/.test(f);
1743
+ const f = formula.trim().toUpperCase();
1744
+ return /^(TODAY|NOW|DATE|EDATE)\s*\(/.test(f);
1729
1745
  }
1730
1746
  function formatCellValue(val, cell) {
1731
1747
  if (val === null || val === void 0) return "";
1732
1748
  const fmt = cell?.format?.displayFormat;
1733
1749
  if (typeof val === "number") {
1750
+ const dec = cell?.format?.decimals;
1734
1751
  if (fmt === "date") return serialToDateStr(val);
1735
1752
  if (fmt === "datetime") return serialToDateTimeStr(val);
1736
- if (fmt === "percentage") return (val * 100).toFixed(1) + "%";
1737
- if (fmt === "currency") return "$" + val.toFixed(2);
1753
+ if (fmt === "percentage") return (val * 100).toFixed(dec ?? 1) + "%";
1754
+ if (fmt === "currency") return "$" + val.toFixed(dec ?? 2);
1755
+ if (fmt === "number" && dec !== void 0) return val.toFixed(dec);
1738
1756
  if (fmt === "auto" || !fmt) {
1739
1757
  if (cell?.formula && isDateFormula(cell.formula)) {
1740
1758
  return val % 1 === 0 ? serialToDateStr(val) : serialToDateTimeStr(val);
1741
1759
  }
1760
+ if (dec !== void 0) return val.toFixed(dec);
1742
1761
  }
1743
1762
  }
1744
1763
  if (typeof val === "boolean") return val ? "TRUE" : "FALSE";
@@ -2182,6 +2201,8 @@ function DefaultToolbar() {
2182
2201
  const isBold = cell?.format?.bold ?? false;
2183
2202
  const isItalic = cell?.format?.italic ?? false;
2184
2203
  const textAlign = cell?.format?.textAlign ?? "left";
2204
+ const displayFormat = cell?.format?.displayFormat ?? "auto";
2205
+ const decimals = cell?.format?.decimals;
2185
2206
  const selectedAddresses = [selection.activeCell];
2186
2207
  const handleFormulaBarChange = (e) => {
2187
2208
  if (editingCell) {
@@ -2293,6 +2314,52 @@ function DefaultToolbar() {
2293
2314
  /* @__PURE__ */ jsx("line", { x1: "19", y1: "3", x2: "19", y2: "21", strokeDasharray: "3 3" })
2294
2315
  ] })
2295
2316
  }
2317
+ ),
2318
+ /* @__PURE__ */ jsx("div", { className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700" }),
2319
+ /* @__PURE__ */ jsxs(
2320
+ "select",
2321
+ {
2322
+ className: "h-6 rounded border border-zinc-200 bg-transparent px-1 text-[11px] text-zinc-600 outline-none hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-600",
2323
+ value: displayFormat,
2324
+ onChange: (e) => setCellFormat(selectedAddresses, { displayFormat: e.target.value }),
2325
+ disabled: readOnly,
2326
+ title: "Cell format",
2327
+ children: [
2328
+ /* @__PURE__ */ jsx("option", { value: "auto", children: "Auto" }),
2329
+ /* @__PURE__ */ jsx("option", { value: "text", children: "Text" }),
2330
+ /* @__PURE__ */ jsx("option", { value: "number", children: "Number" }),
2331
+ /* @__PURE__ */ jsx("option", { value: "currency", children: "Currency ($)" }),
2332
+ /* @__PURE__ */ jsx("option", { value: "percentage", children: "Percentage (%)" }),
2333
+ /* @__PURE__ */ jsx("option", { value: "date", children: "Date" }),
2334
+ /* @__PURE__ */ jsx("option", { value: "datetime", children: "Date & Time" })
2335
+ ]
2336
+ }
2337
+ ),
2338
+ /* @__PURE__ */ jsxs(
2339
+ "button",
2340
+ {
2341
+ className: btnClass,
2342
+ onClick: () => setCellFormat(selectedAddresses, { decimals: Math.max(0, (decimals ?? 0) - 1) }),
2343
+ disabled: readOnly || (decimals ?? 0) <= 0,
2344
+ title: "Decrease decimal places",
2345
+ children: [
2346
+ /* @__PURE__ */ jsx("span", { className: "text-[10px]", children: ".0" }),
2347
+ /* @__PURE__ */ jsx("span", { className: "text-[8px]", children: "\u2190" })
2348
+ ]
2349
+ }
2350
+ ),
2351
+ /* @__PURE__ */ jsxs(
2352
+ "button",
2353
+ {
2354
+ className: btnClass,
2355
+ onClick: () => setCellFormat(selectedAddresses, { decimals: (decimals ?? 0) + 1 }),
2356
+ disabled: readOnly,
2357
+ title: "Increase decimal places",
2358
+ children: [
2359
+ /* @__PURE__ */ jsx("span", { className: "text-[10px]", children: ".00" }),
2360
+ /* @__PURE__ */ jsx("span", { className: "text-[8px]", children: "\u2192" })
2361
+ ]
2362
+ }
2296
2363
  )
2297
2364
  ] }),
2298
2365
  /* @__PURE__ */ jsxs("div", { "data-fancy-sheets-formula-bar": "", className: "flex items-center gap-2 border-b border-zinc-200 px-2 py-1 dark:border-zinc-700", children: [