react-excel-lite 0.0.1 → 0.0.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 prkgnt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # react-excel-lite
2
+
3
+ [![GitHub repo](https://img.shields.io/badge/GitHub-repo-grey?logo=github)](https://github.com/prkgnt/react-excel-lite)
4
+ [![npm package](https://img.shields.io/npm/v/react-excel-lite?color=brightgreen&label=npm)](https://www.npmjs.org/package/react-excel-lite)
5
+ [![npm downloads](https://img.shields.io/npm/dw/react-excel-lite)](https://www.npmjs.org/package/react-excel-lite)
6
+ [![MIT License](https://img.shields.io/github/license/prkgnt/react-excel-lite)](https://github.com/prkgnt/react-excel-lite/blob/main/LICENSE)
7
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://github.com/prkgnt/react-excel-lite/pulls)
8
+
9
+ A lightweight, Excel-like editable grid component for React.
10
+
11
+ ## Features
12
+
13
+ - Excel-like cell selection (click & drag)
14
+ - Copy/Paste support (Ctrl+C / Ctrl+V)
15
+ - Auto Fill with arithmetic sequence detection (drag fill handle)
16
+ - Grouped column headers with custom styling
17
+ - Row headers with custom styling
18
+ - Keyboard shortcuts (Delete/Backspace to clear)
19
+ - Zero external dependencies
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install react-excel-lite
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ```tsx
30
+ import { useState } from "react";
31
+ import { ExcelGrid } from "react-excel-lite";
32
+
33
+ function App() {
34
+ const [data, setData] = useState([
35
+ ["100", "200", "300"],
36
+ ["400", "500", "600"],
37
+ ["700", "800", "900"],
38
+ ]);
39
+
40
+ return <ExcelGrid data={data} onChange={setData} />;
41
+ }
42
+ ```
43
+
44
+ ## Props
45
+
46
+ | Prop | Type | Required | Description |
47
+ | ---------------- | ---------------------------- | -------- | --------------------------- |
48
+ | `data` | `string[][]` | Yes | 2D array of strings |
49
+ | `onChange` | `(data: string[][]) => void` | Yes | Callback when data changes |
50
+ | `rowHeaders` | `RowHeaderGroup[]` | No | Row header definitions |
51
+ | `colHeaders` | `ColHeaderGroup[]` | No | Grouped column headers |
52
+ | `className` | `string` | No | CSS class for container |
53
+ | `rowHeaderTitle` | `string` | No | Title for row header column |
54
+ | `styles` | `GridStyles` | No | Style configuration object |
55
+
56
+ ## With Headers
57
+
58
+ ```tsx
59
+ import { useState } from "react";
60
+ import { ExcelGrid } from "react-excel-lite";
61
+ import type { ColHeaderGroup, RowHeaderGroup } from "react-excel-lite";
62
+
63
+ function App() {
64
+ const [data, setData] = useState([
65
+ ["100", "200", "300", "400"],
66
+ ["500", "600", "700", "800"],
67
+ ]);
68
+
69
+ const colHeaders: ColHeaderGroup[] = [
70
+ {
71
+ label: "Q1",
72
+ headers: [
73
+ { key: "jan", label: "Jan" },
74
+ { key: "feb", label: "Feb" },
75
+ ],
76
+ },
77
+ {
78
+ label: "Q2",
79
+ headers: [
80
+ { key: "mar", label: "Mar" },
81
+ { key: "apr", label: "Apr" },
82
+ ],
83
+ },
84
+ ];
85
+
86
+ const rowHeaders: RowHeaderGroup[] = [
87
+ { key: "prodA", label: "Product A", description: "Main product line" },
88
+ { key: "prodB", label: "Product B", description: "Secondary product" },
89
+ ];
90
+
91
+ return (
92
+ <ExcelGrid
93
+ data={data}
94
+ onChange={setData}
95
+ colHeaders={colHeaders}
96
+ rowHeaders={rowHeaders}
97
+ rowHeaderTitle="Product"
98
+ />
99
+ );
100
+ }
101
+ ```
102
+
103
+ ## Custom Styling
104
+
105
+ Use the `styles` prop to customize selection, fill handle, and header styles:
106
+
107
+ ```tsx
108
+ import type { GridStyles } from "react-excel-lite";
109
+
110
+ const styles: GridStyles = {
111
+ cell: "text-sm",
112
+ selected: "bg-purple-100 ring-2 ring-inset ring-purple-500",
113
+ fillTarget: "bg-purple-50",
114
+ fillHandle: "bg-purple-500",
115
+ colGroup: "bg-purple-100 text-purple-700",
116
+ colHeader: "bg-purple-50",
117
+ rowHeader: "bg-slate-200",
118
+ };
119
+
120
+ <ExcelGrid data={data} onChange={setData} styles={styles} />;
121
+ ```
122
+
123
+ Style individual column headers and groups:
124
+
125
+ ```tsx
126
+ const colHeaders: ColHeaderGroup[] = [
127
+ {
128
+ label: "Revenue",
129
+ className: "bg-green-100 text-green-700",
130
+ headers: [
131
+ { key: "q1r", label: "Q1", className: "bg-green-50" },
132
+ { key: "q2r", label: "Q2", className: "bg-green-50" },
133
+ ],
134
+ },
135
+ ];
136
+ ```
137
+
138
+ Style individual row headers:
139
+
140
+ ```tsx
141
+ const rowHeaders: RowHeaderGroup[] = [
142
+ {
143
+ key: "regionA",
144
+ label: "Region A",
145
+ className: "bg-slate-700 text-white",
146
+ },
147
+ ];
148
+ ```
149
+
150
+ ## Auto Fill (Arithmetic Sequence)
151
+
152
+ Select cells with a numeric pattern and drag the fill handle to auto-fill:
153
+
154
+ - `1, 2, 3` → drag down → `4, 5, 6, 7, ...`
155
+ - `100, 200, 300` → drag down → `400, 500, 600, ...`
156
+ - `10, 8, 6` → drag down → `4, 2, 0, -2, ...`
157
+ - Text values → repeats the pattern
158
+
159
+ ## Keyboard Shortcuts
160
+
161
+ | Shortcut | Action |
162
+ | ---------------------- | -------------------- |
163
+ | `Ctrl+C` / `Cmd+C` | Copy selected cells |
164
+ | `Ctrl+V` / `Cmd+V` | Paste from clipboard |
165
+ | `Delete` / `Backspace` | Clear selected cells |
166
+
167
+ ## Exports
168
+
169
+ ### Components
170
+
171
+ - `ExcelGrid` - Main grid component
172
+ - `GridCell` - Individual cell component
173
+
174
+ ### Hooks
175
+
176
+ - `useGridSelection` - Cell selection logic
177
+ - `useGridClipboard` - Copy/paste logic
178
+ - `useGridDragFill` - Fill handle logic
179
+
180
+ ### Utilities
181
+
182
+ - `cn` - Classname merge utility
183
+ - `coordToKey` - Convert coordinate to string key
184
+ - `keyToCoord` - Convert string key to coordinate
185
+ - `getCellsInRange` - Get all cells in a range
186
+ - `isCellInRange` - Check if cell is in range
187
+ - `parseTSV` - Parse TSV string to 2D array
188
+ - `toTSV` - Convert 2D array to TSV string
189
+ - `normalizeRange` - Normalize selection range
190
+ - `getFillTargetCells` - Get fill target cells
191
+
192
+ ### Types
193
+
194
+ ```ts
195
+ interface CellCoord {
196
+ row: number;
197
+ col: number;
198
+ }
199
+
200
+ interface SelectionRange {
201
+ start: CellCoord | null;
202
+ end: CellCoord | null;
203
+ }
204
+
205
+ interface ColHeader {
206
+ key: string;
207
+ label: string;
208
+ description?: string;
209
+ className?: string;
210
+ }
211
+
212
+ interface ColHeaderGroup {
213
+ label: string;
214
+ headers: ColHeader[];
215
+ className?: string;
216
+ }
217
+
218
+ interface RowHeaderGroup {
219
+ key: string;
220
+ label: string;
221
+ description?: string;
222
+ className?: string;
223
+ }
224
+
225
+ interface GridStyles {
226
+ cell?: string;
227
+ selected?: string;
228
+ fillTarget?: string;
229
+ fillHandle?: string;
230
+ colGroup?: string;
231
+ colHeader?: string;
232
+ rowHeader?: string;
233
+ }
234
+
235
+ interface ExcelGridProps {
236
+ data: string[][];
237
+ onChange: (data: string[][]) => void;
238
+ rowHeaders?: RowHeaderGroup[];
239
+ colHeaders?: ColHeaderGroup[];
240
+ className?: string;
241
+ rowHeaderTitle?: string;
242
+ styles?: GridStyles;
243
+ }
244
+ ```
245
+
246
+ ## Styling
247
+
248
+ The component uses Tailwind CSS classes. Make sure Tailwind CSS is configured in your project, or override styles using the `styles` prop.
@@ -0,0 +1,3 @@
1
+ import { ExcelGridProps } from '../types';
2
+ export declare function ExcelGrid({ data, onChange, rowHeaders, colHeaders, className, rowHeaderTitle, styles, }: ExcelGridProps): import("react/jsx-runtime").JSX.Element;
3
+ //# sourceMappingURL=excel-grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excel-grid.d.ts","sourceRoot":"","sources":["../../src/components/excel-grid.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAa,MAAM,UAAU,CAAC;AAO1D,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,QAAQ,EACR,UAAU,EACV,UAAU,EACV,SAAS,EACT,cAAmB,EACnB,MAAM,GACP,EAAE,cAAc,2CAwPhB"}
@@ -0,0 +1,3 @@
1
+ import { GridCellProps } from '../types';
2
+ export declare function GridCell({ coord, value, isSelected, isFillTarget, showFillHandle, onMouseDown, onMouseEnter, onChange, onFillHandleMouseDown, styles, }: GridCellProps): import("react/jsx-runtime").JSX.Element;
3
+ //# sourceMappingURL=grid-cell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grid-cell.d.ts","sourceRoot":"","sources":["../../src/components/grid-cell.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,KAAK,EACL,UAAU,EACV,YAAY,EACZ,cAAc,EACd,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,qBAAqB,EACrB,MAAM,GACP,EAAE,aAAa,2CAmDf"}
@@ -0,0 +1,19 @@
1
+ import { CellCoord, SelectionRange } from '../types';
2
+ interface UseGridClipboardProps {
3
+ selection: SelectionRange;
4
+ getValue: (coord: CellCoord) => string;
5
+ setValues: (updates: {
6
+ coord: CellCoord;
7
+ value: string;
8
+ }[]) => void;
9
+ rowCount: number;
10
+ colCount: number;
11
+ }
12
+ interface UseGridClipboardReturn {
13
+ handleCopy: () => Promise<void>;
14
+ handlePaste: () => Promise<void>;
15
+ handleKeyDown: (e: React.KeyboardEvent) => void;
16
+ }
17
+ export declare function useGridClipboard({ selection, getValue, setValues, rowCount, colCount, }: UseGridClipboardProps): UseGridClipboardReturn;
18
+ export {};
19
+ //# sourceMappingURL=use-grid-clipboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-grid-clipboard.d.ts","sourceRoot":"","sources":["../../src/hooks/use-grid-clipboard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG1D,UAAU,qBAAqB;IAC7B,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,MAAM,CAAC;IACvC,SAAS,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,KAAK,IAAI,CAAC;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,sBAAsB;IAC9B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,aAAa,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;CACjD;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,QAAQ,GACT,EAAE,qBAAqB,GAAG,sBAAsB,CAmHhD"}
@@ -0,0 +1,21 @@
1
+ import { CellCoord, SelectionRange } from '../types';
2
+ interface UseGridDragFillProps {
3
+ selection: SelectionRange;
4
+ getValue: (coord: CellCoord) => string;
5
+ setValues: (updates: {
6
+ coord: CellCoord;
7
+ value: string;
8
+ }[]) => void;
9
+ }
10
+ interface UseGridDragFillReturn {
11
+ fillSource: CellCoord | null;
12
+ fillTargets: CellCoord[];
13
+ isDraggingFill: boolean;
14
+ isFillTarget: (coord: CellCoord) => boolean;
15
+ handleFillHandleMouseDown: (sourceCoord: CellCoord) => void;
16
+ handleCellMouseEnterForFill: (coord: CellCoord) => void;
17
+ handleFillMouseUp: () => void;
18
+ }
19
+ export declare function useGridDragFill({ selection, getValue, setValues, }: UseGridDragFillProps): UseGridDragFillReturn;
20
+ export {};
21
+ //# sourceMappingURL=use-grid-drag-fill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-grid-drag-fill.d.ts","sourceRoot":"","sources":["../../src/hooks/use-grid-drag-fill.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG1D,UAAU,oBAAoB;IAC5B,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,MAAM,CAAC;IACvC,SAAS,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,KAAK,IAAI,CAAC;CACrE;AAED,UAAU,qBAAqB;IAC7B,UAAU,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC;IAC5C,yBAAyB,EAAE,CAAC,WAAW,EAAE,SAAS,KAAK,IAAI,CAAC;IAC5D,2BAA2B,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACxD,iBAAiB,EAAE,MAAM,IAAI,CAAC;CAC/B;AA8CD,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,QAAQ,EACR,SAAS,GACV,EAAE,oBAAoB,GAAG,qBAAqB,CAwM9C"}
@@ -0,0 +1,14 @@
1
+ import { CellCoord, SelectionRange } from '../types';
2
+ interface UseGridSelectionReturn {
3
+ selection: SelectionRange;
4
+ isSelecting: boolean;
5
+ isCellSelected: (coord: CellCoord) => boolean;
6
+ handleCellMouseDown: (coord: CellCoord) => void;
7
+ handleCellMouseEnter: (coord: CellCoord) => void;
8
+ handleMouseUp: () => void;
9
+ clearSelection: () => void;
10
+ setSelection: (range: SelectionRange) => void;
11
+ }
12
+ export declare function useGridSelection(): UseGridSelectionReturn;
13
+ export {};
14
+ //# sourceMappingURL=use-grid-selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-grid-selection.d.ts","sourceRoot":"","sources":["../../src/hooks/use-grid-selection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG1D,UAAU,sBAAsB;IAC9B,SAAS,EAAE,cAAc,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC;IAC9C,mBAAmB,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAChD,oBAAoB,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACjD,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;CAC/C;AAED,wBAAgB,gBAAgB,IAAI,sBAAsB,CA8DzD"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,10 @@
1
- export {};
1
+ export { ExcelGrid } from './components/excel-grid';
2
+ export { GridCell } from './components/grid-cell';
3
+ export { useGridSelection } from './hooks/use-grid-selection';
4
+ export { useGridClipboard } from './hooks/use-grid-clipboard';
5
+ export { useGridDragFill } from './hooks/use-grid-drag-fill';
6
+ export { cn } from './utils/cn';
7
+ export { formatCurrency, parseCurrency } from './utils/format-utils';
8
+ export { coordToKey, keyToCoord, getCellsInRange, isCellInRange, parseTSV, toTSV, normalizeRange, getFillTargetCells, } from './utils/grid-utils';
9
+ export type { CellCoord, SelectionRange, ColHeader, ColHeaderGroup, RowHeaderGroup, GridStyles, ExcelGridProps, GridCellProps, } from './types';
2
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGlD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAG7D,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EACL,UAAU,EACV,UAAU,EACV,eAAe,EACf,aAAa,EACb,QAAQ,EACR,KAAK,EACL,cAAc,EACd,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAG5B,YAAY,EACV,SAAS,EACT,cAAc,EACd,SAAS,EACT,cAAc,EACd,cAAc,EACd,UAAU,EACV,cAAc,EACd,aAAa,GACd,MAAM,SAAS,CAAC"}
@@ -1 +1,535 @@
1
-
1
+ import { jsxs as j, jsx as E } from "react/jsx-runtime";
2
+ import { useState as L, useCallback as x, useEffect as $, useRef as _, useMemo as q } from "react";
3
+ function R(...n) {
4
+ return n.filter(Boolean).join(" ");
5
+ }
6
+ function on(n) {
7
+ return `${n.row}-${n.col}`;
8
+ }
9
+ function tn(n) {
10
+ const [o, e] = n.split("-").map(Number);
11
+ return { row: o, col: e };
12
+ }
13
+ function en(n, o) {
14
+ if (!n || !o) return n ? [n] : [];
15
+ const e = Math.min(n.row, o.row), s = Math.max(n.row, o.row), i = Math.min(n.col, o.col), b = Math.max(n.col, o.col), c = [];
16
+ for (let f = e; f <= s; f++)
17
+ for (let a = i; a <= b; a++)
18
+ c.push({ row: f, col: a });
19
+ return c;
20
+ }
21
+ function J(n, o) {
22
+ if (!o.start) return !1;
23
+ const e = o.end || o.start, s = Math.min(o.start.row, e.row), i = Math.max(o.start.row, e.row), b = Math.min(o.start.col, e.col), c = Math.max(o.start.col, e.col);
24
+ return n.row >= s && n.row <= i && n.col >= b && n.col <= c;
25
+ }
26
+ function O(n) {
27
+ return n.split(/\r?\n/).filter((o) => o.trim()).map((o) => o.split(" ").map((e) => e.trim()));
28
+ }
29
+ function Q(n) {
30
+ return n.map((o) => o.join(" ")).join(`
31
+ `);
32
+ }
33
+ function B(n) {
34
+ return !n.start || !n.end ? n : {
35
+ start: {
36
+ row: Math.min(n.start.row, n.end.row),
37
+ col: Math.min(n.start.col, n.end.col)
38
+ },
39
+ end: {
40
+ row: Math.max(n.start.row, n.end.row),
41
+ col: Math.max(n.start.col, n.end.col)
42
+ }
43
+ };
44
+ }
45
+ function ln(n, o) {
46
+ if (n.row === o.row && n.col === o.col)
47
+ return [];
48
+ const e = Math.min(n.row, o.row), s = Math.max(n.row, o.row), i = Math.min(n.col, o.col), b = Math.max(n.col, o.col), c = [];
49
+ for (let f = e; f <= s; f++)
50
+ for (let a = i; a <= b; a++)
51
+ f === n.row && a === n.col || c.push({ row: f, col: a });
52
+ return c;
53
+ }
54
+ function W() {
55
+ const [n, o] = L({
56
+ start: null,
57
+ end: null
58
+ }), [e, s] = L(!1), i = x((u) => {
59
+ o({ start: u, end: u }), s(!0);
60
+ }, []), b = x(
61
+ (u) => {
62
+ e && o((l) => ({ ...l, end: u }));
63
+ },
64
+ [e]
65
+ ), c = x(() => {
66
+ s(!1);
67
+ }, []), f = x(() => {
68
+ o({ start: null, end: null }), s(!1);
69
+ }, []), a = x(
70
+ (u) => J(u, n),
71
+ [n]
72
+ );
73
+ return $(() => {
74
+ const u = () => {
75
+ e && s(!1);
76
+ };
77
+ return window.addEventListener("mouseup", u), () => window.removeEventListener("mouseup", u);
78
+ }, [e]), {
79
+ selection: n,
80
+ isSelecting: e,
81
+ isCellSelected: a,
82
+ handleCellMouseDown: i,
83
+ handleCellMouseEnter: b,
84
+ handleMouseUp: c,
85
+ clearSelection: f,
86
+ setSelection: o
87
+ };
88
+ }
89
+ function X({
90
+ selection: n,
91
+ getValue: o,
92
+ setValues: e,
93
+ rowCount: s,
94
+ colCount: i
95
+ }) {
96
+ const b = x(() => {
97
+ const l = B(n);
98
+ if (!l.start || !l.end) return [];
99
+ const y = [];
100
+ for (let C = l.start.row; C <= l.end.row; C++) {
101
+ const h = [];
102
+ for (let D = l.start.col; D <= l.end.col; D++)
103
+ h.push(o({ row: C, col: D }));
104
+ y.push(h);
105
+ }
106
+ return y;
107
+ }, [n, o]), c = x(async () => {
108
+ const l = b();
109
+ if (l.length === 0) return;
110
+ const y = Q(l);
111
+ try {
112
+ await navigator.clipboard.writeText(y);
113
+ } catch (C) {
114
+ console.error("Clipboard copy failed:", C);
115
+ }
116
+ }, [b]), f = x(async () => {
117
+ if (n.start)
118
+ try {
119
+ const l = await navigator.clipboard.readText(), y = O(l);
120
+ if (y.length === 0) return;
121
+ const C = n.start.row, h = n.start.col, D = [];
122
+ y.forEach((d, M) => {
123
+ const w = C + M;
124
+ w >= s || d.forEach((m, v) => {
125
+ const F = h + v;
126
+ F >= i || D.push({ coord: { row: w, col: F }, value: m });
127
+ });
128
+ }), D.length > 0 && e(D);
129
+ } catch (l) {
130
+ console.error("Clipboard paste failed:", l);
131
+ }
132
+ }, [n.start, e, s, i]), a = x(() => {
133
+ const l = B(n);
134
+ if (!l.start || !l.end) return;
135
+ const y = [];
136
+ for (let C = l.start.row; C <= l.end.row; C++)
137
+ for (let h = l.start.col; h <= l.end.col; h++)
138
+ y.push({ coord: { row: C, col: h }, value: "" });
139
+ y.length > 0 && e(y);
140
+ }, [n, e]), u = x(
141
+ (l) => {
142
+ (l.ctrlKey || l.metaKey) && l.key === "c" && (l.preventDefault(), c()), (l.ctrlKey || l.metaKey) && l.key === "v" && (l.preventDefault(), f()), (l.key === "Backspace" || l.key === "Delete") && (l.preventDefault(), a());
143
+ },
144
+ [c, f, a]
145
+ );
146
+ return {
147
+ handleCopy: c,
148
+ handlePaste: f,
149
+ handleKeyDown: u
150
+ };
151
+ }
152
+ function Y(n) {
153
+ if (n.trim() === "") return null;
154
+ const o = Number(n);
155
+ return isNaN(o) ? null : o;
156
+ }
157
+ function P(n) {
158
+ if (n.length === 0) return null;
159
+ const o = n.map(Y);
160
+ if (o.some((i) => i === null)) return null;
161
+ const e = o;
162
+ if (e.length === 1)
163
+ return { numbers: e, diff: 0 };
164
+ const s = e[1] - e[0];
165
+ for (let i = 2; i < e.length; i++)
166
+ if (e[i] - e[i - 1] !== s)
167
+ return { numbers: e, diff: 0 };
168
+ return { numbers: e, diff: s };
169
+ }
170
+ function Z({
171
+ selection: n,
172
+ getValue: o,
173
+ setValues: e
174
+ }) {
175
+ const [s, i] = L(null), [b, c] = L([]), [f, a] = L(!1), [u, l] = L(null), y = x((d) => {
176
+ i(d), a(!0), c([]), l(null);
177
+ }, []), C = x(
178
+ (d) => {
179
+ if (!f || !n.start) return;
180
+ const M = B(n);
181
+ if (!M.start || !M.end) return;
182
+ l(d);
183
+ const w = {
184
+ row: Math.min(M.start.row, d.row),
185
+ col: Math.min(M.start.col, d.col)
186
+ }, m = {
187
+ row: Math.max(M.end.row, d.row),
188
+ col: Math.max(M.end.col, d.col)
189
+ }, v = [];
190
+ for (let F = w.row; F <= m.row; F++)
191
+ for (let r = w.col; r <= m.col; r++)
192
+ F >= M.start.row && F <= M.end.row && r >= M.start.col && r <= M.end.col || v.push({ row: F, col: r });
193
+ c(v);
194
+ },
195
+ [f, n]
196
+ ), h = x(() => {
197
+ if (!n.start || b.length === 0 || !u) {
198
+ i(null), c([]), a(!1), l(null);
199
+ return;
200
+ }
201
+ const d = B(n);
202
+ if (!d.start || !d.end) {
203
+ i(null), c([]), a(!1), l(null);
204
+ return;
205
+ }
206
+ const M = [], w = d.start, m = d.end, v = m.row - w.row + 1, F = m.col - w.col + 1;
207
+ b.forEach((r) => {
208
+ let T, k;
209
+ if (r.row < w.row) {
210
+ const S = w.row - r.row;
211
+ T = m.row - (S - 1) % v;
212
+ } else if (r.row > m.row) {
213
+ const S = r.row - m.row;
214
+ T = w.row + (S - 1) % v;
215
+ } else
216
+ T = r.row;
217
+ if (r.col < w.col) {
218
+ const S = w.col - r.col;
219
+ k = m.col - (S - 1) % F;
220
+ } else if (r.col > m.col) {
221
+ const S = r.col - m.col;
222
+ k = w.col + (S - 1) % F;
223
+ } else
224
+ k = r.col;
225
+ let z = o({ row: T, col: k });
226
+ if (r.row !== T && r.col >= w.col && r.col <= m.col) {
227
+ const S = [];
228
+ for (let t = w.row; t <= m.row; t++)
229
+ S.push(o({ row: t, col: r.col }));
230
+ const g = P(S);
231
+ if (g && g.diff !== 0) {
232
+ if (r.row > m.row) {
233
+ const t = r.row - m.row;
234
+ z = String(g.numbers[g.numbers.length - 1] + g.diff * t);
235
+ } else if (r.row < w.row) {
236
+ const t = w.row - r.row;
237
+ z = String(g.numbers[0] - g.diff * t);
238
+ }
239
+ }
240
+ }
241
+ if (r.col !== k && r.row >= w.row && r.row <= m.row) {
242
+ const S = [];
243
+ for (let t = w.col; t <= m.col; t++)
244
+ S.push(o({ row: r.row, col: t }));
245
+ const g = P(S);
246
+ if (g && g.diff !== 0) {
247
+ if (r.col > m.col) {
248
+ const t = r.col - m.col;
249
+ z = String(g.numbers[g.numbers.length - 1] + g.diff * t);
250
+ } else if (r.col < w.col) {
251
+ const t = w.col - r.col;
252
+ z = String(g.numbers[0] - g.diff * t);
253
+ }
254
+ }
255
+ }
256
+ M.push({ coord: r, value: z });
257
+ }), M.length > 0 && e(M), i(null), c([]), a(!1), l(null);
258
+ }, [n, b, u, o, e]), D = x(
259
+ (d) => b.some(
260
+ (M) => M.row === d.row && M.col === d.col
261
+ ),
262
+ [b]
263
+ );
264
+ return $(() => {
265
+ const d = () => {
266
+ f && h();
267
+ };
268
+ return window.addEventListener("mouseup", d), () => window.removeEventListener("mouseup", d);
269
+ }, [f, h]), {
270
+ fillSource: s,
271
+ fillTargets: b,
272
+ isDraggingFill: f,
273
+ isFillTarget: D,
274
+ handleFillHandleMouseDown: y,
275
+ handleCellMouseEnterForFill: C,
276
+ handleFillMouseUp: h
277
+ };
278
+ }
279
+ function V({
280
+ coord: n,
281
+ value: o,
282
+ isSelected: e,
283
+ isFillTarget: s,
284
+ showFillHandle: i,
285
+ onMouseDown: b,
286
+ onMouseEnter: c,
287
+ onChange: f,
288
+ onFillHandleMouseDown: a,
289
+ styles: u
290
+ }) {
291
+ const l = (h) => {
292
+ f(n, h.target.value);
293
+ }, y = (h) => {
294
+ h.target.classList.contains("fill-handle") || b(n);
295
+ }, C = (h) => {
296
+ h.stopPropagation(), h.preventDefault(), a(n);
297
+ };
298
+ return /* @__PURE__ */ j(
299
+ "td",
300
+ {
301
+ className: R(
302
+ "relative border border-gray-300 p-0",
303
+ u?.cell,
304
+ e && (u?.selected ?? "bg-blue-100 ring-2 ring-inset ring-blue-500"),
305
+ s && (u?.fillTarget ?? "bg-blue-50")
306
+ ),
307
+ onMouseDown: y,
308
+ onMouseEnter: () => c(n),
309
+ children: [
310
+ /* @__PURE__ */ E(
311
+ "input",
312
+ {
313
+ type: "text",
314
+ value: o,
315
+ onChange: l,
316
+ className: R(
317
+ "w-full h-full px-1 py-1 text-right text-xs bg-transparent outline-none cursor-cell",
318
+ e && "bg-transparent"
319
+ )
320
+ }
321
+ ),
322
+ i && /* @__PURE__ */ E(
323
+ "div",
324
+ {
325
+ className: R(
326
+ "fill-handle absolute -bottom-0.5 -right-0.5 w-2 h-2 cursor-crosshair z-20",
327
+ u?.fillHandle ?? "bg-blue-500"
328
+ ),
329
+ onMouseDown: C
330
+ }
331
+ )
332
+ ]
333
+ }
334
+ );
335
+ }
336
+ function rn({
337
+ data: n,
338
+ onChange: o,
339
+ rowHeaders: e,
340
+ colHeaders: s,
341
+ className: i,
342
+ rowHeaderTitle: b = "",
343
+ styles: c
344
+ }) {
345
+ const f = _(null), a = n.length, u = q(() => s ? s.reduce(
346
+ (t, p) => t + p.headers.length,
347
+ 0
348
+ ) : n[0]?.length ?? 0, [s, n]), l = q(() => s ? s.flatMap((t) => t.headers) : [], [s]), y = x(
349
+ (t) => t.row < 0 || t.row >= a || t.col < 0 || t.col >= u ? "" : n[t.row]?.[t.col] ?? "",
350
+ [n, a, u]
351
+ ), C = x(
352
+ (t, p) => {
353
+ const N = n.map(
354
+ (K, G) => G === t.row ? K.map(
355
+ (I, U) => U === t.col ? p : I
356
+ ) : K
357
+ );
358
+ o(N);
359
+ },
360
+ [n, o]
361
+ ), h = x(
362
+ (t) => {
363
+ if (t.length === 0) return;
364
+ const p = n.map((N) => [...N]);
365
+ t.forEach(({ coord: N, value: K }) => {
366
+ N.row >= 0 && N.row < a && N.col >= 0 && N.col < u && (p[N.row][N.col] = K);
367
+ }), o(p);
368
+ },
369
+ [n, o, a, u]
370
+ ), {
371
+ selection: D,
372
+ isSelecting: d,
373
+ isCellSelected: M,
374
+ handleCellMouseDown: w,
375
+ handleCellMouseEnter: m
376
+ } = W(), { handleKeyDown: v } = X({
377
+ selection: D,
378
+ getValue: y,
379
+ setValues: h,
380
+ rowCount: a,
381
+ colCount: u
382
+ }), {
383
+ isDraggingFill: F,
384
+ isFillTarget: r,
385
+ handleFillHandleMouseDown: T,
386
+ handleCellMouseEnterForFill: k
387
+ } = Z({
388
+ selection: D,
389
+ getValue: y,
390
+ setValues: h
391
+ }), z = x(
392
+ (t) => {
393
+ F ? k(t) : d && m(t);
394
+ },
395
+ [
396
+ F,
397
+ d,
398
+ k,
399
+ m
400
+ ]
401
+ ), S = x(
402
+ (t, p) => {
403
+ C(t, p);
404
+ },
405
+ [C]
406
+ ), g = x(
407
+ (t) => {
408
+ if (!D.start) return !1;
409
+ const p = B(D);
410
+ return !p.start || !p.end ? !1 : t.row === p.end.row && t.col === p.end.col;
411
+ },
412
+ [D]
413
+ );
414
+ return /* @__PURE__ */ E(
415
+ "div",
416
+ {
417
+ ref: f,
418
+ className: R("outline-none overflow-x-auto", i),
419
+ tabIndex: 0,
420
+ onKeyDown: v,
421
+ children: /* @__PURE__ */ j("table", { className: "border-collapse text-xs select-none", children: [
422
+ /* @__PURE__ */ j("thead", { children: [
423
+ s && /* @__PURE__ */ j("tr", { children: [
424
+ e && /* @__PURE__ */ E(
425
+ "th",
426
+ {
427
+ className: R(
428
+ "sticky left-0 z-10 bg-gray-100 border border-gray-300 px-2 py-1.5 text-center font-medium",
429
+ c?.rowHeader
430
+ ),
431
+ children: b
432
+ }
433
+ ),
434
+ s.map((t, p) => /* @__PURE__ */ E(
435
+ "th",
436
+ {
437
+ colSpan: t.headers.length,
438
+ className: R(
439
+ "bg-blue-100 border border-gray-300 px-1 py-1.5 text-center font-medium text-blue-700",
440
+ c?.colGroup,
441
+ t.className
442
+ ),
443
+ children: t.label
444
+ },
445
+ p
446
+ ))
447
+ ] }),
448
+ s && /* @__PURE__ */ j("tr", { children: [
449
+ e && /* @__PURE__ */ E(
450
+ "th",
451
+ {
452
+ className: R(
453
+ "sticky left-0 z-10 bg-gray-100 border border-gray-300 px-2 py-1",
454
+ c?.rowHeader
455
+ )
456
+ }
457
+ ),
458
+ l.map((t) => /* @__PURE__ */ E(
459
+ "th",
460
+ {
461
+ className: R(
462
+ "bg-gray-50 border border-gray-300 px-1 py-1 text-center font-medium text-[11px]",
463
+ c?.colHeader,
464
+ t.className
465
+ ),
466
+ title: t.description,
467
+ children: t.label
468
+ },
469
+ t.key
470
+ ))
471
+ ] })
472
+ ] }),
473
+ /* @__PURE__ */ E("tbody", { children: n.map((t, p) => /* @__PURE__ */ j("tr", { children: [
474
+ e && e[p] && /* @__PURE__ */ E(
475
+ "td",
476
+ {
477
+ className: R(
478
+ "sticky left-0 z-10 bg-gray-100 border border-gray-300 px-1 py-1.5 text-center font-medium",
479
+ c?.rowHeader,
480
+ e[p].className
481
+ ),
482
+ title: e[p].description,
483
+ children: e[p].label
484
+ }
485
+ ),
486
+ t.map((N, K) => {
487
+ const G = { row: p, col: K }, I = M(G), U = r(G), A = g(G);
488
+ return /* @__PURE__ */ E(
489
+ V,
490
+ {
491
+ coord: G,
492
+ value: y(G),
493
+ isSelected: I,
494
+ isFillTarget: U,
495
+ showFillHandle: A && !F,
496
+ onMouseDown: w,
497
+ onMouseEnter: z,
498
+ onChange: S,
499
+ onFillHandleMouseDown: T,
500
+ styles: c
501
+ },
502
+ K
503
+ );
504
+ })
505
+ ] }, p)) })
506
+ ] })
507
+ }
508
+ );
509
+ }
510
+ function sn(n) {
511
+ const o = typeof n == "string" ? parseFloat(n.replace(/,/g, "")) : n;
512
+ return isNaN(o) ? "0" : new Intl.NumberFormat("ko-KR").format(o);
513
+ }
514
+ function cn(n) {
515
+ const o = parseInt(n.replace(/,/g, ""), 10);
516
+ return isNaN(o) ? 0 : o;
517
+ }
518
+ export {
519
+ rn as ExcelGrid,
520
+ V as GridCell,
521
+ R as cn,
522
+ on as coordToKey,
523
+ sn as formatCurrency,
524
+ en as getCellsInRange,
525
+ ln as getFillTargetCells,
526
+ J as isCellInRange,
527
+ tn as keyToCoord,
528
+ B as normalizeRange,
529
+ cn as parseCurrency,
530
+ O as parseTSV,
531
+ Q as toTSV,
532
+ X as useGridClipboard,
533
+ Z as useGridDragFill,
534
+ W as useGridSelection
535
+ };
@@ -1 +1,2 @@
1
- (function(n){typeof define=="function"&&define.amd?define(n):n()})((function(){"use strict"}));
1
+ (function(m,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],g):(m=typeof globalThis<"u"?globalThis:m||self,g(m.ReactExcelLite={},m.ReactJsxRuntime,m.React))})(this,(function(m,g,s){"use strict";function T(...l){return l.filter(Boolean).join(" ")}function _(l){return`${l.row}-${l.col}`}function Q(l){const[n,o]=l.split("-").map(Number);return{row:n,col:o}}function W(l,n){if(!l||!n)return l?[l]:[];const o=Math.min(l.row,n.row),c=Math.max(l.row,n.row),a=Math.min(l.col,n.col),y=Math.max(l.col,n.col),i=[];for(let w=o;w<=c;w++)for(let u=a;u<=y;u++)i.push({row:w,col:u});return i}function B(l,n){if(!n.start)return!1;const o=n.end||n.start,c=Math.min(n.start.row,o.row),a=Math.max(n.start.row,o.row),y=Math.min(n.start.col,o.col),i=Math.max(n.start.col,o.col);return l.row>=c&&l.row<=a&&l.col>=y&&l.col<=i}function U(l){return l.split(/\r?\n/).filter(n=>n.trim()).map(n=>n.split(" ").map(o=>o.trim()))}function P(l){return l.map(n=>n.join(" ")).join(`
2
+ `)}function I(l){return!l.start||!l.end?l:{start:{row:Math.min(l.start.row,l.end.row),col:Math.min(l.start.col,l.end.col)},end:{row:Math.max(l.start.row,l.end.row),col:Math.max(l.start.col,l.end.col)}}}function X(l,n){if(l.row===n.row&&l.col===n.col)return[];const o=Math.min(l.row,n.row),c=Math.max(l.row,n.row),a=Math.min(l.col,n.col),y=Math.max(l.col,n.col),i=[];for(let w=o;w<=c;w++)for(let u=a;u<=y;u++)w===l.row&&u===l.col||i.push({row:w,col:u});return i}function $(){const[l,n]=s.useState({start:null,end:null}),[o,c]=s.useState(!1),a=s.useCallback(f=>{n({start:f,end:f}),c(!0)},[]),y=s.useCallback(f=>{o&&n(t=>({...t,end:f}))},[o]),i=s.useCallback(()=>{c(!1)},[]),w=s.useCallback(()=>{n({start:null,end:null}),c(!1)},[]),u=s.useCallback(f=>B(f,l),[l]);return s.useEffect(()=>{const f=()=>{o&&c(!1)};return window.addEventListener("mouseup",f),()=>window.removeEventListener("mouseup",f)},[o]),{selection:l,isSelecting:o,isCellSelected:u,handleCellMouseDown:a,handleCellMouseEnter:y,handleMouseUp:i,clearSelection:w,setSelection:n}}function A({selection:l,getValue:n,setValues:o,rowCount:c,colCount:a}){const y=s.useCallback(()=>{const t=I(l);if(!t.start||!t.end)return[];const S=[];for(let k=t.start.row;k<=t.end.row;k++){const b=[];for(let D=t.start.col;D<=t.end.col;D++)b.push(n({row:k,col:D}));S.push(b)}return S},[l,n]),i=s.useCallback(async()=>{const t=y();if(t.length===0)return;const S=P(t);try{await navigator.clipboard.writeText(S)}catch(k){console.error("Clipboard copy failed:",k)}},[y]),w=s.useCallback(async()=>{if(l.start)try{const t=await navigator.clipboard.readText(),S=U(t);if(S.length===0)return;const k=l.start.row,b=l.start.col,D=[];S.forEach((p,M)=>{const d=k+M;d>=c||p.forEach((h,v)=>{const F=b+v;F>=a||D.push({coord:{row:d,col:F},value:h})})}),D.length>0&&o(D)}catch(t){console.error("Clipboard paste failed:",t)}},[l.start,o,c,a]),u=s.useCallback(()=>{const t=I(l);if(!t.start||!t.end)return;const S=[];for(let k=t.start.row;k<=t.end.row;k++)for(let b=t.start.col;b<=t.end.col;b++)S.push({coord:{row:k,col:b},value:""});S.length>0&&o(S)},[l,o]),f=s.useCallback(t=>{(t.ctrlKey||t.metaKey)&&t.key==="c"&&(t.preventDefault(),i()),(t.ctrlKey||t.metaKey)&&t.key==="v"&&(t.preventDefault(),w()),(t.key==="Backspace"||t.key==="Delete")&&(t.preventDefault(),u())},[i,w,u]);return{handleCopy:i,handlePaste:w,handleKeyDown:f}}function Y(l){if(l.trim()==="")return null;const n=Number(l);return isNaN(n)?null:n}function J(l){if(l.length===0)return null;const n=l.map(Y);if(n.some(a=>a===null))return null;const o=n;if(o.length===1)return{numbers:o,diff:0};const c=o[1]-o[0];for(let a=2;a<o.length;a++)if(o[a]-o[a-1]!==c)return{numbers:o,diff:0};return{numbers:o,diff:c}}function O({selection:l,getValue:n,setValues:o}){const[c,a]=s.useState(null),[y,i]=s.useState([]),[w,u]=s.useState(!1),[f,t]=s.useState(null),S=s.useCallback(p=>{a(p),u(!0),i([]),t(null)},[]),k=s.useCallback(p=>{if(!w||!l.start)return;const M=I(l);if(!M.start||!M.end)return;t(p);const d={row:Math.min(M.start.row,p.row),col:Math.min(M.start.col,p.col)},h={row:Math.max(M.end.row,p.row),col:Math.max(M.end.col,p.col)},v=[];for(let F=d.row;F<=h.row;F++)for(let r=d.col;r<=h.col;r++)F>=M.start.row&&F<=M.end.row&&r>=M.start.col&&r<=M.end.col||v.push({row:F,col:r});i(v)},[w,l]),b=s.useCallback(()=>{if(!l.start||y.length===0||!f){a(null),i([]),u(!1),t(null);return}const p=I(l);if(!p.start||!p.end){a(null),i([]),u(!1),t(null);return}const M=[],d=p.start,h=p.end,v=h.row-d.row+1,F=h.col-d.col+1;y.forEach(r=>{let G,z;if(r.row<d.row){const E=d.row-r.row;G=h.row-(E-1)%v}else if(r.row>h.row){const E=r.row-h.row;G=d.row+(E-1)%v}else G=r.row;if(r.col<d.col){const E=d.col-r.col;z=h.col-(E-1)%F}else if(r.col>h.col){const E=r.col-h.col;z=d.col+(E-1)%F}else z=r.col;let K=n({row:G,col:z});if(r.row!==G&&r.col>=d.col&&r.col<=h.col){const E=[];for(let e=d.row;e<=h.row;e++)E.push(n({row:e,col:r.col}));const x=J(E);if(x&&x.diff!==0){if(r.row>h.row){const e=r.row-h.row;K=String(x.numbers[x.numbers.length-1]+x.diff*e)}else if(r.row<d.row){const e=d.row-r.row;K=String(x.numbers[0]-x.diff*e)}}}if(r.col!==z&&r.row>=d.row&&r.row<=h.row){const E=[];for(let e=d.col;e<=h.col;e++)E.push(n({row:r.row,col:e}));const x=J(E);if(x&&x.diff!==0){if(r.col>h.col){const e=r.col-h.col;K=String(x.numbers[x.numbers.length-1]+x.diff*e)}else if(r.col<d.col){const e=d.col-r.col;K=String(x.numbers[0]-x.diff*e)}}}M.push({coord:r,value:K})}),M.length>0&&o(M),a(null),i([]),u(!1),t(null)},[l,y,f,n,o]),D=s.useCallback(p=>y.some(M=>M.row===p.row&&M.col===p.col),[y]);return s.useEffect(()=>{const p=()=>{w&&b()};return window.addEventListener("mouseup",p),()=>window.removeEventListener("mouseup",p)},[w,b]),{fillSource:c,fillTargets:y,isDraggingFill:w,isFillTarget:D,handleFillHandleMouseDown:S,handleCellMouseEnterForFill:k,handleFillMouseUp:b}}function V({coord:l,value:n,isSelected:o,isFillTarget:c,showFillHandle:a,onMouseDown:y,onMouseEnter:i,onChange:w,onFillHandleMouseDown:u,styles:f}){const t=b=>{w(l,b.target.value)},S=b=>{b.target.classList.contains("fill-handle")||y(l)},k=b=>{b.stopPropagation(),b.preventDefault(),u(l)};return g.jsxs("td",{className:T("relative border border-gray-300 p-0",f?.cell,o&&(f?.selected??"bg-blue-100 ring-2 ring-inset ring-blue-500"),c&&(f?.fillTarget??"bg-blue-50")),onMouseDown:S,onMouseEnter:()=>i(l),children:[g.jsx("input",{type:"text",value:n,onChange:t,className:T("w-full h-full px-1 py-1 text-right text-xs bg-transparent outline-none cursor-cell",o&&"bg-transparent")}),a&&g.jsx("div",{className:T("fill-handle absolute -bottom-0.5 -right-0.5 w-2 h-2 cursor-crosshair z-20",f?.fillHandle??"bg-blue-500"),onMouseDown:k})]})}function Z({data:l,onChange:n,rowHeaders:o,colHeaders:c,className:a,rowHeaderTitle:y="",styles:i}){const w=s.useRef(null),u=l.length,f=s.useMemo(()=>c?c.reduce((e,C)=>e+C.headers.length,0):l[0]?.length??0,[c,l]),t=s.useMemo(()=>c?c.flatMap(e=>e.headers):[],[c]),S=s.useCallback(e=>e.row<0||e.row>=u||e.col<0||e.col>=f?"":l[e.row]?.[e.col]??"",[l,u,f]),k=s.useCallback((e,C)=>{const N=l.map((R,j)=>j===e.row?R.map((L,q)=>q===e.col?C:L):R);n(N)},[l,n]),b=s.useCallback(e=>{if(e.length===0)return;const C=l.map(N=>[...N]);e.forEach(({coord:N,value:R})=>{N.row>=0&&N.row<u&&N.col>=0&&N.col<f&&(C[N.row][N.col]=R)}),n(C)},[l,n,u,f]),{selection:D,isSelecting:p,isCellSelected:M,handleCellMouseDown:d,handleCellMouseEnter:h}=$(),{handleKeyDown:v}=A({selection:D,getValue:S,setValues:b,rowCount:u,colCount:f}),{isDraggingFill:F,isFillTarget:r,handleFillHandleMouseDown:G,handleCellMouseEnterForFill:z}=O({selection:D,getValue:S,setValues:b}),K=s.useCallback(e=>{F?z(e):p&&h(e)},[F,p,z,h]),E=s.useCallback((e,C)=>{k(e,C)},[k]),x=s.useCallback(e=>{if(!D.start)return!1;const C=I(D);return!C.start||!C.end?!1:e.row===C.end.row&&e.col===C.end.col},[D]);return g.jsx("div",{ref:w,className:T("outline-none overflow-x-auto",a),tabIndex:0,onKeyDown:v,children:g.jsxs("table",{className:"border-collapse text-xs select-none",children:[g.jsxs("thead",{children:[c&&g.jsxs("tr",{children:[o&&g.jsx("th",{className:T("sticky left-0 z-10 bg-gray-100 border border-gray-300 px-2 py-1.5 text-center font-medium",i?.rowHeader),children:y}),c.map((e,C)=>g.jsx("th",{colSpan:e.headers.length,className:T("bg-blue-100 border border-gray-300 px-1 py-1.5 text-center font-medium text-blue-700",i?.colGroup,e.className),children:e.label},C))]}),c&&g.jsxs("tr",{children:[o&&g.jsx("th",{className:T("sticky left-0 z-10 bg-gray-100 border border-gray-300 px-2 py-1",i?.rowHeader)}),t.map(e=>g.jsx("th",{className:T("bg-gray-50 border border-gray-300 px-1 py-1 text-center font-medium text-[11px]",i?.colHeader,e.className),title:e.description,children:e.label},e.key))]})]}),g.jsx("tbody",{children:l.map((e,C)=>g.jsxs("tr",{children:[o&&o[C]&&g.jsx("td",{className:T("sticky left-0 z-10 bg-gray-100 border border-gray-300 px-1 py-1.5 text-center font-medium",i?.rowHeader,o[C].className),title:o[C].description,children:o[C].label}),e.map((N,R)=>{const j={row:C,col:R},L=M(j),q=r(j),nl=x(j);return g.jsx(V,{coord:j,value:S(j),isSelected:L,isFillTarget:q,showFillHandle:nl&&!F,onMouseDown:d,onMouseEnter:K,onChange:E,onFillHandleMouseDown:G,styles:i},R)})]},C))})]})})}function H(l){const n=typeof l=="string"?parseFloat(l.replace(/,/g,"")):l;return isNaN(n)?"0":new Intl.NumberFormat("ko-KR").format(n)}function ll(l){const n=parseInt(l.replace(/,/g,""),10);return isNaN(n)?0:n}m.ExcelGrid=Z,m.GridCell=V,m.cn=T,m.coordToKey=_,m.formatCurrency=H,m.getCellsInRange=W,m.getFillTargetCells=X,m.isCellInRange=B,m.keyToCoord=Q,m.normalizeRange=I,m.parseCurrency=ll,m.parseTSV=U,m.toTSV=P,m.useGridClipboard=A,m.useGridDragFill=O,m.useGridSelection=$,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"})}));
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Cell coordinate
3
+ */
4
+ export interface CellCoord {
5
+ row: number;
6
+ col: number;
7
+ }
8
+ /**
9
+ * Selection range
10
+ */
11
+ export interface SelectionRange {
12
+ start: CellCoord | null;
13
+ end: CellCoord | null;
14
+ }
15
+ /**
16
+ * Column header definition
17
+ */
18
+ export interface ColHeader {
19
+ key: string;
20
+ label: string;
21
+ description?: string;
22
+ /** Custom class name for this header cell */
23
+ className?: string;
24
+ }
25
+ /**
26
+ * Column header group
27
+ */
28
+ export interface ColHeaderGroup {
29
+ label: string;
30
+ headers: ColHeader[];
31
+ /** Custom class name for this group header */
32
+ className?: string;
33
+ }
34
+ /**
35
+ * Row header group
36
+ */
37
+ export interface RowHeaderGroup {
38
+ key: string;
39
+ label: string;
40
+ description?: string;
41
+ /** Custom class name for this row header */
42
+ className?: string;
43
+ }
44
+ /**
45
+ * Grid styles configuration
46
+ */
47
+ export interface GridStyles {
48
+ /** Data cell style */
49
+ cell?: string;
50
+ /** Selected cell style */
51
+ selected?: string;
52
+ /** Fill target cell style (when dragging fill handle) */
53
+ fillTarget?: string;
54
+ /** Fill handle style (bottom-right corner handle) */
55
+ fillHandle?: string;
56
+ /** Column group header style */
57
+ colGroup?: string;
58
+ /** Column header style */
59
+ colHeader?: string;
60
+ /** Row header style */
61
+ rowHeader?: string;
62
+ }
63
+ /**
64
+ * Grid Props
65
+ */
66
+ export interface ExcelGridProps {
67
+ /** 2D string array data */
68
+ data: string[][];
69
+ /** Data change callback */
70
+ onChange: (data: string[][]) => void;
71
+ /** Row header definitions (overridable per row) */
72
+ rowHeaders?: RowHeaderGroup[];
73
+ /** Column header group definitions */
74
+ colHeaders?: ColHeaderGroup[];
75
+ /** Additional class name for container */
76
+ className?: string;
77
+ /** Row header column title */
78
+ rowHeaderTitle?: string;
79
+ /** Style configuration */
80
+ styles?: GridStyles;
81
+ }
82
+ /**
83
+ * Cell component Props
84
+ */
85
+ export interface GridCellProps {
86
+ coord: CellCoord;
87
+ value: string;
88
+ isSelected: boolean;
89
+ isFillTarget: boolean;
90
+ showFillHandle: boolean;
91
+ onMouseDown: (coord: CellCoord) => void;
92
+ onMouseEnter: (coord: CellCoord) => void;
93
+ onChange: (coord: CellCoord, value: string) => void;
94
+ onFillHandleMouseDown: (coord: CellCoord) => void;
95
+ /** Cell styles */
96
+ styles?: Pick<GridStyles, "cell" | "selected" | "fillTarget" | "fillHandle">;
97
+ }
98
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB,GAAG,EAAE,SAAS,GAAG,IAAI,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,sBAAsB;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,2BAA2B;IAC3B,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;IACjB,2BAA2B;IAC3B,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,IAAI,CAAC;IACrC,mDAAmD;IACnD,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B,sCAAsC;IACtC,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0BAA0B;IAC1B,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACxC,YAAY,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACzC,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,qBAAqB,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAClD,kBAAkB;IAClB,MAAM,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,YAAY,GAAG,YAAY,CAAC,CAAC;CAC9E"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Classname merge utility (simplified version)
3
+ * Filters out falsy values and joins classnames with space
4
+ */
5
+ export declare function cn(...classes: (string | boolean | undefined | null)[]): string;
6
+ //# sourceMappingURL=cn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cn.d.ts","sourceRoot":"","sources":["../../src/utils/cn.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,EAAE,CAChB,GAAG,OAAO,EAAE,CAAC,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC,EAAE,GAClD,MAAM,CAER"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Format number with thousand separators
3
+ * @param value Number to format
4
+ * @returns Formatted string (e.g., 1000000 -> "1,000,000")
5
+ */
6
+ export declare function formatCurrency(value: number | string): string;
7
+ /**
8
+ * Parse string with thousand separators to number
9
+ * @param value String to parse
10
+ * @returns Number (e.g., "1,000,000" -> 1000000)
11
+ */
12
+ export declare function parseCurrency(value: string): number;
13
+ //# sourceMappingURL=format-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-utils.d.ts","sourceRoot":"","sources":["../../src/utils/format-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAK7D;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnD"}
@@ -0,0 +1,34 @@
1
+ import { CellCoord, SelectionRange } from '../types';
2
+ /**
3
+ * Convert coordinate to string key
4
+ */
5
+ export declare function coordToKey(coord: CellCoord): string;
6
+ /**
7
+ * Convert string key to coordinate
8
+ */
9
+ export declare function keyToCoord(key: string): CellCoord;
10
+ /**
11
+ * Get all cell coordinates between two coordinates
12
+ */
13
+ export declare function getCellsInRange(start: CellCoord | null, end: CellCoord | null): CellCoord[];
14
+ /**
15
+ * Check if cell is within selection range
16
+ */
17
+ export declare function isCellInRange(coord: CellCoord, range: SelectionRange): boolean;
18
+ /**
19
+ * Parse TSV (Tab-Separated Values) string - supports Excel copy
20
+ */
21
+ export declare function parseTSV(text: string): string[][];
22
+ /**
23
+ * Convert 2D array to TSV string
24
+ */
25
+ export declare function toTSV(data: string[][]): string;
26
+ /**
27
+ * Normalize selection range (start is always top-left)
28
+ */
29
+ export declare function normalizeRange(range: SelectionRange): SelectionRange;
30
+ /**
31
+ * Calculate fill target cells (2D area support - excludes source cell)
32
+ */
33
+ export declare function getFillTargetCells(source: CellCoord, target: CellCoord): CellCoord[];
34
+ //# sourceMappingURL=grid-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grid-utils.d.ts","sourceRoot":"","sources":["../../src/utils/grid-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE1D;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAEnD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAGjD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,GAAG,IAAI,EACvB,GAAG,EAAE,SAAS,GAAG,IAAI,GACpB,SAAS,EAAE,CAeb;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,cAAc,GACpB,OAAO,CAeT;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAKjD;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,CAE9C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,CAapE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,SAAS,GAChB,SAAS,EAAE,CAsBb"}
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "react-excel-lite",
3
- "version": "0.0.1",
4
- "description": "Simple Excel-like editable grid component for React.",
3
+ "version": "0.0.4",
4
+ "description": "A lightweight, Excel-like editable grid component for React with cell selection, copy/paste, auto-fill with arithmetic sequence detection, and customizable styling.",
5
+ "license": "MIT",
5
6
  "type": "module",
6
7
  "main": "./dist/react-excel-lite.umd.cjs",
7
8
  "module": "./dist/react-excel-lite.js",
@@ -22,7 +23,8 @@
22
23
  },
23
24
  "peerDependencies": {
24
25
  "react": ">=18.0.0",
25
- "react-dom": ">=18.0.0"
26
+ "react-dom": ">=18.0.0",
27
+ "tailwindcss": ">=3.0.0"
26
28
  },
27
29
  "devDependencies": {
28
30
  "@vitejs/plugin-react": "^5.1.1",