@particle-academy/fancy-sheets 0.6.4 → 0.7.1

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
@@ -32,6 +32,8 @@ interface CellFormat {
32
32
  borderBottom?: string;
33
33
  /** Left border color (renders 1px solid) */
34
34
  borderLeft?: string;
35
+ /** Custom CSS class(es) applied to the cell element */
36
+ className?: string;
35
37
  }
36
38
  /** Cell comment */
37
39
  interface CellComment {
@@ -42,6 +44,17 @@ interface CellComment {
42
44
  /** Comment color — used for the corner triangle indicator and cell border (default: #f59e0b / amber-500) */
43
45
  color?: string;
44
46
  }
47
+ /** Consumer-driven cell highlight — rendered as a visual overlay, independent of selection and format */
48
+ interface CellHighlight {
49
+ /** Border/outline color (any CSS color value) */
50
+ color: string;
51
+ /** Background tint — if omitted and color is hex, auto-derived at 10% opacity */
52
+ backgroundColor?: string;
53
+ /** Small label badge in the cell's top-left corner (e.g., "var", "lbl") */
54
+ label?: string;
55
+ }
56
+ /** Map of cell addresses to their highlights */
57
+ type CellHighlightMap = Record<string, CellHighlight>;
45
58
  /** A single cell's complete data */
46
59
  interface CellData {
47
60
  /** The raw value (what the user typed) */
@@ -54,6 +67,8 @@ interface CellData {
54
67
  format?: CellFormat;
55
68
  /** Cell comment — shows a triangle indicator in the corner */
56
69
  comment?: CellComment;
70
+ /** Consumer-defined metadata — the package stores but never reads this */
71
+ meta?: Record<string, unknown>;
57
72
  }
58
73
 
59
74
  /** Sparse cell map — only stores cells that have data */
@@ -135,6 +150,10 @@ interface SpreadsheetProps {
135
150
  readOnly?: boolean;
136
151
  /** Custom context menu items appended after built-in items */
137
152
  contextMenuItems?: SpreadsheetContextMenuItem[] | ((address: string) => SpreadsheetContextMenuItem[]);
153
+ /** Consumer-driven cell highlights — rendered as visual overlays, independent of selection */
154
+ highlights?: CellHighlightMap;
155
+ /** Fires when the active cell changes */
156
+ onActiveCellChange?: (address: string, cell: CellData | undefined) => void;
138
157
  }
139
158
  interface SpreadsheetContextValue {
140
159
  workbook: WorkbookData;
@@ -174,6 +193,8 @@ interface SpreadsheetContextValue {
174
193
  isCellActive: (address: string) => boolean;
175
194
  /** Custom context menu items from consumer */
176
195
  contextMenuItems?: SpreadsheetContextMenuItem[] | ((address: string) => SpreadsheetContextMenuItem[]);
196
+ /** Consumer-provided cell highlights */
197
+ highlights: CellHighlightMap;
177
198
  /** @internal drag-to-select state */
178
199
  _isDragging: React.RefObject<boolean>;
179
200
  }
@@ -209,7 +230,7 @@ declare namespace SpreadsheetSheetTabs {
209
230
  var displayName: string;
210
231
  }
211
232
 
212
- declare function SpreadsheetRoot({ children, className, data, defaultData, onChange, columnCount, rowCount, defaultColumnWidth, rowHeight, readOnly, contextMenuItems, }: SpreadsheetProps): react_jsx_runtime.JSX.Element;
233
+ declare function SpreadsheetRoot({ children, className, data, defaultData, onChange, columnCount, rowCount, defaultColumnWidth, rowHeight, readOnly, contextMenuItems, highlights, onActiveCellChange, }: SpreadsheetProps): react_jsx_runtime.JSX.Element;
213
234
  declare namespace SpreadsheetRoot {
214
235
  var displayName: string;
215
236
  }
@@ -239,8 +260,12 @@ interface SheetProps {
239
260
  readOnly?: boolean;
240
261
  /** Custom context menu items */
241
262
  contextMenuItems?: SpreadsheetContextMenuItem[] | ((address: string) => SpreadsheetContextMenuItem[]);
263
+ /** Consumer-driven cell highlights */
264
+ highlights?: CellHighlightMap;
265
+ /** Fires when the active cell changes */
266
+ onActiveCellChange?: (address: string, cell: CellData | undefined) => void;
242
267
  }
243
- declare function Sheet({ data, onChange, contextMenuItems, ...props }: SheetProps): react_jsx_runtime.JSX.Element;
268
+ declare function Sheet({ data, onChange, contextMenuItems, highlights, onActiveCellChange, ...props }: SheetProps): react_jsx_runtime.JSX.Element;
244
269
  declare namespace Sheet {
245
270
  var displayName: string;
246
271
  }
@@ -273,8 +298,12 @@ interface SheetWorkbookProps {
273
298
  toolbarButtons?: ToolbarButton[];
274
299
  /** Custom context menu items */
275
300
  contextMenuItems?: SpreadsheetContextMenuItem[] | ((address: string) => SpreadsheetContextMenuItem[]);
301
+ /** Consumer-driven cell highlights */
302
+ highlights?: CellHighlightMap;
303
+ /** Fires when the active cell changes */
304
+ onActiveCellChange?: (address: string, cell: CellData | undefined) => void;
276
305
  }
277
- declare function SheetWorkbook({ hideToolbar, hideTabs, toolbarExtra, toolbarButtons, contextMenuItems, ...props }: SheetWorkbookProps): react_jsx_runtime.JSX.Element;
306
+ declare function SheetWorkbook({ hideToolbar, hideTabs, toolbarExtra, toolbarButtons, contextMenuItems, highlights, onActiveCellChange, ...props }: SheetWorkbookProps): react_jsx_runtime.JSX.Element;
278
307
  declare namespace SheetWorkbook {
279
308
  var displayName: string;
280
309
  }
@@ -303,4 +332,4 @@ declare function workbookToCSV(workbook: WorkbookData, sheetId?: string): string
303
332
  type FormulaRangeFunction = (args: CellValue[][]) => CellValue;
304
333
  declare function registerFunction(name: string, fn: FormulaRangeFunction): void;
305
334
 
306
- export { type CellAddress, type CellComment, type CellData, type CellFormat, 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 };
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 };
package/dist/index.d.ts CHANGED
@@ -32,6 +32,8 @@ interface CellFormat {
32
32
  borderBottom?: string;
33
33
  /** Left border color (renders 1px solid) */
34
34
  borderLeft?: string;
35
+ /** Custom CSS class(es) applied to the cell element */
36
+ className?: string;
35
37
  }
36
38
  /** Cell comment */
37
39
  interface CellComment {
@@ -42,6 +44,17 @@ interface CellComment {
42
44
  /** Comment color — used for the corner triangle indicator and cell border (default: #f59e0b / amber-500) */
43
45
  color?: string;
44
46
  }
47
+ /** Consumer-driven cell highlight — rendered as a visual overlay, independent of selection and format */
48
+ interface CellHighlight {
49
+ /** Border/outline color (any CSS color value) */
50
+ color: string;
51
+ /** Background tint — if omitted and color is hex, auto-derived at 10% opacity */
52
+ backgroundColor?: string;
53
+ /** Small label badge in the cell's top-left corner (e.g., "var", "lbl") */
54
+ label?: string;
55
+ }
56
+ /** Map of cell addresses to their highlights */
57
+ type CellHighlightMap = Record<string, CellHighlight>;
45
58
  /** A single cell's complete data */
46
59
  interface CellData {
47
60
  /** The raw value (what the user typed) */
@@ -54,6 +67,8 @@ interface CellData {
54
67
  format?: CellFormat;
55
68
  /** Cell comment — shows a triangle indicator in the corner */
56
69
  comment?: CellComment;
70
+ /** Consumer-defined metadata — the package stores but never reads this */
71
+ meta?: Record<string, unknown>;
57
72
  }
58
73
 
59
74
  /** Sparse cell map — only stores cells that have data */
@@ -135,6 +150,10 @@ interface SpreadsheetProps {
135
150
  readOnly?: boolean;
136
151
  /** Custom context menu items appended after built-in items */
137
152
  contextMenuItems?: SpreadsheetContextMenuItem[] | ((address: string) => SpreadsheetContextMenuItem[]);
153
+ /** Consumer-driven cell highlights — rendered as visual overlays, independent of selection */
154
+ highlights?: CellHighlightMap;
155
+ /** Fires when the active cell changes */
156
+ onActiveCellChange?: (address: string, cell: CellData | undefined) => void;
138
157
  }
139
158
  interface SpreadsheetContextValue {
140
159
  workbook: WorkbookData;
@@ -174,6 +193,8 @@ interface SpreadsheetContextValue {
174
193
  isCellActive: (address: string) => boolean;
175
194
  /** Custom context menu items from consumer */
176
195
  contextMenuItems?: SpreadsheetContextMenuItem[] | ((address: string) => SpreadsheetContextMenuItem[]);
196
+ /** Consumer-provided cell highlights */
197
+ highlights: CellHighlightMap;
177
198
  /** @internal drag-to-select state */
178
199
  _isDragging: React.RefObject<boolean>;
179
200
  }
@@ -209,7 +230,7 @@ declare namespace SpreadsheetSheetTabs {
209
230
  var displayName: string;
210
231
  }
211
232
 
212
- declare function SpreadsheetRoot({ children, className, data, defaultData, onChange, columnCount, rowCount, defaultColumnWidth, rowHeight, readOnly, contextMenuItems, }: SpreadsheetProps): react_jsx_runtime.JSX.Element;
233
+ declare function SpreadsheetRoot({ children, className, data, defaultData, onChange, columnCount, rowCount, defaultColumnWidth, rowHeight, readOnly, contextMenuItems, highlights, onActiveCellChange, }: SpreadsheetProps): react_jsx_runtime.JSX.Element;
213
234
  declare namespace SpreadsheetRoot {
214
235
  var displayName: string;
215
236
  }
@@ -239,8 +260,12 @@ interface SheetProps {
239
260
  readOnly?: boolean;
240
261
  /** Custom context menu items */
241
262
  contextMenuItems?: SpreadsheetContextMenuItem[] | ((address: string) => SpreadsheetContextMenuItem[]);
263
+ /** Consumer-driven cell highlights */
264
+ highlights?: CellHighlightMap;
265
+ /** Fires when the active cell changes */
266
+ onActiveCellChange?: (address: string, cell: CellData | undefined) => void;
242
267
  }
243
- declare function Sheet({ data, onChange, contextMenuItems, ...props }: SheetProps): react_jsx_runtime.JSX.Element;
268
+ declare function Sheet({ data, onChange, contextMenuItems, highlights, onActiveCellChange, ...props }: SheetProps): react_jsx_runtime.JSX.Element;
244
269
  declare namespace Sheet {
245
270
  var displayName: string;
246
271
  }
@@ -273,8 +298,12 @@ interface SheetWorkbookProps {
273
298
  toolbarButtons?: ToolbarButton[];
274
299
  /** Custom context menu items */
275
300
  contextMenuItems?: SpreadsheetContextMenuItem[] | ((address: string) => SpreadsheetContextMenuItem[]);
301
+ /** Consumer-driven cell highlights */
302
+ highlights?: CellHighlightMap;
303
+ /** Fires when the active cell changes */
304
+ onActiveCellChange?: (address: string, cell: CellData | undefined) => void;
276
305
  }
277
- declare function SheetWorkbook({ hideToolbar, hideTabs, toolbarExtra, toolbarButtons, contextMenuItems, ...props }: SheetWorkbookProps): react_jsx_runtime.JSX.Element;
306
+ declare function SheetWorkbook({ hideToolbar, hideTabs, toolbarExtra, toolbarButtons, contextMenuItems, highlights, onActiveCellChange, ...props }: SheetWorkbookProps): react_jsx_runtime.JSX.Element;
278
307
  declare namespace SheetWorkbook {
279
308
  var displayName: string;
280
309
  }
@@ -303,4 +332,4 @@ declare function workbookToCSV(workbook: WorkbookData, sheetId?: string): string
303
332
  type FormulaRangeFunction = (args: CellValue[][]) => CellValue;
304
333
  declare function registerFunction(name: string, fn: FormulaRangeFunction): void;
305
334
 
306
- export { type CellAddress, type CellComment, type CellData, type CellFormat, 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 };
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 };
package/dist/index.js CHANGED
@@ -1343,6 +1343,8 @@ function reducer(state, action) {
1343
1343
  const sheet = getActiveSheet(state);
1344
1344
  const existing = sheet.cells[action.address];
1345
1345
  if (existing?.format) cellData.format = existing.format;
1346
+ if (existing?.meta) cellData.meta = existing.meta;
1347
+ if (existing?.comment) cellData.comment = existing.comment;
1346
1348
  const updatedWorkbook = updateActiveSheet(state, (s) => ({
1347
1349
  ...s,
1348
1350
  cells: { ...s.cells, [action.address]: cellData }
@@ -1799,9 +1801,11 @@ var Cell = memo(function Cell2({ address, row, col }) {
1799
1801
  getColumnWidth,
1800
1802
  isCellSelected,
1801
1803
  isCellActive,
1804
+ highlights,
1802
1805
  _isDragging
1803
1806
  } = useSpreadsheet();
1804
1807
  const cell = activeSheet.cells[address];
1808
+ const highlight = highlights[address];
1805
1809
  const isActive = isCellActive(address);
1806
1810
  const isSelected = isCellSelected(address);
1807
1811
  const isEditing = editingCell === address;
@@ -1869,11 +1873,13 @@ var Cell = memo(function Cell2({ address, row, col }) {
1869
1873
  "data-fancy-sheets-cell": "",
1870
1874
  "data-selected": isSelected || void 0,
1871
1875
  "data-active": isActive || void 0,
1876
+ "data-highlighted": !!highlight || void 0,
1872
1877
  role: "gridcell",
1873
1878
  className: cn(
1874
1879
  "relative flex items-center truncate border-r border-b border-zinc-200 bg-white px-1.5 text-[13px] select-none dark:border-zinc-700 dark:bg-zinc-900",
1875
1880
  isActive && "ring-2 ring-inset ring-blue-500",
1876
- isSelected && !isActive && "bg-blue-50 dark:bg-blue-950/40"
1881
+ isSelected && !isActive && "bg-blue-50 dark:bg-blue-950/40",
1882
+ cell?.format?.className
1877
1883
  ),
1878
1884
  style: { width, minWidth: width, height: rowHeight, ...formatStyle },
1879
1885
  onMouseDown: handleMouseDown,
@@ -1887,6 +1893,27 @@ var Cell = memo(function Cell2({ address, row, col }) {
1887
1893
  },
1888
1894
  onDoubleClick: handleDoubleClick,
1889
1895
  children: [
1896
+ highlight && /* @__PURE__ */ jsx(
1897
+ "div",
1898
+ {
1899
+ className: "pointer-events-none absolute inset-0",
1900
+ style: {
1901
+ outline: `2px solid ${highlight.color}`,
1902
+ outlineOffset: "-2px",
1903
+ backgroundColor: highlight.backgroundColor ?? (highlight.color.startsWith("#") ? `${highlight.color}1A` : void 0)
1904
+ },
1905
+ "aria-hidden": true
1906
+ }
1907
+ ),
1908
+ highlight?.label && /* @__PURE__ */ jsx(
1909
+ "span",
1910
+ {
1911
+ className: "pointer-events-none absolute top-0 left-0 z-[1] px-0.5 text-[8px] font-bold leading-none text-white",
1912
+ style: { backgroundColor: highlight.color },
1913
+ "aria-hidden": true,
1914
+ children: highlight.label
1915
+ }
1916
+ ),
1890
1917
  !isEditing && /* @__PURE__ */ jsx("span", { className: "truncate", children: displayValue }),
1891
1918
  comment && /* @__PURE__ */ jsx(
1892
1919
  "div",
@@ -2582,7 +2609,9 @@ function SpreadsheetRoot({
2582
2609
  defaultColumnWidth = 100,
2583
2610
  rowHeight = 28,
2584
2611
  readOnly = false,
2585
- contextMenuItems
2612
+ contextMenuItems,
2613
+ highlights,
2614
+ onActiveCellChange
2586
2615
  }) {
2587
2616
  const { state, actions } = useSpreadsheetStore(data ?? defaultData);
2588
2617
  const onChangeRef = useRef(onChange);
@@ -2628,7 +2657,15 @@ function SpreadsheetRoot({
2628
2657
  (address) => state.selection.activeCell === address,
2629
2658
  [state.selection.activeCell]
2630
2659
  );
2660
+ const onActiveCellChangeRef = useRef(onActiveCellChange);
2661
+ onActiveCellChangeRef.current = onActiveCellChange;
2662
+ useEffect(() => {
2663
+ const addr = state.selection.activeCell;
2664
+ const cell = activeSheet.cells[addr];
2665
+ onActiveCellChangeRef.current?.(addr, cell);
2666
+ }, [state.selection.activeCell, activeSheet]);
2631
2667
  const isDraggingRef = useRef(false);
2668
+ const highlightsResolved = highlights ?? {};
2632
2669
  const ctx = useMemo(
2633
2670
  () => ({
2634
2671
  workbook: state.workbook,
@@ -2648,9 +2685,10 @@ function SpreadsheetRoot({
2648
2685
  isCellSelected,
2649
2686
  isCellActive,
2650
2687
  contextMenuItems,
2688
+ highlights: highlightsResolved,
2651
2689
  _isDragging: isDraggingRef
2652
2690
  }),
2653
- [state, activeSheet, columnCount, rowCount, defaultColumnWidth, rowHeight, readOnly, actions, getColumnWidth, isCellSelected, isCellActive, contextMenuItems]
2691
+ [state, activeSheet, columnCount, rowCount, defaultColumnWidth, rowHeight, readOnly, actions, getColumnWidth, isCellSelected, isCellActive, contextMenuItems, highlightsResolved]
2654
2692
  );
2655
2693
  return /* @__PURE__ */ jsx(SpreadsheetContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
2656
2694
  "div",
@@ -2667,7 +2705,7 @@ var Spreadsheet = Object.assign(SpreadsheetRoot, {
2667
2705
  Grid: SpreadsheetGrid,
2668
2706
  SheetTabs: SpreadsheetSheetTabs
2669
2707
  });
2670
- function Sheet({ data, onChange, contextMenuItems, ...props }) {
2708
+ function Sheet({ data, onChange, contextMenuItems, highlights, onActiveCellChange, ...props }) {
2671
2709
  const workbook = useMemo(
2672
2710
  () => data ? { sheets: [data], activeSheetId: data.id } : void 0,
2673
2711
  [data]
@@ -2676,7 +2714,7 @@ function Sheet({ data, onChange, contextMenuItems, ...props }) {
2676
2714
  if (!onChange) return void 0;
2677
2715
  return (wb) => onChange(wb.sheets[0]);
2678
2716
  }, [onChange]);
2679
- return /* @__PURE__ */ jsx(Spreadsheet, { data: workbook, onChange: handleChange, contextMenuItems, ...props, children: /* @__PURE__ */ jsx(Spreadsheet.Grid, {}) });
2717
+ return /* @__PURE__ */ jsx(Spreadsheet, { data: workbook, onChange: handleChange, contextMenuItems, highlights, onActiveCellChange, ...props, children: /* @__PURE__ */ jsx(Spreadsheet.Grid, {}) });
2680
2718
  }
2681
2719
  Sheet.displayName = "Sheet";
2682
2720
  function SheetWorkbook({
@@ -2685,9 +2723,11 @@ function SheetWorkbook({
2685
2723
  toolbarExtra,
2686
2724
  toolbarButtons,
2687
2725
  contextMenuItems,
2726
+ highlights,
2727
+ onActiveCellChange,
2688
2728
  ...props
2689
2729
  }) {
2690
- return /* @__PURE__ */ jsxs(Spreadsheet, { ...props, contextMenuItems, children: [
2730
+ return /* @__PURE__ */ jsxs(Spreadsheet, { ...props, contextMenuItems, highlights, onActiveCellChange, children: [
2691
2731
  !hideToolbar && /* @__PURE__ */ jsx(Spreadsheet.Toolbar, { extra: toolbarExtra, buttons: toolbarButtons }),
2692
2732
  /* @__PURE__ */ jsx(Spreadsheet.Grid, {}),
2693
2733
  !hideTabs && /* @__PURE__ */ jsx(Spreadsheet.SheetTabs, {})