@c-time/simple-ex-grid 1.0.0

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.
@@ -0,0 +1,84 @@
1
+ export type GridMode = 'readonly' | 'edit' | 'select';
2
+ export type ColumnWidth = number | `${number}%` | 'fit-content';
3
+ export type RowHeight = 'fit-content' | 'single-line';
4
+ export type SortMark = 'asc' | 'desc' | null;
5
+ export interface ColumnDef<Row> {
6
+ key: keyof Row & string;
7
+ header: string;
8
+ width?: ColumnWidth;
9
+ hidden?: boolean;
10
+ editable?: boolean;
11
+ formatter?: (value: unknown, row: Row) => string;
12
+ render?: (value: unknown, row: Row, rowIndex: number) => React.ReactNode;
13
+ parser?: (input: string) => unknown;
14
+ validator?: (value: unknown) => string | null;
15
+ sortMark?: SortMark;
16
+ align?: 'left' | 'center' | 'right';
17
+ headerAlign?: 'left' | 'center' | 'right';
18
+ }
19
+ export interface CellRange {
20
+ start: {
21
+ row: number;
22
+ col: number;
23
+ };
24
+ end: {
25
+ row: number;
26
+ col: number;
27
+ };
28
+ }
29
+ export interface CellChange {
30
+ row: number;
31
+ col: number;
32
+ before: unknown;
33
+ after: unknown;
34
+ }
35
+ export interface HistoryApi<Row> {
36
+ pushHistory: (beforeRows: Row[], afterRows: Row[]) => void;
37
+ undo: () => void;
38
+ redo: () => void;
39
+ canUndo: boolean;
40
+ canRedo: boolean;
41
+ }
42
+ export interface SimpleExGridProps<Row> {
43
+ rows: Row[];
44
+ columns: ColumnDef<Row>[];
45
+ onChange?: (next: Row[], changes: CellChange[]) => void;
46
+ mode?: GridMode;
47
+ showRowGripHeader?: boolean;
48
+ showColumnGripHeader?: boolean;
49
+ gripHeaderDragSelect?: boolean;
50
+ pinnedLeftCount?: number;
51
+ pinnedTopCount?: number;
52
+ pagination?: boolean;
53
+ pageSize?: number;
54
+ page?: number;
55
+ onPageChange?: (page: number) => void;
56
+ onPageSizeChange?: (pageSize: number) => void;
57
+ clipboard?: {
58
+ enabled?: boolean;
59
+ format?: 'tsv' | 'html' | 'both';
60
+ };
61
+ selection?: CellRange | null;
62
+ onSelectionChange?: (range: CellRange | null) => void;
63
+ onActiveCellChange?: (row: number, col: number) => void;
64
+ onEditStart?: (row: number, col: number) => boolean | void;
65
+ onEditCommit?: (row: number, col: number, before: unknown, after: unknown) => boolean | void;
66
+ onEditCancel?: (row: number, col: number) => void;
67
+ onRowsAdd?: (startRow: number, count: number) => void;
68
+ onRowsDelete?: (startRow: number, count: number) => void;
69
+ rowHeight?: RowHeight;
70
+ allowRowAdd?: boolean;
71
+ allowRowDelete?: boolean;
72
+ maxHistory?: number;
73
+ shouldRecordHistory?: (changes: CellChange[], rowEvent?: {
74
+ kind: 'add' | 'delete';
75
+ startRow: number;
76
+ count: number;
77
+ }) => boolean;
78
+ historyApiRef?: React.MutableRefObject<HistoryApi<Row> | null>;
79
+ colSpan?: (row: number, col: number) => number;
80
+ cellAlign?: (row: number, col: number) => 'left' | 'center' | 'right' | undefined;
81
+ virtualized?: boolean;
82
+ minRows?: number;
83
+ minCols?: number;
84
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Utility functions for resolving column spans.
3
+ *
4
+ * The `ColSpanGetter` callback is only invoked on "owner" columns — the leftmost
5
+ * column of each span group. Covered columns (those absorbed by a previous
6
+ * cell's span) are skipped during the left-to-right scan.
7
+ */
8
+ export type ColSpanGetter = (row: number, col: number) => number;
9
+ /**
10
+ * Find the owner (leftmost) column for a given (row, col) position.
11
+ * If `col` is inside a span that started at an earlier column, this returns
12
+ * that earlier column index.
13
+ */
14
+ export declare function findOwnerCol(row: number, col: number, colCount: number, getColSpan: ColSpanGetter): number;
15
+ /**
16
+ * Get the effective span width for the cell at (row, col).
17
+ * Always resolves from the owner column first.
18
+ */
19
+ export declare function getEffectiveSpan(row: number, col: number, colCount: number, getColSpan: ColSpanGetter): number;
20
+ /**
21
+ * Return the next visible (owner) column after `col` in a given row.
22
+ * Returns `colCount` if there is no next column (i.e. we're at the end).
23
+ */
24
+ export declare function nextOwnerCol(row: number, col: number, colCount: number, getColSpan: ColSpanGetter): number;
25
+ /**
26
+ * Return the previous visible (owner) column before `col` in a given row.
27
+ * Returns -1 if there is no previous column.
28
+ */
29
+ export declare function prevOwnerCol(row: number, col: number, colCount: number, getColSpan: ColSpanGetter): number;
30
+ /**
31
+ * Return the last owner column in a given row.
32
+ */
33
+ export declare function lastOwnerCol(row: number, colCount: number, getColSpan: ColSpanGetter): number;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Encode grid data as an HTML <table> for clipboard (Excel/Sheets interop).
3
+ */
4
+ export interface HtmlEncodeOptions {
5
+ colSpan?: (row: number, col: number) => number;
6
+ startRow?: number;
7
+ startCol?: number;
8
+ }
9
+ export declare function encodeHtml(grid: string[][], options?: HtmlEncodeOptions): string;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Excel-compliant TSV encoder/decoder.
3
+ * - Row delimiter: \r\n
4
+ * - Cell delimiter: \t
5
+ * - Cells containing \t, \n, \r, or " are wrapped in double quotes with " escaped as ""
6
+ */
7
+ export declare function encodeTsv(grid: string[][]): string;
8
+ export declare function decodeTsv(tsv: string): string[][];
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@c-time/simple-ex-grid",
3
+ "version": "1.0.0",
4
+ "description": "Excel-like grid component for React with clipboard, touch support, and cell selection",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "keywords": [
20
+ "react",
21
+ "grid",
22
+ "excel",
23
+ "spreadsheet",
24
+ "clipboard",
25
+ "touch"
26
+ ],
27
+ "author": "c-time",
28
+ "license": "MIT",
29
+ "peerDependencies": {
30
+ "react": "^18.0.0 || ^19.0.0",
31
+ "react-dom": "^18.0.0 || ^19.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@eslint/js": "^10.0.1",
35
+ "@storybook/react": "^10.3.5",
36
+ "@storybook/react-vite": "^10.3.5",
37
+ "@testing-library/jest-dom": "^6.9.1",
38
+ "@testing-library/react": "^16.3.2",
39
+ "@types/react": "^19.2.14",
40
+ "@types/react-dom": "^19.2.3",
41
+ "@vitejs/plugin-react": "^6.0.1",
42
+ "eslint": "^10.2.0",
43
+ "eslint-plugin-react-hooks": "^7.0.1",
44
+ "eslint-plugin-react-refresh": "^0.5.2",
45
+ "jsdom": "^29.0.2",
46
+ "prettier": "^3.8.2",
47
+ "react": "^19.2.5",
48
+ "react-dom": "^19.2.5",
49
+ "storybook": "^10.3.5",
50
+ "typescript": "^6.0.2",
51
+ "typescript-eslint": "^8.58.1",
52
+ "vite": "^8.0.8",
53
+ "vitest": "^4.1.4"
54
+ },
55
+ "scripts": {
56
+ "dev": "storybook dev -p 6006",
57
+ "build": "vite build && tsc --emitDeclarationOnly",
58
+ "test": "vitest run",
59
+ "test:watch": "vitest",
60
+ "lint": "eslint src/",
61
+ "format": "prettier --write \"src/**/*.{ts,tsx}\"",
62
+ "format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
63
+ "storybook:build": "storybook build",
64
+ "typecheck": "tsc --noEmit"
65
+ }
66
+ }