wms-spreadsheet 0.1.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/index.d.ts +337 -0
- package/dist/index.js +4984 -0
- package/dist/spreadsheet.css +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WMS
|
|
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,67 @@
|
|
|
1
|
+
# wms-spreadsheet
|
|
2
|
+
|
|
3
|
+
Thư viện Spreadsheet cho React — giao diện giống Google Sheets, hỗ trợ **10.000+ dòng** mượt mà nhờ virtual window.
|
|
4
|
+
|
|
5
|
+
## Cài đặt
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install wms-spreadsheet
|
|
9
|
+
# hoặc
|
|
10
|
+
pnpm add wms-spreadsheet
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Peer dependencies:** `react` và `react-dom` (^18 hoặc ^19).
|
|
14
|
+
|
|
15
|
+
## Sử dụng nhanh
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { useRef } from "react";
|
|
19
|
+
import {
|
|
20
|
+
Spreadsheet,
|
|
21
|
+
type ISpreadsheetColumn,
|
|
22
|
+
type ISpreadsheetRef,
|
|
23
|
+
} from "wms-spreadsheet";
|
|
24
|
+
import "wms-spreadsheet/style.css";
|
|
25
|
+
|
|
26
|
+
const COLUMNS: ISpreadsheetColumn[] = [
|
|
27
|
+
{ colName: "sku", colText: "Mã SKU", width: 120, showFilter: true },
|
|
28
|
+
{ colName: "qty", colText: "SL", width: 80 },
|
|
29
|
+
{ colName: "active", colText: "Kích hoạt", width: 90, meta: { type: "switch" } },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const INITIAL_DATA = [
|
|
33
|
+
{ sku: "A001", qty: "10", active: "true" },
|
|
34
|
+
{ sku: "A002", qty: "5", active: "false" },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
function App() {
|
|
38
|
+
const sheetRef = useRef<ISpreadsheetRef>(null);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div style={{ width: "100%", height: "600px" }}>
|
|
42
|
+
<Spreadsheet
|
|
43
|
+
ref={sheetRef}
|
|
44
|
+
columns={COLUMNS}
|
|
45
|
+
initialData={INITIAL_DATA}
|
|
46
|
+
rowCount={1000}
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Tính năng
|
|
54
|
+
|
|
55
|
+
- Virtual window 2 chiều — chỉ render cell trong viewport
|
|
56
|
+
- API imperative qua `ref` — `setCellValue`, `loadData`, `mergeCells`, …
|
|
57
|
+
- Cell types: `text`, `select`, `multiSelect`, `boolean`, `switch`, `date`, `custom`
|
|
58
|
+
- Copy/paste range (Ctrl+C / Ctrl+V), filter & sort cột, frozen columns
|
|
59
|
+
- Merge cells, resize cột/hàng, đa ngôn ngữ (`locale` prop)
|
|
60
|
+
|
|
61
|
+
## Tài liệu đầy đủ
|
|
62
|
+
|
|
63
|
+
Xem [README trên GitHub](https://github.com/truongnguyen5x/wms-spreadsheet#readme) để biết API ref, cell meta, custom cells, locale, xử lý lỗi, v.v.
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { ForwardRefExoticComponent } from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { RefAttributes } from 'react';
|
|
4
|
+
|
|
5
|
+
export declare function cellKey(row: number, col: number): string;
|
|
6
|
+
|
|
7
|
+
export declare class CellStore {
|
|
8
|
+
private data;
|
|
9
|
+
private listeners;
|
|
10
|
+
getValue(row: number, col: number): string;
|
|
11
|
+
setValue(row: number, col: number, value: string): void;
|
|
12
|
+
setValues(cells: ICellStoreInput[]): void;
|
|
13
|
+
subscribe(row: number, col: number, listener: () => void): () => void;
|
|
14
|
+
getAllData(): ISheetData;
|
|
15
|
+
loadData(data: ISheetData): void;
|
|
16
|
+
clearAndLoad(data: ISheetData): void;
|
|
17
|
+
getKeys(): string[];
|
|
18
|
+
parseKey(key: string): {
|
|
19
|
+
row: number;
|
|
20
|
+
col: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export declare const COLUMN_HEADER_HEIGHT = 28;
|
|
25
|
+
|
|
26
|
+
export declare function columnLabel(col: number): string;
|
|
27
|
+
|
|
28
|
+
export declare type DeepPartial<T> = {
|
|
29
|
+
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export declare const DEFAULT_COLUMN_WIDTH = 100;
|
|
33
|
+
|
|
34
|
+
export declare const DEFAULT_DATE_FORMAT = "DD/MM/YYYY";
|
|
35
|
+
|
|
36
|
+
export declare const DEFAULT_OVERSCAN = 3;
|
|
37
|
+
|
|
38
|
+
export declare const DEFAULT_ROW_HEIGHT = 28;
|
|
39
|
+
|
|
40
|
+
export declare const DEFAULT_SPREADSHEET_LOCALE: ISpreadsheetLocale;
|
|
41
|
+
|
|
42
|
+
export declare const FILTER_BLANK_VALUE = "__BLANK__";
|
|
43
|
+
|
|
44
|
+
export declare interface ICellAddress {
|
|
45
|
+
row: number;
|
|
46
|
+
col: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export declare interface ICellEditorParams extends ICellRenderParams {
|
|
50
|
+
top: number;
|
|
51
|
+
left: number;
|
|
52
|
+
width: number;
|
|
53
|
+
height: number;
|
|
54
|
+
onCommit: (value: string, direction?: ICommitDirection) => void;
|
|
55
|
+
onCancel: () => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export declare interface ICellInput {
|
|
59
|
+
row: number;
|
|
60
|
+
col?: number | null;
|
|
61
|
+
colName?: string;
|
|
62
|
+
value: TCellValue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export declare interface ICellMeta {
|
|
66
|
+
type?: TCellType;
|
|
67
|
+
options?: ISelectOption[];
|
|
68
|
+
/** Định dạng ngày lưu và hiển thị. Mặc định DD/MM/YYYY */
|
|
69
|
+
dateFormat?: string;
|
|
70
|
+
/** Ngày sớm nhất được chọn (chuỗi theo dateFormat của cell/cột) */
|
|
71
|
+
minDate?: string;
|
|
72
|
+
/** Ngày muộn nhất được chọn (chuỗi theo dateFormat của cell/cột) */
|
|
73
|
+
maxDate?: string;
|
|
74
|
+
customKey?: string;
|
|
75
|
+
customProps?: Record<string, unknown>;
|
|
76
|
+
invalid?: boolean;
|
|
77
|
+
disabled?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export declare interface ICellMetaInput {
|
|
81
|
+
row: number;
|
|
82
|
+
col?: number | null;
|
|
83
|
+
colName?: string;
|
|
84
|
+
meta: Partial<ICellMeta>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export declare interface ICellRenderParams {
|
|
88
|
+
row: number;
|
|
89
|
+
col: number;
|
|
90
|
+
value: string;
|
|
91
|
+
meta: ICellMeta;
|
|
92
|
+
isActive: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Input nội bộ cho CellStore — luôn dùng index cột. */
|
|
96
|
+
declare interface ICellStoreInput {
|
|
97
|
+
row: number;
|
|
98
|
+
col: number;
|
|
99
|
+
value: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export declare interface IClipboardData {
|
|
103
|
+
range: INormalizedRange;
|
|
104
|
+
values: string[][];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export declare interface IColumnFilterState {
|
|
108
|
+
condition: TFilterCondition;
|
|
109
|
+
conditionValue?: string;
|
|
110
|
+
/** null = không giới hạn checkbox value. */
|
|
111
|
+
selectedValues: string[] | null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export declare interface IColumnHeaderRenderParams {
|
|
115
|
+
col: number;
|
|
116
|
+
column: ISpreadsheetColumn;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export declare interface IColumnSortState {
|
|
120
|
+
col: number;
|
|
121
|
+
direction: TSortDirection;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export declare type ICommitDirection = "stay" | "down" | "right";
|
|
125
|
+
|
|
126
|
+
export declare interface ICustomCellDefinition {
|
|
127
|
+
render: (params: ICellRenderParams) => ReactNode;
|
|
128
|
+
editor?: (params: ICellEditorParams) => ReactNode;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export declare interface IMergedRange {
|
|
132
|
+
anchorRow: number;
|
|
133
|
+
anchorCol: number;
|
|
134
|
+
rowSpan: number;
|
|
135
|
+
colSpan: number;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
declare interface IMetaStoreInput {
|
|
139
|
+
row: number;
|
|
140
|
+
col: number;
|
|
141
|
+
meta: Partial<ICellMeta>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export declare interface INormalizedRange {
|
|
145
|
+
startRow: number;
|
|
146
|
+
endRow: number;
|
|
147
|
+
startCol: number;
|
|
148
|
+
endCol: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export declare interface ISelection {
|
|
152
|
+
anchor: ICellAddress;
|
|
153
|
+
focus: ICellAddress;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export declare interface ISelectOption {
|
|
157
|
+
id: string;
|
|
158
|
+
label: string;
|
|
159
|
+
color?: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export declare type ISheetData = Record<string, string>;
|
|
163
|
+
|
|
164
|
+
export declare interface ISpreadsheetColumn {
|
|
165
|
+
/** Key trong object data và API colName. */
|
|
166
|
+
colName: string;
|
|
167
|
+
width: number;
|
|
168
|
+
/** Text hiển thị header; mặc định colName. */
|
|
169
|
+
colText?: string;
|
|
170
|
+
/** Custom render header cell; ưu tiên hơn colText. */
|
|
171
|
+
colRender?: (params: IColumnHeaderRenderParams) => ReactNode;
|
|
172
|
+
/** Hiển thị icon/filter popup ở header cột. */
|
|
173
|
+
showFilter?: boolean;
|
|
174
|
+
/** Meta mặc định cho toàn cột; cell meta override từng field. */
|
|
175
|
+
meta?: Partial<ICellMeta>;
|
|
176
|
+
/** Căn ngang nội dung body cell. Mặc định "left". Không áp header. */
|
|
177
|
+
horizontalAlign?: THorizontalAlign;
|
|
178
|
+
/** Căn dọc nội dung body cell. Mặc định "top". Không áp header. */
|
|
179
|
+
verticalAlign?: TVerticalAlign;
|
|
180
|
+
/** Override render cell body; ưu tiên hơn registry. */
|
|
181
|
+
cellRender?: (params: ICellRenderParams) => ReactNode;
|
|
182
|
+
/** Override editor cell; ưu tiên hơn registry. */
|
|
183
|
+
cellEditor?: (params: ICellEditorParams) => ReactNode;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export declare interface ISpreadsheetError {
|
|
187
|
+
code: TSpreadsheetErrorCode;
|
|
188
|
+
message: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export declare interface ISpreadsheetLocale {
|
|
192
|
+
errors: {
|
|
193
|
+
mergeInvalidRange: string;
|
|
194
|
+
mergeOverlap: string;
|
|
195
|
+
mergeWhileSortFilterActive: string;
|
|
196
|
+
filterWhileMerged: string;
|
|
197
|
+
sortWhileMerged: string;
|
|
198
|
+
copyMergedNotAllowed: string;
|
|
199
|
+
pasteMergedNotAllowed: string;
|
|
200
|
+
};
|
|
201
|
+
filter: {
|
|
202
|
+
columnAriaLabel: string;
|
|
203
|
+
dialogAriaLabel: string;
|
|
204
|
+
sortAsc: string;
|
|
205
|
+
sortDesc: string;
|
|
206
|
+
filterByCondition: string;
|
|
207
|
+
filterByValue: string;
|
|
208
|
+
valuePlaceholder: string;
|
|
209
|
+
searchPlaceholder: string;
|
|
210
|
+
selectAll: string;
|
|
211
|
+
clear: string;
|
|
212
|
+
reset: string;
|
|
213
|
+
ok: string;
|
|
214
|
+
cancel: string;
|
|
215
|
+
blankCells: string;
|
|
216
|
+
conditions: Record<TFilterCondition, string>;
|
|
217
|
+
};
|
|
218
|
+
datepicker: {
|
|
219
|
+
dialogAriaLabel: string;
|
|
220
|
+
prevMonthAriaLabel: string;
|
|
221
|
+
nextMonthAriaLabel: string;
|
|
222
|
+
today: string;
|
|
223
|
+
clear: string;
|
|
224
|
+
monthNames: readonly string[];
|
|
225
|
+
weekdayLabels: readonly string[];
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export declare interface ISpreadsheetProps {
|
|
230
|
+
rowCount: number;
|
|
231
|
+
columnCount: number;
|
|
232
|
+
columns?: ISpreadsheetColumn[];
|
|
233
|
+
rowHeight?: number;
|
|
234
|
+
/** Chiều cao hàng header cột (px). Mặc định COLUMN_HEADER_HEIGHT. */
|
|
235
|
+
colHeaderHeight?: number;
|
|
236
|
+
columnWidth?: number;
|
|
237
|
+
overscan?: number;
|
|
238
|
+
className?: string;
|
|
239
|
+
onChange?: (changes: ICellInput[]) => void;
|
|
240
|
+
onError?: (error: ISpreadsheetError) => void;
|
|
241
|
+
onColumnResize?: (col: number, width: number) => void;
|
|
242
|
+
onRowResize?: (row: number, height: number) => void;
|
|
243
|
+
initialData?: TSheetDataInput;
|
|
244
|
+
/** Số cột cố định từ trái (0 = không cố định). Ví dụ 2 → cột A, B. */
|
|
245
|
+
frozenColumnCount?: number;
|
|
246
|
+
/** Registry custom cell render/editor theo customKey. */
|
|
247
|
+
customCellRegistry?: Record<string, ICustomCellDefinition>;
|
|
248
|
+
/** UI strings; mặc định tiếng Anh. Truyền partial để override đa ngôn ngữ. */
|
|
249
|
+
locale?: DeepPartial<ISpreadsheetLocale>;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export declare interface ISpreadsheetRef {
|
|
253
|
+
setCellValue(row: number, col: number | null, value: TCellValue, colName?: string): void;
|
|
254
|
+
getCellValue(row: number, col: number | null, colName?: string): TCellValue;
|
|
255
|
+
setCellValues(cells: ICellInput[]): void;
|
|
256
|
+
loadData(data: TSheetDataInput): void;
|
|
257
|
+
getData(): TSheetDataOutput;
|
|
258
|
+
getRowData(row: number): TSheetRowDataOutput;
|
|
259
|
+
getActiveCell(): ICellAddress | null;
|
|
260
|
+
setActiveCell(cell: ICellAddress): void;
|
|
261
|
+
getSelection(): ISelection | null;
|
|
262
|
+
setSelection(selection: ISelection): void;
|
|
263
|
+
setCellMeta(row: number, col: number | null, meta: Partial<ICellMeta>, colName?: string): void;
|
|
264
|
+
getCellMeta(row: number, col: number | null, colName?: string): ICellMeta;
|
|
265
|
+
setCellsMeta(cells: ICellMetaInput[]): void;
|
|
266
|
+
mergeCells(range?: INormalizedRange): boolean;
|
|
267
|
+
unmergeCells(row?: number, col?: number): boolean;
|
|
268
|
+
getMergedRanges(): IMergedRange[];
|
|
269
|
+
hasMergedCells(): boolean;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export declare class MergeStore {
|
|
273
|
+
private anchors;
|
|
274
|
+
private coveredToAnchor;
|
|
275
|
+
private listeners;
|
|
276
|
+
private revision;
|
|
277
|
+
getRevision(): number;
|
|
278
|
+
subscribe(listener: () => void): () => void;
|
|
279
|
+
private notify;
|
|
280
|
+
getAll(): IMergedRange[];
|
|
281
|
+
hasAny(): boolean;
|
|
282
|
+
getRole(row: number, col: number): TMergeCellRole;
|
|
283
|
+
resolveAnchor(row: number, col: number): ICellAddress;
|
|
284
|
+
getSpan(row: number, col: number): {
|
|
285
|
+
rowSpan: number;
|
|
286
|
+
colSpan: number;
|
|
287
|
+
};
|
|
288
|
+
getMergeAt(row: number, col: number): IMergedRange | null;
|
|
289
|
+
rangeIntersectsMerge(range: INormalizedRange): boolean;
|
|
290
|
+
expandRange(range: INormalizedRange): INormalizedRange;
|
|
291
|
+
canMerge(range: INormalizedRange): boolean;
|
|
292
|
+
merge(range: INormalizedRange, store: CellStore, metaStore: MetaStore): boolean;
|
|
293
|
+
unmerge(row: number, col: number): boolean;
|
|
294
|
+
clear(): void;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export declare class MetaStore {
|
|
298
|
+
private data;
|
|
299
|
+
private listeners;
|
|
300
|
+
getMeta(row: number, col: number): ICellMeta;
|
|
301
|
+
getStoredMeta(row: number, col: number): Partial<ICellMeta>;
|
|
302
|
+
setMeta(row: number, col: number, meta: Partial<ICellMeta>): void;
|
|
303
|
+
setMetas(cells: IMetaStoreInput[]): void;
|
|
304
|
+
subscribe(row: number, col: number, listener: () => void): () => void;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export declare function resolveSpreadsheetLocale(partial?: DeepPartial<ISpreadsheetLocale>): ISpreadsheetLocale;
|
|
308
|
+
|
|
309
|
+
export declare const ROW_HEADER_WIDTH = 46;
|
|
310
|
+
|
|
311
|
+
export declare const Spreadsheet: ForwardRefExoticComponent<ISpreadsheetProps & RefAttributes<ISpreadsheetRef>>;
|
|
312
|
+
|
|
313
|
+
export declare type TCellType = "text" | "select" | "multiSelect" | "boolean" | "switch" | "date" | "custom";
|
|
314
|
+
|
|
315
|
+
export declare type TCellValue = string | string[];
|
|
316
|
+
|
|
317
|
+
export declare type TFilterCondition = "none" | "isEmpty" | "isNotEmpty" | "isEqualTo" | "isNotEqualTo" | "beginsWith" | "endsWith" | "contains" | "doesNotContain";
|
|
318
|
+
|
|
319
|
+
export declare type THorizontalAlign = "left" | "center" | "right";
|
|
320
|
+
|
|
321
|
+
declare type TMergeCellRole = "anchor" | "covered" | "none";
|
|
322
|
+
|
|
323
|
+
export declare type TSheetDataInput = ISheetData | string[][] | TSheetRowRecord[];
|
|
324
|
+
|
|
325
|
+
export declare type TSheetDataOutput = ISheetData | TSheetRowRecord[];
|
|
326
|
+
|
|
327
|
+
export declare type TSheetRowDataOutput = ISheetData | TSheetRowRecord;
|
|
328
|
+
|
|
329
|
+
declare type TSheetRowRecord = Record<string, TCellValue>;
|
|
330
|
+
|
|
331
|
+
export declare type TSortDirection = "asc" | "desc";
|
|
332
|
+
|
|
333
|
+
export declare type TSpreadsheetErrorCode = "MERGE_COPY_NOT_ALLOWED" | "MERGE_PASTE_NOT_ALLOWED" | "MERGE_INVALID_RANGE" | "MERGE_OVERLAP" | "MERGE_SORT_FILTER_ACTIVE";
|
|
334
|
+
|
|
335
|
+
export declare type TVerticalAlign = "top" | "middle" | "bottom";
|
|
336
|
+
|
|
337
|
+
export { }
|