cenitu-table 0.2.0 → 0.4.0-beta.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 +226 -137
- package/README.tr.md +224 -126
- package/dist/cenitu-table.cjs +1 -1
- package/dist/cenitu-table.js +34 -31
- package/dist/index-BIH30fQ2.js +6571 -0
- package/dist/index-Bidvqh7P.cjs +197 -0
- package/dist/react.cjs +1 -1
- package/dist/react.js +67 -50
- package/dist/types/export/index.d.ts +2 -0
- package/dist/types/export/types.d.ts +1 -0
- package/dist/types/export/unzip.d.ts +21 -0
- package/dist/types/export/xlsxRead.d.ts +35 -0
- package/dist/types/export/xlsxStyles.d.ts +23 -0
- package/dist/types/export/xmlReader.d.ts +15 -0
- package/dist/types/grid/cellTypes.d.ts +10 -1
- package/dist/types/grid/clipboardHtml.d.ts +13 -0
- package/dist/types/grid/columnMenu.d.ts +11 -1
- package/dist/types/grid/filters.d.ts +3 -2
- package/dist/types/grid/grid.d.ts +162 -10
- package/dist/types/grid/messages.d.ts +28 -0
- package/dist/types/grid/search.d.ts +41 -0
- package/dist/types/grid/serverMode.d.ts +66 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/react/index.d.ts +8 -1
- package/package.json +1 -1
- package/dist/index-BJIaMXd1.cjs +0 -164
- package/dist/index-DG98ykBX.js +0 -5230
package/README.md
CHANGED
|
@@ -1,137 +1,226 @@
|
|
|
1
|
-
# CenituTable
|
|
2
|
-
|
|
3
|
-
[](https://github.com/cenitu/cenitu-table/actions/workflows/ci.yml)
|
|
4
|
-
[](https://github.com/cenitu/cenitu-table/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/cenitu-table)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
A spreadsheet-like data grid with a built-in, multi-sheet formula engine. Written in TypeScript with **zero runtime dependencies**; an optional React component is included.
|
|
8
|
+
|
|
9
|
+
**[Live demo](https://cenitu.github.io/cenitu-table/)** · [Türkçe](README.tr.md) · [Design notes](docs/SPEC.md)
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Grid:** virtual scrolling (only visible cells are in the DOM), keyboard navigation (Ctrl+arrows jump to data edges), spreadsheet-compatible copy/cut/paste with styles, undo/redo, column and row resizing, row/column insert and delete, right-click context menu.
|
|
14
|
+
- **Sorting and filtering:** sort by several columns, filter by condition or value from the column menu. Only the view changes; data and formulas stay where they are.
|
|
15
|
+
- **Find and replace:** Ctrl+F / Ctrl+H, also through rows outside the viewport, in displayed values or in formulas.
|
|
16
|
+
- **Formula bar:** the active cell's address and content above the grid, editable.
|
|
17
|
+
- **Autofill:** drag the fill handle to continue number and text series and to copy formulas with relative references; double-click to fill down; Ctrl+D.
|
|
18
|
+
- **Cell types:** numeric (Intl formats: currency, percent), date, checkbox, dropdown; validators, read-only cells, custom renderers.
|
|
19
|
+
- **Formatting:** bold, italic, underline, strike-through, colors, alignment, wrapping (Ctrl+B / I / U), conditional formatting for cells or whole rows.
|
|
20
|
+
- **Layout:** frozen rows and columns, merged cells, hidden columns, column drag-and-drop.
|
|
21
|
+
- **Formula engine:** 100+ functions (SUM, IF, XLOOKUP, SUMIFS, TEXT, date functions…), dynamic arrays that spill (FILTER, SORT, UNIQUE, SEQUENCE), cross-sheet references (`'Other sheet'!A1`), named expressions, incremental recalculation, cycle detection.
|
|
22
|
+
- **Server mode:** paging, sorting and filtering on your backend, with a page bar and text, list, date and yes/no filters.
|
|
23
|
+
- **Data binding:** rows as arrays or as objects bound to columns by property path; edits are written back to your objects.
|
|
24
|
+
- **Import and export:** read and write XLSX (formulas, formats, styles, frozen panes, merges), export CSV with protection against formula injection, all without any library.
|
|
25
|
+
- **Accessibility:** ARIA grid roles and an announced active cell for screen readers.
|
|
26
|
+
- **React:** `<CenituTable />` from `cenitu-table/react`.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install cenitu-table
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { CenituGrid } from 'cenitu-table';
|
|
38
|
+
|
|
39
|
+
const grid = new CenituGrid(document.getElementById('grid')!, {
|
|
40
|
+
data: [
|
|
41
|
+
['Product', 'Qty', 'Price', 'Total'],
|
|
42
|
+
['Screw', 10, 2.5, '=B2*C2'],
|
|
43
|
+
['Nut', 20, 1.25, '=B3*C3'],
|
|
44
|
+
],
|
|
45
|
+
fixedRowsTop: 1,
|
|
46
|
+
columnSorting: true,
|
|
47
|
+
filters: true,
|
|
48
|
+
contextMenu: true,
|
|
49
|
+
columns: [
|
|
50
|
+
{ type: 'dropdown', source: ['Screw', 'Nut', 'Washer'] },
|
|
51
|
+
{ type: 'numeric', validator: (v) => Number(v) > 0 || 'Must be positive' },
|
|
52
|
+
{ type: 'numeric', numericFormat: { style: 'currency', currency: 'EUR' } },
|
|
53
|
+
{ type: 'numeric', numericFormat: { style: 'currency', currency: 'EUR' }, readOnly: true },
|
|
54
|
+
],
|
|
55
|
+
height: 400,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
grid.addHook('afterChange', (changes, source) => console.log(changes, source));
|
|
59
|
+
grid.getDataAtCell(1, 3); // 25 (computed value)
|
|
60
|
+
grid.getSourceDataAtCell(1, 3); // "=B2*C2"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Objects instead of arrays
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const orders = [{ product: 'Screw', qty: 10, customer: { name: 'Acme' } }];
|
|
67
|
+
|
|
68
|
+
new CenituGrid(el, {
|
|
69
|
+
data: orders,
|
|
70
|
+
columns: [{ data: 'product' }, { data: 'qty', type: 'numeric' }, { data: 'customer.name', title: 'Customer' }],
|
|
71
|
+
dataSchema: () => ({ product: '', qty: 0, customer: { name: '' } }),
|
|
72
|
+
});
|
|
73
|
+
// Edits are written back: orders[0].qty changes when the cell changes.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Formula cells write their computed value into the object and keep it current, so your properties stay plain values. Set `bindFormulas: 'text'` to store the formula text instead.
|
|
77
|
+
|
|
78
|
+
### Several sheets
|
|
79
|
+
|
|
80
|
+
Give one engine to several grids — one grid per sheet:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { CenituGrid, FormulaEngine } from 'cenitu-table';
|
|
84
|
+
|
|
85
|
+
const engine = FormulaEngine.buildEmpty();
|
|
86
|
+
engine.addNamedExpression('VatRate', 0.2);
|
|
87
|
+
new CenituGrid(el1, { formulas: engine, sheetName: 'Orders', data: orders });
|
|
88
|
+
new CenituGrid(el2, { formulas: engine, sheetName: 'Summary', data: [['=SUM(Orders!F2:F100)*(1+VatRate)']] });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Export
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
grid.downloadCSV('orders.csv'); // ";" fields and "," decimals for decimal-comma locales
|
|
95
|
+
await grid.downloadXLSX('book.xlsx', [summaryGrid]); // several sheets in one workbook
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Import
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
await grid.importXLSX(file, { sheet: 'Orders' }); // a File, Blob or bytes; default is the first sheet
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
To load a whole workbook, give all grids one engine, so formulas can reach the other sheets:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { CenituGrid, readXLSX, workbookEngine } from 'cenitu-table';
|
|
108
|
+
|
|
109
|
+
const book = await readXLSX(file);
|
|
110
|
+
const engine = workbookEngine(book);
|
|
111
|
+
book.sheets.forEach((sheet, i) => {
|
|
112
|
+
new CenituGrid(hosts[i], { formulas: engine, sheetName: sheet.name }).loadSheet(sheet);
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Read: values, formulas, number formats, fonts, fills, alignment, column widths, row heights, hidden columns, frozen panes, merges and defined names. Not read: charts, images, comments, conditional formats, data validation and hidden rows (they are shown). Pass `formulas: 'values'` to take the results stored in the file instead of formulas, for example when a file uses functions the engine does not have.
|
|
117
|
+
|
|
118
|
+
### Server mode
|
|
119
|
+
|
|
120
|
+
For lists too large to load, the grid describes the rows it needs and your application fetches them. It never sorts or filters on its own.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<CenituTable
|
|
124
|
+
dataMode="server"
|
|
125
|
+
rowKey="id"
|
|
126
|
+
data={page.items}
|
|
127
|
+
totalCount={page.totalCount}
|
|
128
|
+
loading={isFetching}
|
|
129
|
+
loadError={isError}
|
|
130
|
+
query={query}
|
|
131
|
+
onQueryChange={setQuery}
|
|
132
|
+
columns={[
|
|
133
|
+
{ id: 'orderDate', data: 'orderDate', sortable: true, serverFilter: { kind: 'date' } },
|
|
134
|
+
{ id: 'status', data: 'status', serverFilter: { kind: 'list', options: statusOptions } },
|
|
135
|
+
{ id: 'partner', data: 'partner.name' },
|
|
136
|
+
]}
|
|
137
|
+
/>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`query` holds `page`, `pageSize`, `sort: [{ field, desc }]`, `filters` and `search`, always with column ids, so hiding or moving columns cannot break it. Filters are `text`, `list`, `date` (`from`, `to`) or `boolean`. Without React, listen to the `afterQueryChange` hook and pass rows to `grid.setPageRows(rows, total)`.
|
|
141
|
+
|
|
142
|
+
Rules worth knowing:
|
|
143
|
+
|
|
144
|
+
- Only columns with `sortable` or `serverFilter` get a menu.
|
|
145
|
+
- Sort, filter and page size changes return to page 1.
|
|
146
|
+
- New rows keep the sort marks, filters, column layout, scroll position and, by `rowKey`, the selection.
|
|
147
|
+
- Ignoring outdated responses is up to the application, since it makes the requests.
|
|
148
|
+
- Change events carry `rowKey` and `columnId`; saving edits, also before a page change, is up to the application.
|
|
149
|
+
- Undo history ends with each new page.
|
|
150
|
+
- Formulas are off unless `formulas` is set, and they then see only the loaded page.
|
|
151
|
+
- Select all, find and replace, and CSV/XLSX export cover the loaded page only.
|
|
152
|
+
- Rows cannot be added or removed locally. A paste that does not fit is cut off and reported by `afterPasteClipped`.
|
|
153
|
+
|
|
154
|
+
### Row events and saved column layout
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
<CenituTable
|
|
158
|
+
rowKey="id"
|
|
159
|
+
columnLayout={saved}
|
|
160
|
+
onColumnLayoutChange={(layout) => localStorage.setItem('orders', JSON.stringify(layout))}
|
|
161
|
+
onRowDoubleClick={({ rowKey }) => {
|
|
162
|
+
openOrder(rowKey);
|
|
163
|
+
return false; // keeps the cell editor closed
|
|
164
|
+
}}
|
|
165
|
+
/>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Layouts need column ids (`columns[i].id` or `data`). Removed columns are skipped when restoring, and new ones stay after the saved ones. A double click also fires two row clicks, as in the browser. `setColumnLayout` clears undo history when it moves columns.
|
|
169
|
+
|
|
170
|
+
### Find, formula bar, sorting by several columns
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
new CenituGrid(el, { data, search: true, formulaBar: true, columnSorting: true });
|
|
174
|
+
|
|
175
|
+
grid.openSearch(true); // or Ctrl+F / Ctrl+H with `search: true`
|
|
176
|
+
grid.findNext('screw', { matchCase: false });
|
|
177
|
+
grid.replaceAll('A1', 'B1', { lookIn: 'formulas' }); // one undo step; returns the cells changed
|
|
178
|
+
grid.sortBy([{ col: 2, order: 'desc' }, { col: 0, order: 'asc' }]);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Without `lookIn: 'formulas'`, replacing leaves formula cells alone. `formulaBar` adds a row above the grid, on top of its `height`. The column menu offers "Then sort" once another column is sorted.
|
|
182
|
+
|
|
183
|
+
### React
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { CenituTable } from 'cenitu-table/react';
|
|
187
|
+
|
|
188
|
+
<CenituTable ref={gridRef} data={data} fixedRowsTop={1} onChange={(changes) => save(changes)} />;
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Formula engine on its own
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { FormulaEngine, registerFunction } from 'cenitu-table';
|
|
195
|
+
|
|
196
|
+
const engine = FormulaEngine.buildFromArray([[1, 2, '=A1+B1']]);
|
|
197
|
+
engine.setCellContents({ row: 0, col: 0 }, 10); // returns the changed cells
|
|
198
|
+
engine.getCellValue({ row: 0, col: 2 }); // 12
|
|
199
|
+
registerFunction('DOUBLE', { minArgs: 1, maxArgs: 1, impl: ([x]) => (x as number) * 2 });
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Localization
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { trTR } from 'cenitu-table';
|
|
206
|
+
|
|
207
|
+
new CenituGrid(el, { locale: 'tr-TR', language: trTR, data });
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`locale` drives number and date formatting; `language` holds the UI texts (English by default, Turkish included).
|
|
211
|
+
|
|
212
|
+
## Development
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
npm install
|
|
216
|
+
npm run dev # demo at http://localhost:5180
|
|
217
|
+
npm test # unit tests (vitest + jsdom)
|
|
218
|
+
npm run test:e2e # browser tests (Playwright: Chromium, Firefox, WebKit)
|
|
219
|
+
npm run build # type check, ESM/CJS bundles and .d.ts in dist/
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Notable changes are listed in [CHANGELOG.md](CHANGELOG.md).
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
[MIT](LICENSE)
|