@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.
package/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # @c-time/simple-ex-grid
2
+
3
+ Excel-like grid component for React. Touch-first design with clipboard support, drag selection, and three operating modes.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @c-time/simple-ex-grid
9
+ # or
10
+ pnpm add @c-time/simple-ex-grid
11
+ ```
12
+
13
+ **Peer dependencies:** React 19+
14
+
15
+ ## Quick Start
16
+
17
+ ```tsx
18
+ import { SimpleExGrid } from '@c-time/simple-ex-grid'
19
+ import type { ColumnDef } from '@c-time/simple-ex-grid'
20
+
21
+ interface Row {
22
+ id: number
23
+ name: string
24
+ price: number
25
+ }
26
+
27
+ const columns: ColumnDef<Row>[] = [
28
+ { key: 'id', header: 'ID', width: 60, editable: false },
29
+ { key: 'name', header: 'Name', width: 200 },
30
+ { key: 'price', header: 'Price', width: 120, parser: (s) => Number(s) || 0 },
31
+ ]
32
+
33
+ function App() {
34
+ const [rows, setRows] = useState<Row[]>([
35
+ { id: 1, name: 'Item A', price: 100 },
36
+ { id: 2, name: 'Item B', price: 200 },
37
+ ])
38
+
39
+ return (
40
+ <SimpleExGrid
41
+ rows={rows}
42
+ columns={columns}
43
+ mode="edit"
44
+ onChange={setRows}
45
+ />
46
+ )
47
+ }
48
+ ```
49
+
50
+ ## Modes
51
+
52
+ | Mode | Description |
53
+ |------|-------------|
54
+ | `select` (default) | Selection and copy/paste only. No cell editing. |
55
+ | `edit` | Re-tap active cell or press Enter/F2 to start editing. |
56
+ | `readonly` | Selection and copy only. All mutations disabled. |
57
+
58
+ ## Props
59
+
60
+ ### Data
61
+
62
+ | Prop | Type | Default | Description |
63
+ |------|------|---------|-------------|
64
+ | `rows` | `Row[]` | required | Row data array |
65
+ | `columns` | `ColumnDef<Row>[]` | required | Column definitions |
66
+ | `onChange` | `(rows, changes) => void` | - | Called when cell values change |
67
+
68
+ ### Mode & Display
69
+
70
+ | Prop | Type | Default | Description |
71
+ |------|------|---------|-------------|
72
+ | `mode` | `'select' \| 'edit' \| 'readonly'` | `'select'` | Operating mode |
73
+ | `showRowGripHeader` | `boolean` | `true` | Show row grip headers (left) |
74
+ | `showColumnGripHeader` | `boolean` | `true` | Show column grip headers (top) |
75
+ | `rowHeight` | `'single-line' \| 'fit-content'` | `'single-line'` | `single-line`: truncate with ellipsis. `fit-content`: wrap text |
76
+
77
+ ### Column Definition
78
+
79
+ | Property | Type | Default | Description |
80
+ |----------|------|---------|-------------|
81
+ | `key` | `keyof Row & string` | required | Row property key |
82
+ | `header` | `string` | required | Header display text |
83
+ | `width` | `number \| '${n}%' \| 'fit-content'` | `120` | Column width. Fixed px, percentage, or auto |
84
+ | `editable` | `boolean` | `true` | Set `false` to make column read-only |
85
+ | `formatter` | `(value, row) => string` | - | Display formatter |
86
+ | `parser` | `(input) => unknown` | - | Input parser (string to value) |
87
+ | `validator` | `(value) => string \| null` | - | Validation (return error message or null) |
88
+ | `sortMark` | `'asc' \| 'desc' \| null` | `null` | Display sort indicator (▲/▼) in header. Display only — sorting logic is the caller's responsibility. |
89
+ | `align` | `'left' \| 'center' \| 'right'` | - | Default text alignment for the column |
90
+ | `headerAlign` | `'left' \| 'center' \| 'right'` | - | Header text alignment (overrides `align`) |
91
+ | `hidden` | `boolean` | `false` | Hide column visually. Hidden columns are still included in clipboard operations. |
92
+ | `render` | `(value, row, rowIndex) => ReactNode` | - | Custom cell renderer. Returned node replaces default text display. |
93
+
94
+ ### Pagination
95
+
96
+ | Prop | Type | Default | Description |
97
+ |------|------|---------|-------------|
98
+ | `pagination` | `boolean` | `false` | Enable pagination UI |
99
+ | `pageSize` | `number` | `20` | Rows per page |
100
+ | `page` | `number` | - | Controlled current page (0-indexed) |
101
+ | `onPageChange` | `(page) => void` | - | Page change callback |
102
+ | `onPageSizeChange` | `(pageSize) => void` | - | Page size change callback |
103
+
104
+ ### Selection
105
+
106
+ | Prop | Type | Default | Description |
107
+ |------|------|---------|-------------|
108
+ | `selection` | `CellRange \| null` | - | Controlled selection range |
109
+ | `onSelectionChange` | `(range) => void` | - | Selection change callback |
110
+ | `onActiveCellChange` | `(row, col) => void` | - | Active cell move callback |
111
+
112
+ ### Clipboard
113
+
114
+ | Prop | Type | Default | Description |
115
+ |------|------|---------|-------------|
116
+ | `clipboard.enabled` | `boolean` | `true` | Enable clipboard operations |
117
+ | `clipboard.format` | `'tsv' \| 'html' \| 'both'` | `'both'` | Clipboard data format |
118
+
119
+ Keyboard shortcuts: `Ctrl+C` copy, `Ctrl+X` cut, `Ctrl+V` insert paste (add rows), `Ctrl+Shift+V` overwrite paste.
120
+
121
+ ### Cell Layout
122
+
123
+ | Prop | Type | Default | Description |
124
+ |------|------|---------|-------------|
125
+ | `colSpan` | `(row, col) => number` | - | Return column span for a cell. Spanned cells are merged visually. |
126
+ | `cellAlign` | `(row, col) => 'left' \| 'center' \| 'right' \| undefined` | - | Per-cell text alignment override (takes priority over column `align`). |
127
+
128
+ ### Row Mutation
129
+
130
+ | Prop | Type | Default | Description |
131
+ |------|------|---------|-------------|
132
+ | `allowRowAdd` | `boolean` | `false` | Allow adding rows (paste auto-extend, insert paste) |
133
+ | `allowRowDelete` | `boolean` | `false` | Allow deleting rows (context menu) |
134
+ | `onRowsAdd` | `(startRow, count) => void` | - | Fires after rows are added |
135
+ | `onRowsDelete` | `(startRow, count) => void` | - | Fires after rows are deleted |
136
+
137
+ ### Undo / Redo
138
+
139
+ | Prop | Type | Default | Description |
140
+ |------|------|---------|-------------|
141
+ | `maxHistory` | `number` | - | Maximum number of history entries to keep |
142
+ | `shouldRecordHistory` | `(changes, rowEvent?) => boolean` | - | Filter which mutations are recorded. `rowEvent` is `{ kind, startRow, count }` for row add/delete. |
143
+ | `historyApiRef` | `React.MutableRefObject<HistoryApi<Row>>` | - | Ref to access undo/redo API externally |
144
+
145
+ `HistoryApi<Row>` exposes `undo()`, `redo()`, `canUndo`, `canRedo`, and `pushHistory(before, after)`.
146
+
147
+ Keyboard shortcuts: `Ctrl+Z` undo, `Ctrl+Y` / `Ctrl+Shift+Z` redo.
148
+
149
+ ### Editing Lifecycle
150
+
151
+ | Prop | Type | Description |
152
+ |------|------|-------------|
153
+ | `onEditStart` | `(row, col) => boolean \| void` | Fires when editing begins. Return `false` to cancel. |
154
+ | `onEditCommit` | `(row, col, before, after) => boolean \| void` | Fires on edit confirm. Return `false` to prevent write. |
155
+ | `onEditCancel` | `(row, col) => void` | Fires when editing is cancelled (Escape). |
156
+
157
+ ## Features
158
+
159
+ ### Selection
160
+
161
+ - Click to select, Shift+Click to extend range, drag for rectangle selection
162
+ - Arrow keys to navigate, Tab to move right (wraps), Shift+Tab to move left
163
+ - `Ctrl+A` to select all, `Escape` to clear selection
164
+ - Grip headers: click to select entire row/column, drag to extend
165
+
166
+ ### Editing (edit mode)
167
+
168
+ - Re-tap active cell, press Enter, F2, or type a character to begin editing
169
+ - Enter commits and moves down, Tab commits and moves right, Escape cancels
170
+ - Double-click also starts editing in any writable mode
171
+
172
+ ### Clipboard
173
+
174
+ - Copy/cut/paste with Excel-compatible TSV + HTML table format
175
+ - Paste auto-extends rows when `allowRowAdd` is enabled
176
+ - Insert paste (`Ctrl+V`) inserts new rows at selection position
177
+ - Overwrite paste (`Ctrl+Shift+V`) overwrites existing cells
178
+ - `Ctrl+I` insert rows, `Ctrl+D` delete selected rows
179
+ - Right-click context menu for all clipboard and row operations
180
+ - Undo/redo (`Ctrl+Z` / `Ctrl+Y`) tracks all cell edits, paste, and row operations
181
+
182
+ ### Touch Support
183
+
184
+ - Pointer Events API for unified mouse/touch/pen handling
185
+ - `setPointerCapture` for reliable touch drag selection
186
+ - Long-press opens context menu without losing selection (deferred selection pattern)
187
+
188
+ ### Layout
189
+
190
+ - Sticky grip headers (row headers stick left, column headers stick top)
191
+ - Custom overlay scrollbars (no layout shift from native scrollbar appearance)
192
+ - Works in fixed-size containers, percentage-based layouts, and unconstrained layouts
193
+ - Flex-based scroll structure handles both constrained and auto-sized parents
194
+
195
+ ## Development
196
+
197
+ ```bash
198
+ pnpm install
199
+ pnpm dev # Storybook dev server
200
+ pnpm test # Vitest
201
+ pnpm typecheck # TypeScript
202
+ pnpm lint # ESLint
203
+ pnpm build # Vite library build
204
+ ```
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,3 @@
1
+ import type { SimpleExGridProps } from './types';
2
+ import './styles.css';
3
+ export declare function SimpleExGrid<Row>(props: SimpleExGridProps<Row>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom/vitest';
@@ -0,0 +1,9 @@
1
+ interface CellEditorProps {
2
+ value: string;
3
+ onChange: (value: string) => void;
4
+ onCommit: () => void;
5
+ onCancel: () => void;
6
+ onKeyDown: (e: React.KeyboardEvent) => void;
7
+ }
8
+ export declare function CellEditor({ value, onChange, onCommit, onKeyDown }: CellEditorProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,17 @@
1
+ export type ContextMenuItem = {
2
+ type?: 'item';
3
+ label: string;
4
+ shortcut?: string;
5
+ disabled?: boolean;
6
+ onClick: () => void;
7
+ } | {
8
+ type: 'separator';
9
+ };
10
+ interface ContextMenuProps {
11
+ x: number;
12
+ y: number;
13
+ items: ContextMenuItem[];
14
+ onClose: () => void;
15
+ }
16
+ export declare function ContextMenu({ x, y, items, onClose }: ContextMenuProps): import("react/jsx-runtime").JSX.Element;
17
+ export {};
@@ -0,0 +1,29 @@
1
+ import type { GridMode, ColumnDef, CellChange } from '../types';
2
+ import type { SelectionState } from './useSelection';
3
+ import type { ColSpanGetter } from '../utils/colspan';
4
+ interface UseClipboardOptions<Row> {
5
+ rows: Row[];
6
+ columns: ColumnDef<Row>[];
7
+ mode: GridMode;
8
+ selection: SelectionState;
9
+ onChange?: (next: Row[], changes: CellChange[]) => void;
10
+ clipboardFormat: 'tsv' | 'html' | 'both';
11
+ allowRowAdd: boolean;
12
+ allowRowDelete: boolean;
13
+ onRowsAdd?: (startRow: number, count: number) => void;
14
+ onRowsDelete?: (startRow: number, count: number) => void;
15
+ colSpan?: ColSpanGetter;
16
+ }
17
+ export interface ClipboardActions {
18
+ copy: () => Promise<void>;
19
+ cut: () => Promise<void>;
20
+ paste: () => Promise<void>;
21
+ pasteInsert: () => Promise<void>;
22
+ addRows: () => void;
23
+ deleteRows: () => void;
24
+ hasSelection: boolean;
25
+ allowRowAdd: boolean;
26
+ allowRowDelete: boolean;
27
+ }
28
+ export declare function useClipboard<Row>(options: UseClipboardOptions<Row>): ClipboardActions;
29
+ export {};
@@ -0,0 +1,30 @@
1
+ import type { GridMode, ColumnDef, CellChange } from '../types';
2
+ interface UseEditingOptions<Row> {
3
+ rows: Row[];
4
+ columns: ColumnDef<Row>[];
5
+ mode: GridMode;
6
+ activeCell: {
7
+ row: number;
8
+ col: number;
9
+ } | null;
10
+ onChange?: (next: Row[], changes: CellChange[]) => void;
11
+ onCommitMove: (dRow: number, dCol: number) => void;
12
+ onEditStart?: (row: number, col: number) => boolean | void;
13
+ onEditCommit?: (row: number, col: number, before: unknown, after: unknown) => boolean | void;
14
+ onEditCancel?: (row: number, col: number) => void;
15
+ }
16
+ export interface EditingState {
17
+ editingCell: {
18
+ row: number;
19
+ col: number;
20
+ } | null;
21
+ editValue: string;
22
+ setEditValue: (value: string) => void;
23
+ startEditing: (row: number, col: number, initialChar?: string) => void;
24
+ commitEdit: () => void;
25
+ cancelEdit: () => void;
26
+ isEditing: (row: number, col: number) => boolean;
27
+ onEditorKeyDown: (e: React.KeyboardEvent) => void;
28
+ }
29
+ export declare function useEditing<Row>(options: UseEditingOptions<Row>): EditingState;
30
+ export {};
@@ -0,0 +1,15 @@
1
+ import type { SelectionState } from './useSelection';
2
+ interface UseGripSelectionOptions {
3
+ selection: SelectionState;
4
+ rowCount: number;
5
+ colCount: number;
6
+ }
7
+ export declare function useGripSelection(options: UseGripSelectionOptions): {
8
+ onRowGripPointerDown: (e: React.PointerEvent) => void;
9
+ onRowGripPointerMove: (e: React.PointerEvent) => void;
10
+ onRowGripPointerUp: (e: React.PointerEvent) => void;
11
+ onColGripPointerDown: (e: React.PointerEvent) => void;
12
+ onColGripPointerMove: (e: React.PointerEvent) => void;
13
+ onColGripPointerUp: (e: React.PointerEvent) => void;
14
+ };
15
+ export {};
@@ -0,0 +1,27 @@
1
+ import type { CellChange, HistoryApi } from '../types';
2
+ import type { SelectionState } from './useSelection';
3
+ interface UseHistoryOptions<Row> {
4
+ rows: Row[];
5
+ selection: SelectionState;
6
+ onChange?: (next: Row[], changes: CellChange[]) => void;
7
+ onRowsAdd?: (startRow: number, count: number) => void;
8
+ onRowsDelete?: (startRow: number, count: number) => void;
9
+ maxHistory: number;
10
+ shouldRecordHistory?: (changes: CellChange[], rowEvent?: {
11
+ kind: 'add' | 'delete';
12
+ startRow: number;
13
+ count: number;
14
+ }) => boolean;
15
+ historyApiRef?: React.MutableRefObject<HistoryApi<Row> | null>;
16
+ }
17
+ export interface HistoryActions<Row> {
18
+ wrappedOnChange: (next: Row[], changes: CellChange[]) => void;
19
+ wrappedOnRowsAdd: (startRow: number, count: number) => void;
20
+ wrappedOnRowsDelete: (startRow: number, count: number) => void;
21
+ undo: () => void;
22
+ redo: () => void;
23
+ canUndo: boolean;
24
+ canRedo: boolean;
25
+ }
26
+ export declare function useHistory<Row>(options: UseHistoryOptions<Row>): HistoryActions<Row>;
27
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { GridMode } from '../types';
2
+ import type { SelectionState } from './useSelection';
3
+ import type { EditingState } from './useEditing';
4
+ interface UseKeyboardNavigationOptions {
5
+ selection: SelectionState;
6
+ editing: EditingState;
7
+ mode: GridMode;
8
+ rowCount: number;
9
+ colCount: number;
10
+ }
11
+ export declare function useKeyboardNavigation(options: UseKeyboardNavigationOptions): {
12
+ onKeyDown: (e: React.KeyboardEvent) => void;
13
+ };
14
+ export {};
@@ -0,0 +1,18 @@
1
+ interface ScrollbarState {
2
+ vTop: number;
3
+ vHeight: number;
4
+ showV: boolean;
5
+ hLeft: number;
6
+ hWidth: number;
7
+ showH: boolean;
8
+ }
9
+ export declare function useOverlayScrollbar(): {
10
+ scrollRef: import("react").RefObject<HTMLDivElement | null>;
11
+ areaRef: import("react").RefObject<HTMLDivElement | null>;
12
+ state: ScrollbarState;
13
+ onVBarPointerDown: (e: React.PointerEvent) => void;
14
+ onHBarPointerDown: (e: React.PointerEvent) => void;
15
+ onBarPointerMove: (e: React.PointerEvent) => void;
16
+ onBarPointerUp: (e: React.PointerEvent) => void;
17
+ };
18
+ export {};
@@ -0,0 +1,22 @@
1
+ interface UsePaginationOptions {
2
+ totalRows: number;
3
+ pageSize: number;
4
+ page?: number;
5
+ onPageChange?: (page: number) => void;
6
+ onPageSizeChange?: (pageSize: number) => void;
7
+ }
8
+ export interface PaginationState {
9
+ currentPage: number;
10
+ pageSize: number;
11
+ totalPages: number;
12
+ startRow: number;
13
+ endRow: number;
14
+ setPage: (page: number) => void;
15
+ setPageSize: (size: number) => void;
16
+ goFirst: () => void;
17
+ goPrev: () => void;
18
+ goNext: () => void;
19
+ goLast: () => void;
20
+ }
21
+ export declare function usePagination(options: UsePaginationOptions): PaginationState;
22
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { GridMode } from '../types';
2
+ import type { SelectionState } from './useSelection';
3
+ import type { EditingState } from './useEditing';
4
+ interface UsePointerSelectionOptions {
5
+ mode: GridMode;
6
+ selection: SelectionState;
7
+ editing: EditingState;
8
+ }
9
+ export declare function usePointerSelection(options: UsePointerSelectionOptions): {
10
+ onPointerDown: (e: React.PointerEvent) => void;
11
+ onPointerMove: (e: React.PointerEvent) => void;
12
+ onPointerUp: (e: React.PointerEvent) => void;
13
+ };
14
+ export {};
@@ -0,0 +1,37 @@
1
+ import type { CellRange } from '../types';
2
+ import type { ColSpanGetter } from '../utils/colspan';
3
+ interface UseSelectionOptions {
4
+ rowCount: number;
5
+ colCount: number;
6
+ selection?: CellRange | null;
7
+ onSelectionChange?: (range: CellRange | null) => void;
8
+ onActiveCellChange?: (row: number, col: number) => void;
9
+ colSpan?: ColSpanGetter;
10
+ hiddenColumns?: Set<number>;
11
+ }
12
+ export interface SelectionState {
13
+ activeCell: {
14
+ row: number;
15
+ col: number;
16
+ } | null;
17
+ selectionRange: CellRange | null;
18
+ selectCell: (row: number, col: number) => void;
19
+ extendSelection: (row: number, col: number) => void;
20
+ setRangeEnd: (row: number, col: number) => void;
21
+ selectAll: () => void;
22
+ clearSelection: () => void;
23
+ moveActiveCell: (dRow: number, dCol: number) => void;
24
+ moveActiveCellTo: (row: number, col: number) => void;
25
+ isSelected: (row: number, col: number) => boolean;
26
+ isActiveCell: (row: number, col: number) => boolean;
27
+ selectEntireRow: (row: number) => void;
28
+ selectEntireCol: (col: number) => void;
29
+ extendRowSelection: (row: number) => void;
30
+ extendColSelection: (col: number) => void;
31
+ restoreSelection: (range: CellRange | null, activeCell: {
32
+ row: number;
33
+ col: number;
34
+ } | null) => void;
35
+ }
36
+ export declare function useSelection(options: UseSelectionOptions): SelectionState;
37
+ export {};
package/dist/index.cjs ADDED
@@ -0,0 +1,5 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`react`),t=require(`react/jsx-runtime`);function n(e,t,n,r){let i=0;for(;i<n&&i<=t;){let a=Math.max(1,Math.min(r(e,i),n-i));if(t<i+a)return i;i+=a}return t}function r(e,t,r,i){let a=n(e,t,r,i);return a+Math.max(1,Math.min(i(e,a),r-a))}function i(e,t,r,i){let a=n(e,t,r,i);return a===0?-1:n(e,a-1,r,i)}function a(e,t,r){return n(e,t-1,t,r)}function o(t){let{rowCount:o,colCount:s,selection:c,onSelectionChange:l,onActiveCellChange:u,colSpan:d,hiddenColumns:f}=t,[p,m]=(0,e.useState)(null),[h,g]=(0,e.useState)(null),[_,v]=(0,e.useState)(null),y=(0,e.useRef)(null),b=(0,e.useCallback)(e=>{let t=y.current;(t?.row!==e.row||t?.col!==e.col)&&u?.(e.row,e.col),y.current=e,m(e)},[u]),x=c!==void 0,S=x?c??null:h,C=p,w=(0,e.useCallback)(e=>{x?l?.(e):g(e)},[x,l]),T=(0,e.useCallback)((e,t)=>({row:Math.max(0,Math.min(e,o-1)),col:Math.max(0,Math.min(t,s-1))}),[o,s]),E=(0,e.useCallback)((e,t)=>{if(!f||f.size===0)return e;let n=e;for(;n>=0&&n<s&&f.has(n);)n+=t;return n},[f,s]),D=(0,e.useCallback)(()=>{if(!f||f.size===0)return 0;for(let e=0;e<s;e++)if(!f.has(e))return e;return 0},[f,s]),O=(0,e.useCallback)(()=>{if(!f||f.size===0)return s-1;for(let e=s-1;e>=0;e--)if(!f.has(e))return e;return s-1},[f,s]),k=(0,e.useCallback)((e,t)=>{let n=T(e,t);f?.has(n.col)&&(n.col=E(n.col,1),n.col>=s&&(n.col=E(t,-1)),n.col<0)||(b(n),v(n),w({start:n,end:n}))},[T,w,b,f,E,s]),A=(0,e.useCallback)((e,t)=>{let n=T(e,t),r=_??p??n;b(n),w({start:r,end:n})},[_,p,T,w,b]),j=(0,e.useCallback)((e,t)=>{let n=T(e,t);w({start:_??p??n,end:n})},[_,p,T,w]),M=(0,e.useCallback)(()=>{o===0||s===0||(b({row:0,col:0}),v({row:0,col:0}),w({start:{row:0,col:0},end:{row:o-1,col:s-1}}))},[o,s,w,b]),N=(0,e.useCallback)(()=>{y.current=null,m(null),v(null),w(null)},[w]),P=(0,e.useCallback)((e,t)=>{let c=p??{row:0,col:0},l=c.row+e,u=c.col+t;if(d){if(t>0&&e===0)u=r(c.row,c.col,s,d);else if(t<0&&e===0){let e=i(c.row,c.col,s,d);u=e>=0?e:c.col}u>=s?(u=0,l=c.row+1):u<0&&(l=c.row-1,u=l>=0?a(l,s,d):s-1),e!==0&&(u=n(Math.max(0,Math.min(l,o-1)),Math.max(0,Math.min(u,s-1)),s,d))}else f&&f.size>0&&t!==0&&e===0&&(u=E(u,t>0?1:-1)),u>=s?(u=D(),l+=1):u<0&&(u=O(),--l),f&&f.size>0&&e!==0&&(u=E(u,1),u>=s&&(u=O()));let m=T(l,u);b(m),v(m),w({start:m,end:m})},[p,s,o,T,w,b,d,f,E,D,O]),F=(0,e.useCallback)((e,t)=>{let r=T(e,t);d&&(r.col=n(r.row,r.col,s,d)),!(f?.has(r.col)&&(r.col=E(r.col,1),r.col>=s&&(r.col=E(t,-1)),r.col<0))&&(b(r),v(r),w({start:r,end:r}))},[T,s,w,b,d,f,E]),I=(0,e.useCallback)(e=>{let t=Math.max(0,Math.min(e,o-1));y.current||b({row:t,col:0}),v({row:t,col:0}),w({start:{row:t,col:0},end:{row:t,col:s-1}})},[o,s,w,b]),L=(0,e.useCallback)(e=>{let t=Math.max(0,Math.min(e,o-1));w({start:{row:(_??{row:t,col:0}).row,col:0},end:{row:t,col:s-1}})},[_,o,s,w]),R=(0,e.useCallback)(e=>{let t=Math.max(0,Math.min(e,s-1));y.current||b({row:0,col:t}),v({row:0,col:t}),w({start:{row:0,col:t},end:{row:o-1,col:t}})},[o,s,w,b]),z=(0,e.useCallback)(e=>{let t=Math.max(0,Math.min(e,s-1));w({start:{row:0,col:(_??{row:0,col:t}).col},end:{row:o-1,col:t}})},[_,o,s,w]),B=(0,e.useCallback)((e,t)=>{if(!S)return!1;let n=Math.min(S.start.row,S.end.row),r=Math.max(S.start.row,S.end.row),i=Math.min(S.start.col,S.end.col),a=Math.max(S.start.col,S.end.col);return e>=n&&e<=r&&t>=i&&t<=a},[S]),V=(0,e.useCallback)((e,t)=>C?.row===e&&C?.col===t,[C]),H=(0,e.useCallback)((e,t)=>{t?(y.current=t,m(t),v(t)):(y.current=null,m(null),v(null)),w(e)},[w]);return(0,e.useMemo)(()=>({activeCell:C,selectionRange:S,selectCell:k,extendSelection:A,setRangeEnd:j,selectAll:M,clearSelection:N,moveActiveCell:P,moveActiveCellTo:F,isSelected:B,isActiveCell:V,selectEntireRow:I,selectEntireCol:R,extendRowSelection:L,extendColSelection:z,restoreSelection:H}),[C,S,k,A,j,M,N,P,F,B,V,I,R,L,z,H])}function s(e){let t=e.target.closest(`[data-row][data-col]`);if(!t)return null;let n=parseInt(t.dataset.row,10),r=parseInt(t.dataset.col,10);return isNaN(n)||isNaN(r)?null:{row:n,col:r}}function c(e,t){let n=document.elementFromPoint(e,t)?.closest(`[data-row][data-col]`);if(!n)return null;let r=parseInt(n.dataset.row,10),i=parseInt(n.dataset.col,10);return isNaN(r)||isNaN(i)?null:{row:r,col:i}}function l(t){let{mode:n,selection:r,editing:i}=t,a=(0,e.useRef)(!1),o=(0,e.useRef)(!1),l=(0,e.useRef)(null),u=(0,e.useRef)(null),d=(0,e.useRef)(null),f=(0,e.useRef)(null),p=(0,e.useCallback)((e,t)=>{let n=e.closest(`.seg-scroll-inner`);n&&(n.setPointerCapture?.(t),l.current=n)},[]),m=(0,e.useCallback)(e=>{let t=e.target;if(t.closest(`[data-grip-row]`)||t.closest(`[data-grip-col]`))return;let c=s(e);if(c&&(u.current=null,o.current=!1,!(e.button===2&&r.isSelected(c.row,c.col)))){if(n===`edit`){f.current=null;let n=d.current;if(n!==null&&n.row===c.row&&n.col===c.col&&!i.editingCell){f.current=c;return}i.editingCell&&i.commitEdit(),r.selectCell(c.row,c.col),d.current=c,a.current=!0,p(t,e.pointerId);return}if(i.editingCell&&i.commitEdit(),e.shiftKey){r.extendSelection(c.row,c.col);return}if(r.isSelected(c.row,c.col)){u.current=c,a.current=!0,p(t,e.pointerId);return}r.selectCell(c.row,c.col),d.current=c,a.current=!0,p(t,e.pointerId)}},[n,r,i,p]),h=(0,e.useCallback)(e=>{if(!a.current)return;let t=c(e.clientX,e.clientY);t&&(u.current&&=(r.selectCell(u.current.row,u.current.col),null),o.current=!0,r.setRangeEnd(t.row,t.col))},[r]),g=(0,e.useCallback)(e=>{if(f.current){let e=f.current;f.current=null,i.startEditing(e.row,e.col);return}u.current&&!o.current&&(r.selectCell(u.current.row,u.current.col),d.current=u.current,u.current=null),u.current=null,a.current&&(a.current=!1,o.current=!1,l.current&&=(l.current.releasePointerCapture?.(e.pointerId),null))},[r,i]);return(0,e.useMemo)(()=>({onPointerDown:m,onPointerMove:h,onPointerUp:g}),[m,h,g])}function u(t){let{selection:n,colCount:r,rowCount:i}=t,a=(0,e.useRef)(!1),o=(0,e.useRef)(!1),s=(0,e.useRef)(!1),c=(0,e.useRef)(!1),l=(0,e.useRef)(null),u=(0,e.useRef)(null),d=(0,e.useCallback)(e=>{let t=n.selectionRange;if(!t)return!1;let i=Math.min(t.start.row,t.end.row),a=Math.max(t.start.row,t.end.row),o=Math.min(t.start.col,t.end.col),s=Math.max(t.start.col,t.end.col);return e>=i&&e<=a&&o===0&&s===r-1},[n.selectionRange,r]),f=(0,e.useCallback)(e=>{let t=n.selectionRange;if(!t)return!1;let r=Math.min(t.start.row,t.end.row),a=Math.max(t.start.row,t.end.row),o=Math.min(t.start.col,t.end.col),s=Math.max(t.start.col,t.end.col);return e>=o&&e<=s&&r===0&&a===i-1},[n.selectionRange,i]),p=(0,e.useCallback)(e=>{let t=e.currentTarget,r=parseInt(t.dataset.gripRow,10);isNaN(r)||e.button===2&&d(r)||(t.setPointerCapture?.(e.pointerId),a.current=!0,s.current=!1,l.current=null,e.shiftKey?n.extendRowSelection(r):d(r)?l.current=r:n.selectEntireRow(r))},[n,d]),m=(0,e.useCallback)(e=>{if(!a.current)return;let t=document.elementFromPoint(e.clientX,e.clientY)?.closest(`[data-grip-row]`);if(!t)return;let r=parseInt(t.dataset.gripRow,10);isNaN(r)||(l.current!==null&&(n.selectEntireRow(l.current),l.current=null),s.current=!0,n.extendRowSelection(r))},[n]),h=(0,e.useCallback)(e=>{l.current!==null&&!s.current&&n.selectEntireRow(l.current),l.current=null,a.current=!1,s.current=!1,e.currentTarget.releasePointerCapture?.(e.pointerId)},[n]),g=(0,e.useCallback)(e=>{let t=e.currentTarget,r=parseInt(t.dataset.gripCol,10);isNaN(r)||e.button===2&&f(r)||(t.setPointerCapture?.(e.pointerId),o.current=!0,c.current=!1,u.current=null,e.shiftKey?n.extendColSelection(r):f(r)?u.current=r:n.selectEntireCol(r))},[n,f]),_=(0,e.useCallback)(e=>{if(!o.current)return;let t=document.elementFromPoint(e.clientX,e.clientY)?.closest(`[data-grip-col]`);if(!t)return;let r=parseInt(t.dataset.gripCol,10);isNaN(r)||(u.current!==null&&(n.selectEntireCol(u.current),u.current=null),c.current=!0,n.extendColSelection(r))},[n]),v=(0,e.useCallback)(e=>{u.current!==null&&!c.current&&n.selectEntireCol(u.current),u.current=null,o.current=!1,c.current=!1,e.currentTarget.releasePointerCapture?.(e.pointerId)},[n]);return(0,e.useMemo)(()=>({onRowGripPointerDown:p,onRowGripPointerMove:m,onRowGripPointerUp:h,onColGripPointerDown:g,onColGripPointerMove:_,onColGripPointerUp:v}),[p,m,h,g,_,v])}function d(t){let{selection:n,editing:r,mode:i,rowCount:a,colCount:o}=t,s=(0,e.useCallback)(e=>{if(r.editingCell)return;let{key:t,shiftKey:s,ctrlKey:c,metaKey:l}=e,u=c||l;switch(t){case`ArrowUp`:e.preventDefault(),s?n.extendSelection((n.activeCell?.row??0)-1,n.activeCell?.col??0):n.moveActiveCell(-1,0);break;case`ArrowDown`:e.preventDefault(),s?n.extendSelection((n.activeCell?.row??0)+1,n.activeCell?.col??0):n.moveActiveCell(1,0);break;case`ArrowLeft`:e.preventDefault(),s?n.extendSelection(n.activeCell?.row??0,(n.activeCell?.col??0)-1):n.moveActiveCell(0,-1);break;case`ArrowRight`:e.preventDefault(),s?n.extendSelection(n.activeCell?.row??0,(n.activeCell?.col??0)+1):n.moveActiveCell(0,1);break;case`Tab`:e.preventDefault(),n.moveActiveCell(0,s?-1:1);break;case`Enter`:e.preventDefault(),i===`edit`&&n.activeCell?r.startEditing(n.activeCell.row,n.activeCell.col):n.moveActiveCell(1,0);break;case`F2`:e.preventDefault(),i===`edit`&&n.activeCell&&r.startEditing(n.activeCell.row,n.activeCell.col);break;case`Escape`:e.preventDefault(),n.clearSelection();break;case`Home`:e.preventDefault(),u?n.moveActiveCellTo(0,0):n.moveActiveCellTo(n.activeCell?.row??0,0);break;case`End`:e.preventDefault(),u?n.moveActiveCellTo(a-1,o-1):n.moveActiveCellTo(n.activeCell?.row??0,o-1);break;case`a`:u&&(e.preventDefault(),n.selectAll());break;default:i===`edit`&&n.activeCell&&t.length===1&&!u&&r.startEditing(n.activeCell.row,n.activeCell.col,t);break}},[n,r,i,a,o]);return(0,e.useMemo)(()=>({onKeyDown:s}),[s])}function f(t){let{rows:n,columns:r,mode:i,onChange:a,onCommitMove:o,onEditStart:s,onEditCommit:c,onEditCancel:l}=t,[u,d]=(0,e.useState)(null),[f,p]=(0,e.useState)(``),[m,h]=(0,e.useState)(!1),g=(0,e.useCallback)((e,t,a)=>{if(i===`readonly`||i===`select`)return;let o=r[t];if(o?.editable===!1||s?.(e,t)===!1)return;let c=n[e]?.[o.key],l=a??String(c??``);d({row:e,col:t}),p(l)},[i,r,n,s]),_=(0,e.useCallback)(()=>{if(!u)return;let{row:e,col:t}=u,i=r[t];if(!i){d(null);return}let o=i.parser?i.parser(f):f,s=n[e]?.[i.key];if(c?.(e,t,s,o)===!1){d(null);return}if(s!==o&&a){let r={...n[e],[i.key]:o},c=[...n];c[e]=r,a(c,[{row:e,col:t,before:s,after:o}])}d(null)},[u,r,f,n,a,c]),v=(0,e.useCallback)(()=>{u&&l?.(u.row,u.col),d(null)},[u,l]),y=(0,e.useCallback)((e,t)=>u?.row===e&&u?.col===t,[u]),b=(0,e.useCallback)(e=>{if(e.nativeEvent.type===`compositionstart`){h(!0);return}if(e.nativeEvent.type===`compositionend`){h(!1);return}if(!m)switch(e.key){case`Enter`:e.preventDefault(),e.stopPropagation(),_(),o(1,0);break;case`Tab`:e.preventDefault(),e.stopPropagation(),_(),o(0,e.shiftKey?-1:1);break;case`Escape`:e.preventDefault(),e.stopPropagation(),v();break}},[m,_,v,o]);return(0,e.useMemo)(()=>({editingCell:u,editValue:f,setEditValue:p,startEditing:g,commitEdit:_,cancelEdit:v,isEditing:y,onEditorKeyDown:b}),[u,f,g,_,v,y,b])}function p(e){return e.includes(` `)||e.includes(`
2
+ `)||e.includes(`\r`)||e.includes(`"`)?`"${e.replace(/"/g,`""`)}"`:e}function m(e){return e.map(e=>e.map(p).join(` `)).join(`\r
3
+ `)}function h(e){let t=[],n=``,r=!1,i=[];for(let a=0;a<e.length;a++){let o=e[a];r?o===`"`?e[a+1]===`"`?(n+=`"`,a++):r=!1:n+=o:o===`"`?r=!0:o===` `?(i.push(n),n=``):o===`\r`?(e[a+1]===`
4
+ `&&a++,i.push(n),n=``,t.push(i),i=[]):o===`
5
+ `?(i.push(n),n=``,t.push(i),i=[]):n+=o}return(n||i.length>0)&&(i.push(n),t.push(i)),t}function g(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}function _(e,t){let n=t?.colSpan,r=t?.startRow??0,i=t?.startCol??0;return`<table>${e.map((e,t)=>{let a=r+t,o=``,s=0;for(;s<e.length;){let t=i+s,r=n?Math.max(1,Math.min(n(a,t),e.length-s)):1,c=r>1?` colspan="${r}"`:``;o+=`<td${c}>${g(e[s])}</td>`,s+=r}return`<tr>${o}</tr>`}).join(``)}</table>`}function v(e){let t=e.selectionRange;return t?{minRow:Math.min(t.start.row,t.end.row),maxRow:Math.max(t.start.row,t.end.row),minCol:Math.min(t.start.col,t.end.col),maxCol:Math.max(t.start.col,t.end.col)}:null}function y(t){let{rows:n,columns:r,mode:i,selection:a,onChange:o,clipboardFormat:s,allowRowAdd:c,allowRowDelete:l,onRowsAdd:u,onRowsDelete:d,colSpan:f}=t,p=(0,e.useCallback)(()=>{let e=v(a);if(!e)return null;let t=[];for(let i=e.minRow;i<=e.maxRow;i++){let a=[];for(let t=e.minCol;t<=e.maxCol;t++){let e=r[t],o=n[i]?.[e.key];a.push(String(o??``))}t.push(a)}return t},[n,r,a]),g=(0,e.useCallback)(async(e,t,n)=>{let r=m(e),i={};if((s===`tsv`||s===`both`)&&(i[`text/plain`]=new Blob([r],{type:`text/plain`})),s===`html`||s===`both`){let a=_(e,{colSpan:f,startRow:t,startCol:n});i[`text/html`]=new Blob([a],{type:`text/html`}),i[`text/plain`]||=new Blob([r],{type:`text/plain`})}await navigator.clipboard.write([new ClipboardItem(i)])},[s,f]),y=(0,e.useCallback)(async()=>{let e=p();if(!e)return;let t=v(a);await g(e,t?.minRow,t?.minCol)},[p,g,a]),b=(0,e.useCallback)(async()=>{if(i===`readonly`)return;let e=v(a);if(!e)return;let t=p();if(t)if(await g(t,e.minRow,e.minCol),l){let t=e.minRow,i=e.maxRow,s=i-t+1,c=[...n];c.splice(t,s);let l=[];for(let e=t;e<=i;e++)for(let t=0;t<r.length;t++)l.push({row:e,col:t,before:n[e][r[t].key],after:void 0});if(o?.(c,l),d?.(t,s),c.length===0)a.clearSelection();else{let n=Math.min(t,c.length-1);a.selectCell(n,e.minCol)}}else{let t=[...n],i=[];for(let n=e.minRow;n<=e.maxRow;n++){t[n]={...t[n]};for(let a=e.minCol;a<=e.maxCol;a++){let e=r[a];if(e.editable===!1)continue;let o=t[n][e.key],s=e.parser?e.parser(``):``;t[n][e.key]=s,i.push({row:n,col:a,before:o,after:s})}}i.length>0&&o?.(t,i)}},[i,n,r,a,p,g,o,l,d]),x=(0,e.useCallback)(async()=>{if(i===`readonly`)return;let e=v(a)??{minRow:0,maxRow:0,minCol:0,maxCol:0},t;try{t=await navigator.clipboard.readText()}catch{return}let s=h(t);if(s.length===0)return;let l=[...n],d=[],f=e.minRow,p=e.minCol;for(let e=0;e<s.length;e++){let t=f+e;if(t>=l.length){if(!c)break;let e={};for(let t of r)e[t.key]=t.parser?t.parser(``):``;l.push(e)}l[t]={...l[t]};for(let n=0;n<s[e].length;n++){let i=p+n;if(i>=r.length)break;let a=r[i];if(a.editable===!1)continue;let o=l[t][a.key],c=a.parser?a.parser(s[e][n]):s[e][n];l[t][a.key]=c,d.push({row:t,col:i,before:o,after:c})}}d.length>0&&o?.(l,d);let m=l.length-n.length;m>0&&u?.(n.length,m)},[i,n,r,a,o,c,u]),S=(0,e.useCallback)(async()=>{if(i===`readonly`||!c)return;let e=v(a)??{minRow:0,maxRow:-1,minCol:0,maxCol:0},t;try{t=await navigator.clipboard.readText()}catch{return}let s=h(t);if(s.length===0)return;let l=e.minCol,d=e.maxRow+1,f=n[e.minRow]??{},p=s.map(e=>{let t={...f};for(let n=0;n<e.length;n++){let i=l+n;if(i>=r.length)break;let a=r[i];a.editable!==!1&&(t[a.key]=a.parser?a.parser(e[n]):e[n])}return t}),m=[...n];m.splice(d,0,...p);let g=[];for(let e=0;e<p.length;e++)for(let t=0;t<(s[e]?.length??0);t++){let n=l+t;if(n>=r.length)break;let i=r[n];i.editable!==!1&&g.push({row:d+e,col:n,before:void 0,after:p[e][i.key]})}o&&o(m,g),u?.(d,p.length)},[i,c,n,r,a,o,u]),C=(0,e.useCallback)(()=>{if(i===`readonly`||!c)return;let e=v(a)??{minRow:0,maxRow:-1,minCol:0,maxCol:0},t=Math.max(1,e.maxRow-e.minRow+1),s=e.maxRow+1,l=[...n],d=Array.from({length:t},()=>{let e={};for(let t of r)e[t.key]=t.parser?t.parser(``):``;return e});l.splice(s,0,...d),o?.(l,[]),u?.(s,t)},[i,c,n,r,a,o,u]),w=(0,e.useCallback)(()=>{if(i===`readonly`||!l)return;let e=v(a);if(!e)return;let t=e.minRow,s=e.maxRow,c=s-t+1,u=[...n];u.splice(t,c);let f=[];for(let e=t;e<=s;e++)for(let t=0;t<r.length;t++)f.push({row:e,col:t,before:n[e][r[t].key],after:void 0});if(o?.(u,f),d?.(t,c),u.length===0)a.clearSelection();else{let n=Math.min(t,u.length-1);a.selectCell(n,e.minCol)}},[i,l,n,r,a,o,d]),T=a.selectionRange!==null;return(0,e.useMemo)(()=>({copy:y,cut:b,paste:x,pasteInsert:S,addRows:C,deleteRows:w,hasSelection:T,allowRowAdd:c,allowRowDelete:l}),[y,b,x,S,C,w,T,c,l])}function b(t){let{rows:n,selection:r,onChange:i,onRowsAdd:a,onRowsDelete:o,maxHistory:s,shouldRecordHistory:c,historyApiRef:l}=t,[u,d]=(0,e.useState)([]),[f,p]=(0,e.useState)([]),m=(0,e.useRef)(null),h=(0,e.useRef)(!1),g=(0,e.useCallback)(()=>({activeCell:r.activeCell,range:r.selectionRange}),[r]),_=(0,e.useCallback)(e=>{d(t=>{let n=[...t,e];return n.length>s&&n.splice(0,n.length-s),n}),p([])},[s]),v=(0,e.useCallback)((e,t)=>{h.current||(m.current={entry:{beforeRows:n,afterRows:e,beforeSelection:g()},changes:t},queueMicrotask(()=>{let e=m.current;e&&(m.current=null,!(c&&!c(e.changes,e.entry.rowEvent))&&_(e.entry))})),i?.(e,t)},[n,i,g,c,_]),y=(0,e.useCallback)((e,t)=>{!h.current&&m.current&&(m.current.entry.rowEvent={kind:`add`,startRow:e,count:t}),a?.(e,t)},[a]),b=(0,e.useCallback)((e,t)=>{!h.current&&m.current&&(m.current.entry.rowEvent={kind:`delete`,startRow:e,count:t}),o?.(e,t)},[o]),x=(0,e.useCallback)(()=>{d(e=>{if(e.length===0)return e;let t=e[e.length-1],n=e.slice(0,-1);return p(e=>[...e,t]),h.current=!0,i?.(t.beforeRows,[]),t.rowEvent&&(t.rowEvent.kind===`add`?o?.(t.rowEvent.startRow,t.rowEvent.count):a?.(t.rowEvent.startRow,t.rowEvent.count)),r.restoreSelection(t.beforeSelection.range,t.beforeSelection.activeCell),h.current=!1,n})},[i,a,o,r]),S=(0,e.useCallback)(()=>{p(e=>{if(e.length===0)return e;let t=e[e.length-1],n=e.slice(0,-1);return d(e=>{let n=[...e,t];return n.length>s&&n.splice(0,n.length-s),n}),h.current=!0,i?.(t.afterRows,[]),t.rowEvent&&(t.rowEvent.kind===`add`?a?.(t.rowEvent.startRow,t.rowEvent.count):o?.(t.rowEvent.startRow,t.rowEvent.count)),h.current=!1,n})},[s,i,a,o]),C=(0,e.useCallback)((e,t)=>{_({beforeRows:e,afterRows:t,beforeSelection:g()})},[g,_]),w=u.length>0,T=f.length>0;return(0,e.useEffect)(()=>{l&&(l.current={pushHistory:C,undo:x,redo:S,canUndo:w,canRedo:T})},[l,C,x,S,w,T]),(0,e.useMemo)(()=>({wrappedOnChange:v,wrappedOnRowsAdd:y,wrappedOnRowsDelete:b,undo:x,redo:S,canUndo:w,canRedo:T}),[v,y,b,x,S,w,T])}function x(){let t=(0,e.useRef)(null),n=(0,e.useRef)(null),[r,i]=(0,e.useState)({vTop:0,vHeight:0,showV:!1,hLeft:0,hWidth:0,showH:!1}),a=(0,e.useRef)(null),o=(0,e.useRef)(null),s=(0,e.useRef)({pos:0,scroll:0}),c=(0,e.useCallback)(()=>{let e=t.current;if(!e)return;let{scrollTop:r,scrollLeft:o,scrollWidth:s,scrollHeight:c,clientWidth:l,clientHeight:u}=e,d=c>u+1,f=s>l+1,p=u/c,m=l/s;i({showV:d,vTop:d?r/c*u:0,vHeight:d?Math.max(p*u,24):0,showH:f,hLeft:f?o/s*l:0,hWidth:f?Math.max(m*l,24):0});let h=n.current;h&&(h.classList.add(`seg-scrolling`),a.current&&clearTimeout(a.current),a.current=setTimeout(()=>{h.classList.remove(`seg-scrolling`)},800))},[]);return(0,e.useEffect)(()=>{let e=t.current;if(!e)return;e.addEventListener(`scroll`,c,{passive:!0});let n=new ResizeObserver(c);return n.observe(e),e.firstElementChild&&n.observe(e.firstElementChild),c(),()=>{e.removeEventListener(`scroll`,c),n.disconnect()}},[c]),{scrollRef:t,areaRef:n,state:r,onVBarPointerDown:(0,e.useCallback)(e=>{e.preventDefault(),e.stopPropagation(),o.current=`v`,s.current={pos:e.clientY,scroll:t.current?.scrollTop??0},e.target.setPointerCapture?.(e.pointerId)},[]),onHBarPointerDown:(0,e.useCallback)(e=>{e.preventDefault(),e.stopPropagation(),o.current=`h`,s.current={pos:e.clientX,scroll:t.current?.scrollLeft??0},e.target.setPointerCapture?.(e.pointerId)},[]),onBarPointerMove:(0,e.useCallback)(e=>{let n=t.current;if(!(!n||!o.current))if(o.current===`v`){let t=e.clientY-s.current.pos,r=n.scrollHeight/n.clientHeight;n.scrollTop=s.current.scroll+t*r}else{let t=e.clientX-s.current.pos,r=n.scrollWidth/n.clientWidth;n.scrollLeft=s.current.scroll+t*r}},[]),onBarPointerUp:(0,e.useCallback)(e=>{o.current=null,e.target.releasePointerCapture?.(e.pointerId)},[])}}function S(t){let{totalRows:n,pageSize:r,page:i,onPageChange:a,onPageSizeChange:o}=t,[s,c]=(0,e.useState)(0),[l,u]=(0,e.useState)(r),d=i!==void 0,f=d?i:s,p=l,m=Math.max(1,Math.ceil(n/p)),h=(0,e.useCallback)(e=>{let t=Math.max(0,Math.min(e,m-1));d||c(t),a?.(t)},[m,d,a]),g=(0,e.useCallback)(e=>{let t=Math.max(1,e);u(t),o?.(t),h(0)},[o,h]),_=(0,e.useCallback)(()=>h(0),[h]),v=(0,e.useCallback)(()=>h(f-1),[h,f]),y=(0,e.useCallback)(()=>h(f+1),[h,f]),b=(0,e.useCallback)(()=>h(m-1),[h,m]),x=f*p,S=Math.min(x+p,n);return(0,e.useMemo)(()=>({currentPage:f,pageSize:p,totalPages:m,startRow:x,endRow:S,setPage:h,setPageSize:g,goFirst:_,goPrev:v,goNext:y,goLast:b}),[f,p,m,x,S,h,g,_,v,y,b])}function C({value:n,onChange:r,onCommit:i,onKeyDown:a}){let o=(0,e.useRef)(null);return(0,e.useEffect)(()=>{let e=o.current;e&&(e.focus(),e.select())},[]),(0,t.jsx)(`input`,{ref:o,className:`seg-cell-editor`,value:n,onChange:e=>r(e.target.value),onKeyDown:a,onBlur:i,onPointerDown:e=>e.stopPropagation()})}function w({x:n,y:r,items:i,onClose:a}){let o=(0,e.useRef)(null);return(0,e.useEffect)(()=>{let e=e=>{o.current&&!o.current.contains(e.target)&&a()},t=e=>{e.key===`Escape`&&a()};return document.addEventListener(`mousedown`,e),document.addEventListener(`keydown`,t),()=>{document.removeEventListener(`mousedown`,e),document.removeEventListener(`keydown`,t)}},[a]),(0,e.useEffect)(()=>{let e=o.current;if(!e)return;let t=e.getBoundingClientRect();t.right>window.innerWidth&&(e.style.left=`${n-t.width}px`),t.bottom>window.innerHeight&&(e.style.top=`${r-t.height}px`)},[n,r]),(0,t.jsx)(`div`,{ref:o,className:`seg-context-menu`,style:{left:n,top:r},children:i.map((e,n)=>`type`in e&&e.type===`separator`?(0,t.jsx)(`hr`,{className:`seg-context-menu-separator`},n):(0,t.jsxs)(`button`,{className:`seg-context-menu-item`,disabled:`disabled`in e?e.disabled:!1,onClick:()=>{`onClick`in e&&e.onClick(),a()},children:[(0,t.jsx)(`span`,{className:`seg-context-menu-label`,children:`label`in e?e.label:``}),`shortcut`in e&&e.shortcut&&(0,t.jsx)(`span`,{className:`seg-context-menu-shortcut`,children:e.shortcut})]},n))})}function T(n){let{rows:r,columns:i,mode:a=`select`,onChange:s,showRowGripHeader:c=!0,showColumnGripHeader:p=!0,rowHeight:m=`single-line`,allowRowAdd:h=!1,allowRowDelete:g=!1,onEditStart:_,onEditCommit:v,onEditCancel:T,onActiveCellChange:E,onRowsAdd:D,onRowsDelete:O,pagination:k=!1,pageSize:A=20,page:j,onPageChange:M,onPageSizeChange:N,clipboard:P,colSpan:F,cellAlign:I,maxHistory:L=200,shouldRecordHistory:R,historyApiRef:z}=n,B=P?.enabled!==!1,V=P?.format??`both`,H=(0,e.useRef)(null),U=S({totalRows:r.length,pageSize:A,page:j,onPageChange:M,onPageSizeChange:N}),ee=k?r.slice(U.startRow,U.endRow):r,te=k?U.startRow:0,W=r.length,G=i.length,ne=(0,e.useMemo)(()=>{let e=new Set;return i.forEach((t,n)=>{t.hidden&&e.add(n)}),e},[i]),K=o({rowCount:W,colCount:G,selection:n.selection,onSelectionChange:n.onSelectionChange,onActiveCellChange:E,colSpan:F,hiddenColumns:ne}),q=b({rows:r,selection:K,onChange:s,onRowsAdd:D,onRowsDelete:O,maxHistory:L,shouldRecordHistory:R,historyApiRef:z}),J=f({rows:r,columns:i,mode:a,activeCell:K.activeCell,onChange:q.wrappedOnChange,onCommitMove:K.moveActiveCell,onEditStart:_,onEditCommit:v,onEditCancel:T}),Y=y({rows:r,columns:i,mode:a,selection:K,onChange:q.wrappedOnChange,clipboardFormat:V,allowRowAdd:h,allowRowDelete:g,onRowsAdd:q.wrappedOnRowsAdd,onRowsDelete:q.wrappedOnRowsDelete,colSpan:F}),X=l({mode:a,selection:K,editing:J}),Z=u({selection:K,rowCount:W,colCount:G}),re=d({selection:K,editing:J,mode:a,rowCount:W,colCount:G}),{scrollRef:ie,areaRef:ae,state:Q,onVBarPointerDown:oe,onHBarPointerDown:se,onBarPointerMove:ce,onBarPointerUp:le}=x(),[$,ue]=(0,e.useState)(null),de=(0,e.useCallback)(e=>{e.preventDefault();let t=e.target.closest(`[data-row][data-col]`);if(t){let e=parseInt(t.dataset.row,10),n=parseInt(t.dataset.col,10);!isNaN(e)&&!isNaN(n)&&!K.isSelected(e,n)&&K.selectCell(e,n)}ue({x:e.clientX,y:e.clientY})},[K]),fe=(0,e.useCallback)(()=>ue(null),[]),pe=(0,e.useMemo)(()=>{let e=a!==`readonly`,t=Y.hasSelection;return[{label:`元に戻す`,shortcut:`Ctrl+Z`,disabled:!q.canUndo,onClick:()=>q.undo()},{label:`やり直す`,shortcut:`Ctrl+Y`,disabled:!q.canRedo,onClick:()=>q.redo()},{type:`separator`},{label:`コピー`,shortcut:`Ctrl+C`,disabled:!t||!B,onClick:()=>void Y.copy()},{label:`切り取り`,shortcut:`Ctrl+X`,disabled:!t||!e||!B,onClick:()=>void Y.cut()},{label:`貼り付け`,shortcut:`Ctrl+V`,disabled:!e||!B||!h,onClick:()=>void Y.pasteInsert()},{label:`貼り付け(上書き)`,shortcut:`Ctrl+Shift+V`,disabled:!t||!e||!B,onClick:()=>void Y.paste()},{type:`separator`},{label:`行を追加`,shortcut:`Ctrl+I`,disabled:!e||!h,onClick:()=>Y.addRows()},{label:`行を削除`,shortcut:`Ctrl+D`,disabled:!t||!e||!g,onClick:()=>Y.deleteRows()},{type:`separator`},{label:`全選択`,shortcut:`Ctrl+A`,disabled:W===0,onClick:()=>K.selectAll()},{label:`選択解除`,shortcut:`Esc`,disabled:!t,onClick:()=>K.clearSelection()}]},[a,Y,B,h,g,W,K,q]),me=(0,e.useCallback)(e=>{if(!J.editingCell){if(e.ctrlKey||e.metaKey){if(e.key===`z`||e.key===`Z`){e.preventDefault(),e.shiftKey?q.redo():q.undo();return}if(e.key===`y`){e.preventDefault(),q.redo();return}if(B)switch(e.key){case`c`:e.preventDefault(),Y.copy();return;case`x`:e.preventDefault(),Y.cut();return;case`v`:e.preventDefault(),e.shiftKey?Y.paste():Y.pasteInsert();return}if(a!==`readonly`)switch(e.key){case`i`:h&&(e.preventDefault(),Y.addRows());return;case`d`:g&&Y.hasSelection&&(e.preventDefault(),Y.deleteRows());return}}if(e.key===`Escape`){e.preventDefault(),K.clearSelection();return}if((e.key===`Delete`||e.key===`Backspace`)&&a!==`readonly`){e.preventDefault(),Y.cut().catch(()=>{});return}re.onKeyDown(e)}},[J.editingCell,B,Y,a,re,h,g,K,q]),he=(0,e.useCallback)((e,t,n=1)=>{let r=[`seg-cell`],i=!1;for(let r=t;r<t+n;r++)if(K.isSelected(e,r)){i=!0;break}return i&&r.push(`seg-selected`),K.isActiveCell(e,t)&&r.push(`seg-active`),J.isEditing(e,t)&&r.push(`seg-editing`),r.join(` `)},[K,J]),ge=(0,e.useCallback)(e=>{let t=K.selectionRange;if(!t)return!1;let n=Math.min(t.start.row,t.end.row),r=Math.max(t.start.row,t.end.row),i=Math.min(t.start.col,t.end.col),a=Math.max(t.start.col,t.end.col);return e>=n&&e<=r&&i===0&&a===G-1},[K.selectionRange,G]),_e=(0,e.useMemo)(()=>i.map(e=>{let t=e.width;return t===void 0?{width:120}:t===`fit-content`?{width:`auto`}:{width:t}}),[i]),ve=m===`single-line`,ye=i.some(e=>e.width===`fit-content`||e.width===void 0)?void 0:`fixed`;return(0,t.jsxs)(`div`,{ref:H,className:`seg-container${ve?` seg-single-line`:``}`,role:`grid`,"data-mode":a,tabIndex:0,onKeyDown:me,onContextMenu:de,children:[(0,t.jsxs)(`div`,{className:`seg-scroll-area`,ref:ae,children:[(0,t.jsx)(`div`,{className:`seg-scroll-inner`,ref:ie,onPointerDown:X.onPointerDown,onPointerMove:X.onPointerMove,onPointerUp:X.onPointerUp,children:(0,t.jsxs)(`table`,{className:`seg-table`,style:ye?{tableLayout:ye}:void 0,children:[(0,t.jsxs)(`colgroup`,{children:[c&&(0,t.jsx)(`col`,{style:{width:32}}),i.map((e,n)=>e.hidden?null:(0,t.jsx)(`col`,{style:_e[n]},e.key))]}),(0,t.jsxs)(`thead`,{children:[p&&(0,t.jsxs)(`tr`,{className:`seg-grip-col-row`,children:[c&&(0,t.jsx)(`th`,{className:`seg-corner`,onClick:()=>K.clearSelection()}),i.map((e,n)=>e.hidden?null:(0,t.jsx)(`th`,{"data-grip-col":n,onPointerDown:Z.onColGripPointerDown,onPointerMove:Z.onColGripPointerMove,onPointerUp:Z.onColGripPointerUp},e.key))]}),(0,t.jsxs)(`tr`,{className:`seg-header-row`,children:[c&&(0,t.jsx)(`th`,{className:`seg-grip-placeholder`,onClick:()=>K.clearSelection()}),i.map(e=>e.hidden?null:(0,t.jsx)(`th`,{style:e.headerAlign?{textAlign:e.headerAlign}:void 0,children:(0,t.jsxs)(`span`,{className:`seg-header-content`,children:[e.header,e.sortMark===`asc`&&(0,t.jsx)(`span`,{className:`seg-sort-indicator`,children:` ▲`}),e.sortMark===`desc`&&(0,t.jsx)(`span`,{className:`seg-sort-indicator`,children:` ▼`})]})},e.key))]})]}),(0,t.jsx)(`tbody`,{children:ee.map((e,n)=>{let r=n+te;return(0,t.jsxs)(`tr`,{"data-row":r,children:[c&&(0,t.jsx)(`td`,{className:`seg-grip-row${ge(r)?` seg-selected`:``}`,"data-grip-row":r,onPointerDown:Z.onRowGripPointerDown,onPointerMove:Z.onRowGripPointerMove,onPointerUp:Z.onRowGripPointerUp}),(()=>{let n=[],a=0;for(;a<i.length;){let o=F?Math.max(1,Math.min(F(r,a),i.length-a)):1,s=i[a];if(s.hidden){a+=o;continue}let c=0;for(let e=0;e<o;e++)i[a+e]?.hidden||c++;let l=e[s.key],u=s.render?s.render(l,e,r):s.formatter?s.formatter(l,e):String(l??``),d=I?.(r,a)??s.align;n.push((0,t.jsx)(`td`,{className:he(r,a,o),"data-row":r,"data-col":a,colSpan:c>1?c:void 0,style:d?{textAlign:d}:void 0,onDoubleClick:()=>J.startEditing(r,a),children:J.isEditing(r,a)?(0,t.jsx)(C,{value:J.editValue,onChange:J.setEditValue,onCommit:J.commitEdit,onCancel:J.cancelEdit,onKeyDown:J.onEditorKeyDown}):u},s.key)),a+=o}return n})()]},r)})})]})}),Q.showV&&(0,t.jsx)(`div`,{className:`seg-scrollbar seg-scrollbar-v`,style:{top:Q.vTop,height:Q.vHeight},onPointerDown:oe,onPointerMove:ce,onPointerUp:le}),Q.showH&&(0,t.jsx)(`div`,{className:`seg-scrollbar seg-scrollbar-h`,style:{left:Q.hLeft,width:Q.hWidth},onPointerDown:se,onPointerMove:ce,onPointerUp:le})]}),k&&(0,t.jsxs)(`div`,{className:`seg-pagination`,children:[(0,t.jsx)(`div`,{className:`seg-pagination-info`,children:r.length===0?`0 件`:`${U.startRow+1}–${U.endRow} / ${r.length} 件`}),(0,t.jsxs)(`div`,{className:`seg-pagination-controls`,children:[(0,t.jsx)(`button`,{className:`seg-pagination-btn`,disabled:U.currentPage===0,onClick:U.goFirst,children:`«`}),(0,t.jsx)(`button`,{className:`seg-pagination-btn`,disabled:U.currentPage===0,onClick:U.goPrev,children:`‹`}),(0,t.jsxs)(`span`,{className:`seg-pagination-page`,children:[U.currentPage+1,` / `,U.totalPages]}),(0,t.jsx)(`button`,{className:`seg-pagination-btn`,disabled:U.currentPage>=U.totalPages-1,onClick:U.goNext,children:`›`}),(0,t.jsx)(`button`,{className:`seg-pagination-btn`,disabled:U.currentPage>=U.totalPages-1,onClick:U.goLast,children:`»`})]})]}),$&&(0,t.jsx)(w,{x:$.x,y:$.y,items:pe,onClose:fe})]})}exports.SimpleExGrid=T;
package/dist/index.css ADDED
@@ -0,0 +1,2 @@
1
+ .seg-container{color:#222;-webkit-user-select:none;user-select:none;touch-action:none;border:1px solid #b0b0b0;outline:none;flex-direction:column;flex:auto;min-height:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;line-height:1.4;display:flex;position:relative;overflow:hidden}.seg-container:focus-within{border-color:#26c}.seg-scroll-area{flex-direction:column;flex:auto;min-height:0;display:flex;position:relative;overflow:hidden}.seg-scroll-inner{scrollbar-width:none;flex:auto;min-height:0;overflow:auto}.seg-scroll-inner::-webkit-scrollbar{display:none}.seg-scrollbar{z-index:10;opacity:0;pointer-events:auto;background:#00000026;border-radius:4px;transition:opacity .2s;position:absolute}.seg-scrollbar:hover,.seg-scrollbar.seg-scrollbar-active{background:#00000059}.seg-scroll-area:hover .seg-scrollbar,.seg-scroll-area.seg-scrolling .seg-scrollbar{opacity:1}.seg-scrollbar-v{width:6px;right:2px}.seg-scrollbar-h{height:6px;bottom:2px}.seg-table{border-collapse:separate;border-spacing:0}.seg-table thead th{z-index:3;position:sticky;top:0}.seg-grip-col-row th{z-index:3;top:0}.seg-header-row th{z-index:3}.seg-grip-col-row+.seg-header-row th{top:16px}.seg-grip-row,.seg-corner,.seg-grip-placeholder{z-index:2;position:sticky;left:0}.seg-table thead th.seg-corner{z-index:4;top:auto}.seg-table thead th.seg-grip-placeholder{z-index:4}.seg-corner{background:#e8e8e8;border-bottom:1px solid #b0b0b0;border-right:1px solid #b0b0b0;width:32px;min-width:32px;max-width:32px}.seg-grip-col-row th{background:#e8e8e8;border-bottom:1px solid #b0b0b0;border-right:1px solid #d0d0d0;height:16px;max-height:16px;padding:0}.seg-grip-col-row th:after{content:"";background:#217346;width:100%;height:4px;display:block;position:absolute;bottom:0;left:0}.seg-grip-col-row th.seg-corner:after{display:none}.seg-header-row th{text-align:left;white-space:nowrap;text-overflow:ellipsis;background:#f3f3f3;border-bottom:2px solid #b0b0b0;border-right:1px solid #d0d0d0;padding:6px 8px;font-weight:600;overflow:hidden}.seg-header-content{align-items:center;display:inline-flex}.seg-sort-indicator{color:#666;flex-shrink:0;font-size:10px}.seg-header-row th.seg-grip-placeholder{background:#f3f3f3;border-right:1px solid #b0b0b0;width:32px;min-width:32px;max-width:32px;padding:0}.seg-grip-row{cursor:pointer;background:#f3f3f3;border-bottom:1px solid #d0d0d0;border-right:1px solid #b0b0b0;width:32px;min-width:32px;max-width:32px;padding:0}.seg-grip-row:after{content:"";background:#217346;width:4px;height:100%;display:block;position:absolute;top:0;right:0}.seg-cell{cursor:cell;background:#fff;border-bottom:1px solid #d0d0d0;border-right:1px solid #d0d0d0;padding:4px 8px}.seg-single-line .seg-cell{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.seg-container:not(.seg-single-line) .seg-cell{white-space:normal;word-break:break-word}.seg-cell.seg-selected{background:#c6e0f5}.seg-cell.seg-active{outline-offset:-2px;z-index:1;outline:2px solid #26c;position:relative}.seg-grip-row.seg-selected{background:#d5e5d5}.seg-grip-col-row th.seg-selected:after{background:#145a30}.seg-cell.seg-editing{padding:0;overflow:visible}.seg-cell-editor{width:100%;height:100%;font:inherit;color:inherit;box-sizing:border-box;background:#fff;border:none;outline:none;padding:4px 8px}.seg-container[data-mode=readonly] .seg-cell{cursor:default}.seg-table tbody tr:nth-child(2n) .seg-cell:not(.seg-selected):not(.seg-active){background:#fafafa}.seg-context-menu{z-index:1000;background:#fff;border:1px solid #d0d0d0;border-radius:6px;min-width:180px;padding:4px 0;font-size:13px;position:fixed;box-shadow:0 4px 16px #00000026}.seg-context-menu-separator{border:none;border-top:1px solid #e0e0e0;margin:4px 0}.seg-context-menu-item{width:100%;font:inherit;color:#222;cursor:pointer;text-align:left;background:0 0;border:none;justify-content:space-between;align-items:center;padding:6px 14px;display:flex}.seg-context-menu-item:hover:not(:disabled){background:#e8f0fe}.seg-context-menu-item:disabled{color:#aaa;cursor:default}.seg-context-menu-shortcut{color:#888;margin-left:24px;font-size:12px}.seg-pagination{color:#555;background:#f8f8f8;border-top:1px solid #d0d0d0;flex-shrink:0;justify-content:space-between;align-items:center;padding:6px 12px;font-size:12px;display:flex}.seg-pagination-info{white-space:nowrap}.seg-pagination-controls{align-items:center;gap:4px;display:flex}.seg-pagination-btn{color:#333;cursor:pointer;background:#fff;border:1px solid #ccc;border-radius:3px;justify-content:center;align-items:center;width:28px;height:24px;padding:0;font-size:14px;line-height:1;display:inline-flex}.seg-pagination-btn:hover:not(:disabled){background:#e8f0fe;border-color:#aac}.seg-pagination-btn:disabled{color:#bbb;cursor:default;background:#f0f0f0}.seg-pagination-page{white-space:nowrap;padding:0 8px}
2
+ /*$vite$:1*/
@@ -0,0 +1,2 @@
1
+ export { SimpleExGrid } from './SimpleExGrid';
2
+ export type { SimpleExGridProps, ColumnDef, ColumnWidth, RowHeight, SortMark, CellRange, CellChange, GridMode, HistoryApi, } from './types';