@ricsam/react-spreadsheets 0.0.0 → 0.0.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/README.md +143 -43
- package/dist/cjs/lib.cjs +4045 -0
- package/dist/cjs/lib.cjs.map +27 -0
- package/dist/cjs/package.json +5 -0
- package/dist/mjs/lib.mjs +3991 -0
- package/dist/mjs/lib.mjs.map +27 -0
- package/dist/mjs/package.json +5 -0
- package/dist/styles.css +483 -0
- package/dist/types/clipboard/clipboard-manager.d.ts +51 -0
- package/dist/types/grid/frame.d.ts +22 -0
- package/dist/types/grid/icons.d.ts +15 -0
- package/dist/types/grid/types.d.ts +178 -0
- package/dist/types/lib.d.ts +26 -0
- package/dist/types/spreadsheet/constants.d.ts +4 -0
- package/dist/types/spreadsheet/snapping.d.ts +22 -0
- package/dist/types/spreadsheet/spreadsheet.d.ts +88 -0
- package/dist/types/spreadsheet/utils.d.ts +28 -0
- package/dist/types/spreadsheet/zoom-constants.d.ts +5 -0
- package/dist/types/types.d.ts +33 -0
- package/dist/types/utils/cn.d.ts +8 -0
- package/dist/types/utils/use-click-away.d.ts +1 -0
- package/dist/types/utils/use-color-scheme.d.ts +10 -0
- package/dist/types/utils/use-resize-observer.d.ts +15 -0
- package/dist/types/workbook/border-sides.d.ts +20 -0
- package/dist/types/workbook/cell-data-type.d.ts +15 -0
- package/dist/types/workbook/cell-edit-error.d.ts +22 -0
- package/dist/types/workbook/workbook-selection-manager.d.ts +71 -0
- package/dist/types/workbook/workbook.d.ts +86 -0
- package/package.json +63 -6
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Initial state for a GridChild component
|
|
4
|
+
*/
|
|
5
|
+
export interface GridChildInitial {
|
|
6
|
+
/** Initial X coordinate in px */
|
|
7
|
+
x: number;
|
|
8
|
+
/** Initial Y coordinate in px */
|
|
9
|
+
y: number;
|
|
10
|
+
/** Initial width in px */
|
|
11
|
+
width: number;
|
|
12
|
+
/** Initial height in px */
|
|
13
|
+
height: number;
|
|
14
|
+
/** Initial zoom/scale (default: 1) */
|
|
15
|
+
zoom?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Represents a child component that can be positioned within the Grid.
|
|
19
|
+
* Each child has a position, size, and render function.
|
|
20
|
+
*/
|
|
21
|
+
export interface GridChild {
|
|
22
|
+
/** X coordinate in px */
|
|
23
|
+
x: number;
|
|
24
|
+
/** Y coordinate in px */
|
|
25
|
+
y: number;
|
|
26
|
+
/** Width in px */
|
|
27
|
+
width: number;
|
|
28
|
+
/** Height in px */
|
|
29
|
+
height: number;
|
|
30
|
+
/** Zoom/scale factor for the child (default: 1) */
|
|
31
|
+
zoom?: number;
|
|
32
|
+
/** Whether the child is minimized */
|
|
33
|
+
minimized?: boolean;
|
|
34
|
+
/** Whether the child is maximized (displayed full-screen instead of in grid) */
|
|
35
|
+
maximized?: boolean;
|
|
36
|
+
/** Initial state for reset functionality */
|
|
37
|
+
initial?: GridChildInitial;
|
|
38
|
+
/** Name of the component to render */
|
|
39
|
+
component: string;
|
|
40
|
+
/** Unique identifier for the child */
|
|
41
|
+
id: string;
|
|
42
|
+
/** Display title for the child */
|
|
43
|
+
title: string;
|
|
44
|
+
/**
|
|
45
|
+
* If true, the child will remain mounted even when outside the viewport.
|
|
46
|
+
* Useful for components that need to maintain state or perform background operations.
|
|
47
|
+
*/
|
|
48
|
+
persistent?: boolean;
|
|
49
|
+
/** Whether the child can be closed */
|
|
50
|
+
canClose?: boolean;
|
|
51
|
+
/** Props for the component */
|
|
52
|
+
props?: any;
|
|
53
|
+
}
|
|
54
|
+
export interface ChildState {
|
|
55
|
+
/** Whether the child is selected */
|
|
56
|
+
isSelected: boolean;
|
|
57
|
+
/** Whether the child is minimized */
|
|
58
|
+
isMinimized: boolean;
|
|
59
|
+
/** Whether the child is maximized */
|
|
60
|
+
isMaximized: boolean;
|
|
61
|
+
}
|
|
62
|
+
export type GridComponent = (gridChild: GridChild, state: ChildState) => ReactNode;
|
|
63
|
+
/**
|
|
64
|
+
* Configuration for a placeholder component that follows the mouse until placed.
|
|
65
|
+
*/
|
|
66
|
+
export interface PlaceholderConfig {
|
|
67
|
+
/** Type of component being placed */
|
|
68
|
+
componentType: string;
|
|
69
|
+
/** Title/name for the placeholder */
|
|
70
|
+
title: string;
|
|
71
|
+
/** Width of the placeholder in pixels */
|
|
72
|
+
width: number;
|
|
73
|
+
/** Height of the placeholder in pixels */
|
|
74
|
+
height: number;
|
|
75
|
+
/** Callback when the placeholder is placed - receives world coordinates */
|
|
76
|
+
onPlaced: (x: number, y: number) => void;
|
|
77
|
+
/** Optional callback when placement is cancelled */
|
|
78
|
+
onCancel?: () => void;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Options for zooming to a specific area
|
|
82
|
+
*/
|
|
83
|
+
export interface ZoomToOptions {
|
|
84
|
+
/** Target x coordinate */
|
|
85
|
+
x: number;
|
|
86
|
+
/** Target y coordinate */
|
|
87
|
+
y: number;
|
|
88
|
+
/** Target zoom level */
|
|
89
|
+
zoom?: number;
|
|
90
|
+
/** Whether to animate the transition (default: false) */
|
|
91
|
+
animate?: boolean;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Options for focusing on a child component
|
|
95
|
+
*/
|
|
96
|
+
export interface FocusChildOptions {
|
|
97
|
+
/** ID of the child to focus */
|
|
98
|
+
childId: string;
|
|
99
|
+
/** Padding around the child in pixels (default: 200) */
|
|
100
|
+
padding?: number;
|
|
101
|
+
/** Target zoom level (default: 1) */
|
|
102
|
+
zoom?: number;
|
|
103
|
+
/** Whether to animate the transition (default: false) */
|
|
104
|
+
animate?: boolean;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Grid component ref API for programmatic control
|
|
108
|
+
*/
|
|
109
|
+
export interface GridRef {
|
|
110
|
+
/** Zoom to a specific position and zoom level */
|
|
111
|
+
zoomTo: (options: ZoomToOptions) => void;
|
|
112
|
+
/** Focus on a specific child component */
|
|
113
|
+
focusChild: (options: FocusChildOptions) => void;
|
|
114
|
+
/** Get current viewport state */
|
|
115
|
+
getViewport: () => ViewportState;
|
|
116
|
+
/** Set viewport state */
|
|
117
|
+
setViewport: (viewport: ViewportState) => void;
|
|
118
|
+
/** Zoom in by one step */
|
|
119
|
+
zoomIn: () => void;
|
|
120
|
+
/** Zoom out by one step */
|
|
121
|
+
zoomOut: () => void;
|
|
122
|
+
/** Reset zoom to 100% */
|
|
123
|
+
resetZoom: () => void;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Props for the Grid component.
|
|
127
|
+
*/
|
|
128
|
+
export interface GridProps {
|
|
129
|
+
/** Array of child components to render in the grid */
|
|
130
|
+
children: GridChild[];
|
|
131
|
+
/** Map of component names to their render functions */
|
|
132
|
+
components: Record<string, GridComponent>;
|
|
133
|
+
/** Optional CSS styles for the grid container */
|
|
134
|
+
style?: CSSProperties;
|
|
135
|
+
/** Size of each grid cell in pixels (default: 20) */
|
|
136
|
+
gridSize?: number;
|
|
137
|
+
/** ID of the currently selected child */
|
|
138
|
+
selectedChildId?: string | null;
|
|
139
|
+
/** Callback when a child is selected */
|
|
140
|
+
onChildSelect?: (childId: string | null) => void;
|
|
141
|
+
/** Callback when a component is blurred (deselected) */
|
|
142
|
+
onComponentBlur?: (childId: string) => void;
|
|
143
|
+
/** Callback when viewport changes - used for saving state, not for controlling the viewport */
|
|
144
|
+
onViewportChange?: (viewport: ViewportState) => void;
|
|
145
|
+
/** Callback when children change (for controlled mode) */
|
|
146
|
+
onChildrenChange?: (children: GridChild[]) => void;
|
|
147
|
+
/** Callback when a child requests to be maximized/restored */
|
|
148
|
+
onMaximize?: (childId: string) => void;
|
|
149
|
+
/** Initial viewport state (used only on mount, not controlled) */
|
|
150
|
+
initialViewport?: ViewportState;
|
|
151
|
+
/** Whether to show the background grid lines (default: true) */
|
|
152
|
+
showGrid?: boolean;
|
|
153
|
+
/** Grid rendering style: 'lines' for grid lines, 'dots' for dots at intersections (default: 'lines') */
|
|
154
|
+
gridStyle?: 'lines' | 'dots';
|
|
155
|
+
/** Placeholder configuration for component placement flow */
|
|
156
|
+
placeholder?: PlaceholderConfig;
|
|
157
|
+
/** Whether to show built-in zoom controls (default: true) */
|
|
158
|
+
showZoomControls?: boolean;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Represents the current state of the viewport including position and zoom level.
|
|
162
|
+
*/
|
|
163
|
+
export interface ViewportState {
|
|
164
|
+
/** Current X offset of the viewport */
|
|
165
|
+
x: number;
|
|
166
|
+
/** Current Y offset of the viewport */
|
|
167
|
+
y: number;
|
|
168
|
+
/** Current zoom level (1.0 = 100%) */
|
|
169
|
+
zoom: number;
|
|
170
|
+
}
|
|
171
|
+
export type ResizeHandle = 'top' | 'right' | 'bottom' | 'left' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
172
|
+
export declare class ViewportStream {
|
|
173
|
+
currentViewport: ViewportState;
|
|
174
|
+
constructor(initialViewport: ViewportState);
|
|
175
|
+
private subscribers;
|
|
176
|
+
subscribe(callback: (viewport: ViewportState) => void): () => void;
|
|
177
|
+
next(nextViewport: ViewportState): void;
|
|
178
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ricsam/react-spreadsheets
|
|
3
|
+
*
|
|
4
|
+
* React spreadsheet primitives: an infinitely scrollable, virtualized grid with
|
|
5
|
+
* selection, editing, clipboard, fill handles, column/row resizing and floating
|
|
6
|
+
* overlays — plus optional bindings for `@ricsam/formula-engine`.
|
|
7
|
+
*
|
|
8
|
+
* Remember to import the stylesheet once in your app:
|
|
9
|
+
* import "@ricsam/react-spreadsheets/styles.css";
|
|
10
|
+
*/
|
|
11
|
+
export { Spreadsheet, type SpreadsheetProps, type SpreadsheetRef, type SpreadsheetChild, type SpreadsheetChildInitial, type SpreadsheetChildState, type SpreadsheetComponent, } from "./spreadsheet/spreadsheet";
|
|
12
|
+
export { FormulaSheet, FormulaWorkbook, WorkbookClipboardManager, WorkbookSelectionManager, } from "./workbook/workbook";
|
|
13
|
+
export { coerceCellInput, getCellDisplayText, getCellDisplayValue, } from "./workbook/cell-data-type";
|
|
14
|
+
export { normalizeBorderSides, hasAnyBorderSide, type BorderSides, type NormalizedBorderSides, } from "./workbook/border-sides";
|
|
15
|
+
export { onCellEditError, queueCellEditError, type CellEditErrorListener, } from "./workbook/cell-edit-error";
|
|
16
|
+
export type { SerializedCellValue, SpreadsheetColumnWidths, SpreadsheetRowHeights, CellRenderContext, ConditionalStyleCallbackData, } from "./types";
|
|
17
|
+
export { columnToIndex, indexToColumn, getRowNumber, getCellReference, parseCellReference, rowToLetter, letterToRow, findCell, extractColumn, extractColumnByHeader, } from "./spreadsheet/utils";
|
|
18
|
+
export { DEFAULT_CELL_WIDTH, DEFAULT_CELL_HEIGHT, HEADER_WIDTH, HEADER_HEIGHT, } from "./spreadsheet/constants";
|
|
19
|
+
export { DEFAULT_ZOOM, MIN_ZOOM, MAX_ZOOM, ZOOM_FACTOR, CONTROLLED_ZOOM_FACTOR, } from "./spreadsheet/zoom-constants";
|
|
20
|
+
export { normalizeCellSnapAnchor, getCellSnapAnchorFromRect, getRectFromCellSnapAnchor, moveCellSnapAnchorToNearestOrigin, resizeCellSnapAnchorToNearestEdges, getColumnEdgePosition, getRowEdgePosition, getNearestColumnEdgeIndex, getNearestRowEdgeIndex, type CellSnapAnchor, type CellSnapRect, type CellSnapResizeHandle, } from "./spreadsheet/snapping";
|
|
21
|
+
export { ClipboardUtils, type CellDataUpdate, type ExtractedCells, } from "./clipboard/clipboard-manager";
|
|
22
|
+
export { cn } from "./utils/cn";
|
|
23
|
+
export { useResizeObserver } from "./utils/use-resize-observer";
|
|
24
|
+
export { useColorScheme, type ColorScheme } from "./utils/use-color-scheme";
|
|
25
|
+
export { useClickAway } from "./utils/use-click-away";
|
|
26
|
+
export { ViewportStream, type ViewportState, type ResizeHandle } from "./grid/types";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface CellSnapAnchor {
|
|
2
|
+
startCol: number;
|
|
3
|
+
startRow: number;
|
|
4
|
+
endCol: number;
|
|
5
|
+
endRow: number;
|
|
6
|
+
}
|
|
7
|
+
export interface CellSnapRect {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
export type CellSnapResizeHandle = 'top' | 'right' | 'bottom' | 'left' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
14
|
+
export declare const normalizeCellSnapAnchor: (anchor: CellSnapAnchor) => CellSnapAnchor;
|
|
15
|
+
export declare const getColumnEdgePosition: (edgeIndex: number, columnWidths: Map<string, number>) => number;
|
|
16
|
+
export declare const getRowEdgePosition: (edgeIndex: number, rowHeights: Map<number, number>) => number;
|
|
17
|
+
export declare const getNearestColumnEdgeIndex: (x: number, columnWidths: Map<string, number>) => number;
|
|
18
|
+
export declare const getNearestRowEdgeIndex: (y: number, rowHeights: Map<number, number>) => number;
|
|
19
|
+
export declare const getCellSnapAnchorFromRect: (rect: CellSnapRect, columnWidths: Map<string, number>, rowHeights: Map<number, number>) => CellSnapAnchor;
|
|
20
|
+
export declare const getRectFromCellSnapAnchor: (anchor: CellSnapAnchor, columnWidths: Map<string, number>, rowHeights: Map<number, number>) => CellSnapRect;
|
|
21
|
+
export declare const moveCellSnapAnchorToNearestOrigin: (anchor: CellSnapAnchor, rect: CellSnapRect, columnWidths: Map<string, number>, rowHeights: Map<number, number>) => CellSnapAnchor;
|
|
22
|
+
export declare const resizeCellSnapAnchorToNearestEdges: (anchor: CellSnapAnchor, rect: CellSnapRect, handle: CellSnapResizeHandle, columnWidths: Map<string, number>, rowHeights: Map<number, number>) => CellSnapAnchor;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type Format, SelectionManager, type SelectionManagerState } from '@ricsam/selection-manager';
|
|
2
|
+
import React, { type ReactNode } from 'react';
|
|
3
|
+
import type { CellRenderContext, SerializedCellValue, SpreadsheetColumnWidths, SpreadsheetRowHeights } from '../types';
|
|
4
|
+
import { type CellSnapAnchor } from './snapping';
|
|
5
|
+
export interface SpreadsheetChildInitial {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
zoom?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface SpreadsheetChild {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
zoom?: number;
|
|
20
|
+
snapToCells?: boolean;
|
|
21
|
+
cellAnchor?: CellSnapAnchor;
|
|
22
|
+
minimized?: boolean;
|
|
23
|
+
initial?: SpreadsheetChildInitial;
|
|
24
|
+
component: string;
|
|
25
|
+
canClose?: boolean;
|
|
26
|
+
persistent?: boolean;
|
|
27
|
+
props?: any;
|
|
28
|
+
}
|
|
29
|
+
export interface SpreadsheetChildState {
|
|
30
|
+
isSelected: boolean;
|
|
31
|
+
isMinimized: boolean;
|
|
32
|
+
}
|
|
33
|
+
export type SpreadsheetComponent = (child: SpreadsheetChild, state: SpreadsheetChildState) => ReactNode;
|
|
34
|
+
export interface SpreadsheetProps {
|
|
35
|
+
style?: React.CSSProperties;
|
|
36
|
+
/** Extra class name applied to the outer `.rsp-root` element. */
|
|
37
|
+
className?: string;
|
|
38
|
+
containerProps?: React.HTMLAttributes<HTMLDivElement>;
|
|
39
|
+
cellData?: Map<string, SerializedCellValue>;
|
|
40
|
+
onCellDataChange?: (data: Map<string, SerializedCellValue>) => void;
|
|
41
|
+
onCellDataChangeError?: (error: unknown) => void;
|
|
42
|
+
columnWidths?: SpreadsheetColumnWidths;
|
|
43
|
+
onColumnWidthsChange?: (columnWidths: SpreadsheetColumnWidths) => void;
|
|
44
|
+
rowHeights?: SpreadsheetRowHeights;
|
|
45
|
+
onRowHeightsChange?: (rowHeights: SpreadsheetRowHeights) => void;
|
|
46
|
+
customCellStyle?: (cell: CellRenderContext) => React.CSSProperties;
|
|
47
|
+
customCellRenderer?: (cell: CellRenderContext) => React.ReactNode;
|
|
48
|
+
/**
|
|
49
|
+
* Used for saving the value to the cell data. Most useful for parsing a pasted
|
|
50
|
+
* string value to a number.
|
|
51
|
+
*/
|
|
52
|
+
parseValue?: (value: string, cell: {
|
|
53
|
+
id: string;
|
|
54
|
+
row: number;
|
|
55
|
+
col: string;
|
|
56
|
+
rowIndex: number;
|
|
57
|
+
colIndex: number;
|
|
58
|
+
}) => string | number;
|
|
59
|
+
selection?: {
|
|
60
|
+
initialState?: Partial<SelectionManagerState>;
|
|
61
|
+
state?: SelectionManagerState;
|
|
62
|
+
onStateChange?: (state: SelectionManagerState) => void;
|
|
63
|
+
effects?: (selectionManager: SelectionManager) => (() => void) | void;
|
|
64
|
+
formats?: Format[];
|
|
65
|
+
};
|
|
66
|
+
components?: Record<string, SpreadsheetComponent>;
|
|
67
|
+
overlayChildren?: SpreadsheetChild[];
|
|
68
|
+
selectedOverlayId?: string | null;
|
|
69
|
+
onOverlaySelect?: (childId: string | null) => void;
|
|
70
|
+
onOverlayChildrenChange?: (children: SpreadsheetChild[]) => void;
|
|
71
|
+
overlayPlaceholder?: {
|
|
72
|
+
componentType: string;
|
|
73
|
+
title: string;
|
|
74
|
+
width: number;
|
|
75
|
+
height: number;
|
|
76
|
+
onPlaced: (x: number, y: number) => void;
|
|
77
|
+
onCancel?: () => void;
|
|
78
|
+
};
|
|
79
|
+
/** Whether the parent component is selected (used to gate overlay selection) */
|
|
80
|
+
parentSelected?: boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface SpreadsheetRef {
|
|
83
|
+
focus: () => void;
|
|
84
|
+
blur: () => void;
|
|
85
|
+
}
|
|
86
|
+
export declare const Spreadsheet: React.ForwardRefExoticComponent<SpreadsheetProps & {
|
|
87
|
+
disableClipboard?: boolean;
|
|
88
|
+
} & React.RefAttributes<SpreadsheetRef | undefined>>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const columnToIndex: (col: string) => number;
|
|
2
|
+
export declare const indexToColumn: (index: number) => string;
|
|
3
|
+
export declare function getRowNumber(index: number): number;
|
|
4
|
+
export declare function getCellReference({ rowIndex, colIndex }: {
|
|
5
|
+
rowIndex: number;
|
|
6
|
+
colIndex: number;
|
|
7
|
+
}): string;
|
|
8
|
+
export declare function rowToLetter(row: number): string;
|
|
9
|
+
export declare function letterToRow(letter: string): number;
|
|
10
|
+
export declare const parseCellReference: (cellReference: string) => {
|
|
11
|
+
column: string;
|
|
12
|
+
row: number;
|
|
13
|
+
columnIndex: number;
|
|
14
|
+
rowIndex: number;
|
|
15
|
+
};
|
|
16
|
+
export declare function findCell(map: Map<string, string | number>, search: string): {
|
|
17
|
+
cellReference: string;
|
|
18
|
+
column: string;
|
|
19
|
+
row: number;
|
|
20
|
+
columnIndex: number;
|
|
21
|
+
rowIndex: number;
|
|
22
|
+
} | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Returns the values that belong to the column whose header equals `header`,
|
|
25
|
+
* in strict top-to-bottom order (H2, H3, H4 …).
|
|
26
|
+
*/
|
|
27
|
+
export declare function extractColumnByHeader(map: Map<string, string | number>, header: string): (string | number)[];
|
|
28
|
+
export declare function extractColumn(map: Map<string, string | number>, colIndex: number, rowIndex?: number, handleEmpty?: (rowIndex: number) => string | number): (string | number)[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core value types.
|
|
3
|
+
*
|
|
4
|
+
* These mirror `@ricsam/formula-engine` so the components can be used with or
|
|
5
|
+
* without the engine installed. The engine is an optional peer dependency; the
|
|
6
|
+
* grid itself only ever needs to know that a cell holds a primitive.
|
|
7
|
+
*/
|
|
8
|
+
export type SerializedCellValue = string | number | boolean | undefined;
|
|
9
|
+
/** Column widths keyed by column letter, e.g. `{ A: 160 }`. */
|
|
10
|
+
export type SpreadsheetColumnWidths = Record<string, number>;
|
|
11
|
+
/** Row heights keyed by 1-based row number, e.g. `{ 1: 40 }`. */
|
|
12
|
+
export type SpreadsheetRowHeights = Record<number, number>;
|
|
13
|
+
/** Data passed to `customCellStyle` / `customCellRenderer`. */
|
|
14
|
+
export type CellRenderContext = {
|
|
15
|
+
/** A1-style identifier, e.g. `"B7"`. */
|
|
16
|
+
id: string;
|
|
17
|
+
/** 1-based row number. */
|
|
18
|
+
row: number;
|
|
19
|
+
/** Column letter. */
|
|
20
|
+
col: string;
|
|
21
|
+
/** 0-based row index. */
|
|
22
|
+
rowIndex: number;
|
|
23
|
+
/** 0-based column index. */
|
|
24
|
+
colIndex: number;
|
|
25
|
+
value: SerializedCellValue;
|
|
26
|
+
isSelected: boolean;
|
|
27
|
+
isBeingEdited: boolean;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* @deprecated Use {@link CellRenderContext}. Kept as an alias for consumers
|
|
31
|
+
* migrating from the original internal component.
|
|
32
|
+
*/
|
|
33
|
+
export type ConditionalStyleCallbackData = CellRenderContext;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal class name joiner.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately dependency-free: this library ships no styling framework and
|
|
5
|
+
* only needs truthy filtering + joining for CSS module class composition.
|
|
6
|
+
*/
|
|
7
|
+
export type ClassValue = string | number | null | undefined | false | ClassValue[] | Record<string, boolean | null | undefined>;
|
|
8
|
+
export declare function cn(...values: ClassValue[]): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useClickAway: (ref: HTMLElement | null, callback: (away: boolean) => void) => void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type ColorScheme = "light" | "dark";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the effective color scheme for canvas painting.
|
|
4
|
+
*
|
|
5
|
+
* CSS handles theming through `light-dark()`, but the 2D canvas context cannot
|
|
6
|
+
* use it, so grid lines need a concrete resolved value. When an element is
|
|
7
|
+
* supplied we read its computed `color-scheme`, which lets a consumer pin a
|
|
8
|
+
* subtree to one mode; otherwise we fall back to the OS preference.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useColorScheme(element?: HTMLElement | null): ColorScheme;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ResizeObserverSize {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Observes the size of an element via a callback ref.
|
|
7
|
+
*
|
|
8
|
+
* Returns `[size, ref]` so the consumer can spread the ref onto any element.
|
|
9
|
+
* Falls back to `getBoundingClientRect` once on attach so the first paint has
|
|
10
|
+
* usable dimensions even before the observer fires.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useResizeObserver<T extends HTMLElement = HTMLDivElement>(): [
|
|
13
|
+
ResizeObserverSize,
|
|
14
|
+
(element: T | null) => void
|
|
15
|
+
];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CellStyle } from "@ricsam/formula-engine";
|
|
2
|
+
export type BorderSides = NonNullable<CellStyle["borderSides"]>;
|
|
3
|
+
export type NormalizedBorderSides = {
|
|
4
|
+
top: boolean;
|
|
5
|
+
right: boolean;
|
|
6
|
+
bottom: boolean;
|
|
7
|
+
left: boolean;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Normalizes a partial border-sides record into a fully specified one.
|
|
11
|
+
*
|
|
12
|
+
* When `borderSides` is omitted entirely the style is interpreted as "all
|
|
13
|
+
* sides", which matches how a plain `borderColor` is expected to behave.
|
|
14
|
+
*
|
|
15
|
+
* @param sides Partial per-side flags from a `CellStyle`.
|
|
16
|
+
* @param defaultAllWhenUnset Value used for every side when `sides` is undefined.
|
|
17
|
+
*/
|
|
18
|
+
export declare function normalizeBorderSides(sides: BorderSides | undefined, defaultAllWhenUnset?: boolean): NormalizedBorderSides;
|
|
19
|
+
/** True when at least one side should be drawn. */
|
|
20
|
+
export declare function hasAnyBorderSide(sides: NormalizedBorderSides): boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CellDataType, SerializedCellValue } from "@ricsam/formula-engine";
|
|
2
|
+
/**
|
|
3
|
+
* Converts raw editor input into the value to store for a cell.
|
|
4
|
+
*
|
|
5
|
+
* - Formulas (leading `=`) are always preserved verbatim.
|
|
6
|
+
* - `"general"` infers number/boolean, otherwise keeps the string.
|
|
7
|
+
* - An explicit data type forces that interpretation, falling back to the raw
|
|
8
|
+
* string when the input cannot be represented.
|
|
9
|
+
* - An empty string clears the cell (`undefined`).
|
|
10
|
+
*/
|
|
11
|
+
export declare function coerceCellInput(value: string, dataType?: CellDataType): SerializedCellValue;
|
|
12
|
+
/** Formats an evaluated value for display, with thousands separators. */
|
|
13
|
+
export declare function getCellDisplayText(value: SerializedCellValue): string;
|
|
14
|
+
/** Serializes a value for the clipboard (unformatted, round-trippable). */
|
|
15
|
+
export declare function getCellDisplayValue(value: SerializedCellValue): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cell edit error reporting.
|
|
3
|
+
*
|
|
4
|
+
* The original component reported rejected edits through an app-specific
|
|
5
|
+
* snackbar. A reusable library must not own UI for that, so errors are routed
|
|
6
|
+
* through a subscribable sink instead: by default they are logged, and a host
|
|
7
|
+
* app can subscribe to render them however it likes.
|
|
8
|
+
*/
|
|
9
|
+
export type CellEditErrorListener = (error: unknown) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Subscribes to cell edit errors (e.g. the engine rejecting a formula).
|
|
12
|
+
*
|
|
13
|
+
* @returns An unsubscribe function.
|
|
14
|
+
*/
|
|
15
|
+
export declare function onCellEditError(listener: CellEditErrorListener): () => void;
|
|
16
|
+
/**
|
|
17
|
+
* Default `onCellDataChangeError` handler.
|
|
18
|
+
*
|
|
19
|
+
* Notifies subscribers; if there are none it falls back to `console.error` so
|
|
20
|
+
* failures are never silently swallowed during development.
|
|
21
|
+
*/
|
|
22
|
+
export declare function queueCellEditError(error: unknown): void;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { RangeAddress } from '@ricsam/formula-engine';
|
|
2
|
+
import type { SelectionManager } from '@ricsam/selection-manager';
|
|
3
|
+
interface ManagerContext {
|
|
4
|
+
workbookName: string;
|
|
5
|
+
sheetName: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class WorkbookSelectionManager {
|
|
8
|
+
private managers;
|
|
9
|
+
private lastFocusedManager;
|
|
10
|
+
private listeners;
|
|
11
|
+
private cleanupFunctions;
|
|
12
|
+
private smAreaToSpreadsheetRange;
|
|
13
|
+
/**
|
|
14
|
+
* Register a SelectionManager with its workbook/sheet context
|
|
15
|
+
* Returns a cleanup function to unregister
|
|
16
|
+
*/
|
|
17
|
+
add(manager: SelectionManager, context: ManagerContext): () => void;
|
|
18
|
+
/**
|
|
19
|
+
* Get the SelectionManager that was last focused or had selection activity
|
|
20
|
+
*/
|
|
21
|
+
getLastFocusedSelectionManager(): {
|
|
22
|
+
sm: SelectionManager;
|
|
23
|
+
context: ManagerContext;
|
|
24
|
+
} | null;
|
|
25
|
+
/**
|
|
26
|
+
* Get all current selections from the last focused manager.
|
|
27
|
+
*/
|
|
28
|
+
getSelections(): RangeAddress[];
|
|
29
|
+
/**
|
|
30
|
+
* Get the last/primary selection from the last focused manager
|
|
31
|
+
*/
|
|
32
|
+
getLastSelection(): RangeAddress | null;
|
|
33
|
+
/**
|
|
34
|
+
* Subscribe to selection changes
|
|
35
|
+
* Returns cleanup function
|
|
36
|
+
*/
|
|
37
|
+
onSelectionChange(callback: (selections: RangeAddress[]) => void): () => void;
|
|
38
|
+
/**
|
|
39
|
+
* Format the last selection as a range string
|
|
40
|
+
* e.g., "[Workbook]Sheet!A1:C10" or "[Workbook]'My Sheet'!A1:INFINITY"
|
|
41
|
+
*/
|
|
42
|
+
formatLastSelectionAsRange(): string;
|
|
43
|
+
/**
|
|
44
|
+
* Blur all selection managers (remove focus from all)
|
|
45
|
+
* This should be called when the component loses focus
|
|
46
|
+
*/
|
|
47
|
+
blur(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Focus the selection manager for a specific workbook (first sheet found).
|
|
50
|
+
* This is useful when programmatically switching focus, e.g., after maximizing a window.
|
|
51
|
+
*/
|
|
52
|
+
focusWorkbook(workbookName: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* Focus the selection manager for a specific sheet without changing the cell selection.
|
|
55
|
+
*/
|
|
56
|
+
focusSheet(workbookName: string, sheetName: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Focus a specific cell in a specific workbook and sheet.
|
|
59
|
+
* Finds the matching SelectionManager, sets its selection to the given cell,
|
|
60
|
+
* and focuses it.
|
|
61
|
+
*
|
|
62
|
+
* @returns true if the cell was successfully focused, false if no matching
|
|
63
|
+
* manager was found (e.g. the sheet is not currently rendered).
|
|
64
|
+
*/
|
|
65
|
+
focusCell(workbookName: string, sheetName: string, row: number, col: number): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Notify all listeners of selection change
|
|
68
|
+
*/
|
|
69
|
+
private emitChange;
|
|
70
|
+
}
|
|
71
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { FormulaEngine, type CellAddress } from '@ricsam/formula-engine';
|
|
2
|
+
import type { SelectionManager } from '@ricsam/selection-manager';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { type SpreadsheetProps } from '../spreadsheet/spreadsheet';
|
|
5
|
+
import type { CellRenderContext, SpreadsheetColumnWidths, SpreadsheetRowHeights } from '../types';
|
|
6
|
+
import type { WorkbookSelectionManager } from './workbook-selection-manager';
|
|
7
|
+
import { type CellDataUpdate, ClipboardUtils } from '../clipboard/clipboard-manager';
|
|
8
|
+
type SpreadsheetLayout = {
|
|
9
|
+
columnWidths?: SpreadsheetColumnWidths;
|
|
10
|
+
rowHeights?: SpreadsheetRowHeights;
|
|
11
|
+
};
|
|
12
|
+
interface FormulaSheetProps extends Omit<SpreadsheetProps, 'selection' | 'customCellStyle' | 'customCellRenderer' | 'components' | 'overlayChildren' | 'selectedOverlayId' | 'onOverlaySelect' | 'onOverlayChildrenChange' | 'overlayPlaceholder' | 'parentSelected'> {
|
|
13
|
+
clipboardManager?: WorkbookClipboardManager;
|
|
14
|
+
sheetName: string;
|
|
15
|
+
workbookName: string;
|
|
16
|
+
engine: FormulaEngine;
|
|
17
|
+
verboseErrors?: boolean;
|
|
18
|
+
selection?: SpreadsheetProps['selection'];
|
|
19
|
+
selectionManager?: WorkbookSelectionManager;
|
|
20
|
+
customCellStyle?: (cell: CellRenderContext, internalStyle: React.CSSProperties) => React.CSSProperties;
|
|
21
|
+
customCellRenderer?: (cell: CellRenderContext, internalElement: React.ReactNode) => React.ReactNode;
|
|
22
|
+
components?: SpreadsheetProps['components'];
|
|
23
|
+
overlayChildren?: SpreadsheetProps['overlayChildren'];
|
|
24
|
+
selectedOverlayId?: SpreadsheetProps['selectedOverlayId'];
|
|
25
|
+
onOverlaySelect?: SpreadsheetProps['onOverlaySelect'];
|
|
26
|
+
onOverlayChildrenChange?: SpreadsheetProps['onOverlayChildrenChange'];
|
|
27
|
+
overlayPlaceholder?: SpreadsheetProps['overlayPlaceholder'];
|
|
28
|
+
/** Whether the grid child is selected (controls interactivity) */
|
|
29
|
+
isSelected?: boolean;
|
|
30
|
+
/** Optional toolbar to render above the sheet */
|
|
31
|
+
toolbar?: React.ReactNode;
|
|
32
|
+
/** When true, cells display raw formulas instead of computed values */
|
|
33
|
+
showFormulas?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare class WorkbookClipboardManager extends ClipboardUtils {
|
|
36
|
+
private engine;
|
|
37
|
+
constructor(engine: FormulaEngine);
|
|
38
|
+
copiedCells: CellAddress[];
|
|
39
|
+
signature: string;
|
|
40
|
+
isCut: boolean;
|
|
41
|
+
triggerCopy(context: {
|
|
42
|
+
workbookName: string;
|
|
43
|
+
sheetName: string;
|
|
44
|
+
selectionManager: SelectionManager;
|
|
45
|
+
copyType: 'value' | 'formula';
|
|
46
|
+
cut?: boolean;
|
|
47
|
+
}): void;
|
|
48
|
+
triggerPaste(context: {
|
|
49
|
+
workbookName: string;
|
|
50
|
+
sheetName: string;
|
|
51
|
+
selectionManager: SelectionManager;
|
|
52
|
+
updates: CellDataUpdate[];
|
|
53
|
+
rawString: string;
|
|
54
|
+
pasteType: 'value' | 'formula';
|
|
55
|
+
}): void;
|
|
56
|
+
}
|
|
57
|
+
export declare function FormulaSheet({ sheetName, workbookName, engine, verboseErrors, selection, selectionManager, customCellStyle, customCellRenderer, onCellDataChangeError, components, overlayChildren, selectedOverlayId, onOverlaySelect, onOverlayChildrenChange, overlayPlaceholder, clipboardManager: controlledClipboardManager, isSelected, toolbar, showFormulas, ...rest }: FormulaSheetProps): React.JSX.Element;
|
|
58
|
+
interface FormulaWorkbookProps extends Omit<SpreadsheetProps, 'cellData' | 'onCellDataChange' | 'selection' | 'customCellStyle' | 'customCellRenderer' | 'components' | 'overlayChildren' | 'selectedOverlayId' | 'onOverlaySelect' | 'onOverlayChildrenChange' | 'overlayPlaceholder' | 'parentSelected' | 'columnWidths' | 'onColumnWidthsChange' | 'rowHeights' | 'onRowHeightsChange'> {
|
|
59
|
+
workbookName: string;
|
|
60
|
+
clipboardManager?: WorkbookClipboardManager;
|
|
61
|
+
engine: FormulaEngine;
|
|
62
|
+
activeSheet?: string;
|
|
63
|
+
onActiveSheetChange?: (sheetName: string) => void;
|
|
64
|
+
verboseErrors?: boolean;
|
|
65
|
+
selection?: SpreadsheetProps['selection'];
|
|
66
|
+
selectionManager?: WorkbookSelectionManager;
|
|
67
|
+
customCellStyle?: (cell: CellRenderContext, internalStyle: React.CSSProperties) => React.CSSProperties;
|
|
68
|
+
customCellRenderer?: (cell: CellRenderContext, internalElement: React.ReactNode) => React.ReactNode;
|
|
69
|
+
components?: SpreadsheetProps['components'];
|
|
70
|
+
sheetOverlayChildren?: Record<string, SpreadsheetProps['overlayChildren']>;
|
|
71
|
+
sheetSelectedOverlayIds?: Record<string, SpreadsheetProps['selectedOverlayId']>;
|
|
72
|
+
sheetLayouts?: Record<string, SpreadsheetLayout>;
|
|
73
|
+
onSheetColumnWidthsChange?: (sheetName: string, columnWidths: SpreadsheetColumnWidths) => void;
|
|
74
|
+
onSheetRowHeightsChange?: (sheetName: string, rowHeights: SpreadsheetRowHeights) => void;
|
|
75
|
+
onOverlaySelect?: (sheetName: string, overlayId: string | null) => void;
|
|
76
|
+
onOverlayChildrenChange?: (sheetName: string, children: NonNullable<SpreadsheetProps['overlayChildren']>) => void;
|
|
77
|
+
overlayPlaceholder?: SpreadsheetProps['overlayPlaceholder'];
|
|
78
|
+
/** Whether the grid child is selected (controls interactivity) */
|
|
79
|
+
isSelected?: boolean;
|
|
80
|
+
/** Optional toolbar to render above the workbook */
|
|
81
|
+
toolbar?: React.ReactNode;
|
|
82
|
+
/** When true, cells display raw formulas instead of computed values */
|
|
83
|
+
showFormulas?: boolean;
|
|
84
|
+
}
|
|
85
|
+
export declare function FormulaWorkbook({ clipboardManager: controlledClipboardManager, workbookName, engine, activeSheet: controlledActiveSheet, onActiveSheetChange, verboseErrors, selectionManager, components, sheetOverlayChildren, sheetSelectedOverlayIds, sheetLayouts, onSheetColumnWidthsChange, onSheetRowHeightsChange, onOverlaySelect, onOverlayChildrenChange, overlayPlaceholder, isSelected, toolbar, showFormulas, ...rest }: FormulaWorkbookProps): React.JSX.Element;
|
|
86
|
+
export { WorkbookSelectionManager } from './workbook-selection-manager';
|