@updog/data-editor 0.1.3 → 0.1.5
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 +281 -107
- package/index.css +1 -1
- package/index.d.ts +303 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# @updog/data-editor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Client-side data importer and spreadsheet editor SDK for React. Your users import files, match columns to your schema, fix errors, and submit clean data. Edits happen inline, in the browser, at 1M+ rows.
|
|
4
|
+
|
|
5
|
+
> **Not using React?** Use the framework-agnostic Web Component build — [`@updog/data-editor-wc`](https://www.npmjs.com/package/@updog/data-editor-wc) — for Angular, Vue, Svelte, or vanilla JS.
|
|
4
6
|
|
|
5
7
|
## Requirements
|
|
6
8
|
|
|
7
9
|
- React 18.x or 19.x
|
|
8
|
-
- An API key — this is a **commercial SDK**. Sign up at [updog.tech](https://updog.tech) to
|
|
10
|
+
- An API key — this is a **commercial SDK**. Sign up at [updog.tech](https://updog.tech) to get one.
|
|
9
11
|
|
|
10
12
|
## Installation
|
|
11
13
|
|
|
@@ -16,44 +18,71 @@ npm install @updog/data-editor
|
|
|
16
18
|
## Quick Start
|
|
17
19
|
|
|
18
20
|
```tsx
|
|
19
|
-
import {
|
|
21
|
+
import { useState } from "react";
|
|
22
|
+
import { DataEditor, required, email } from "@updog/data-editor";
|
|
20
23
|
import "@updog/data-editor/styles.css";
|
|
24
|
+
import type { DataEditorColumn } from "@updog/data-editor";
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
];
|
|
26
|
+
type Employee = {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
email: string;
|
|
30
|
+
role: string;
|
|
31
|
+
};
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
33
|
+
const columns: DataEditorColumn[] = [
|
|
34
|
+
{ id: "name", title: "Full Name", size: 200, validate: required("Name is required") },
|
|
35
|
+
{ id: "email", title: "Email", size: 250, validate: [required("Email is required"), email("Invalid email")] },
|
|
36
|
+
{ id: "role", title: "Role", editor: { type: "select", options: ["Admin", "Editor", "Viewer"] } },
|
|
37
|
+
];
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
setOpen(false);
|
|
38
|
-
};
|
|
39
|
+
export function App() {
|
|
40
|
+
const [open, setOpen] = useState(false);
|
|
39
41
|
|
|
40
42
|
return (
|
|
41
43
|
<>
|
|
42
44
|
<button onClick={() => setOpen(true)}>Open Editor</button>
|
|
43
|
-
<DataEditor
|
|
44
|
-
apiKey="your-
|
|
45
|
+
<DataEditor<Employee>
|
|
46
|
+
apiKey="your-license-key"
|
|
45
47
|
open={open}
|
|
46
48
|
onClose={() => setOpen(false)}
|
|
47
49
|
columns={columns}
|
|
48
50
|
primaryKey="id"
|
|
49
|
-
loadData={
|
|
50
|
-
|
|
51
|
+
loadData={async (onChunk) => {
|
|
52
|
+
const res = await fetch("/api/employees");
|
|
53
|
+
onChunk(await res.json());
|
|
54
|
+
}}
|
|
55
|
+
onComplete={async (result, actions) => {
|
|
56
|
+
for (const source of result.sources) {
|
|
57
|
+
const inserts = source.rows.filter((r) => r.isNew && !r.isDeleted && r.isValid);
|
|
58
|
+
const updates = source.rows.filter((r) => !r.isNew && r.isChanged && !r.isDeleted && r.isValid);
|
|
59
|
+
const deletes = source.rows.filter((r) => r.isDeleted && !r.isNew);
|
|
60
|
+
await persist(source.sourceId, { inserts, updates, deletes });
|
|
61
|
+
}
|
|
62
|
+
actions.reset();
|
|
63
|
+
setOpen(false);
|
|
64
|
+
}}
|
|
51
65
|
/>
|
|
52
66
|
</>
|
|
53
67
|
);
|
|
54
68
|
}
|
|
55
69
|
```
|
|
56
70
|
|
|
71
|
+
## TypeScript
|
|
72
|
+
|
|
73
|
+
`DataEditor` accepts a generic type parameter for row data. This gives you type-safe access to `primaryKey` and the result rows in `onComplete`.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
type Employee = { id: string; name: string; email: string };
|
|
77
|
+
|
|
78
|
+
<DataEditor<Employee>
|
|
79
|
+
primaryKey="id" // autocomplete shows "id" | "name" | "email"
|
|
80
|
+
// ...
|
|
81
|
+
/>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
When you omit the generic, rows are typed as `Record<string, unknown>`.
|
|
85
|
+
|
|
57
86
|
## Inline Mode
|
|
58
87
|
|
|
59
88
|
Render the editor directly in the DOM without a modal overlay:
|
|
@@ -61,7 +90,7 @@ Render the editor directly in the DOM without a modal overlay:
|
|
|
61
90
|
```tsx
|
|
62
91
|
<DataEditor
|
|
63
92
|
mode="inline"
|
|
64
|
-
apiKey="your-
|
|
93
|
+
apiKey="your-license-key"
|
|
65
94
|
columns={columns}
|
|
66
95
|
primaryKey="id"
|
|
67
96
|
loadData={loadData}
|
|
@@ -69,151 +98,296 @@ Render the editor directly in the DOM without a modal overlay:
|
|
|
69
98
|
/>
|
|
70
99
|
```
|
|
71
100
|
|
|
101
|
+
In inline mode, `open` and `onClose` don't apply.
|
|
102
|
+
|
|
72
103
|
## Props
|
|
73
104
|
|
|
74
105
|
| Prop | Type | Required | Default | Description |
|
|
75
106
|
|---|---|---|---|---|
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `loadData` | `(onChunk) => Promise<void>` | No | — | Async data loader
|
|
83
|
-
| `onComplete` | `(result, actions) => void` | No | — | Called on submit
|
|
84
|
-
| `variant` | `"editor"` \| `"uploader"` | No | `"editor"` |
|
|
85
|
-
| `translations` | `DataEditorTranslations` | No | — | i18n overrides |
|
|
86
|
-
| `locale` | `string` | No | `"en"` | BCP 47 locale tag |
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
107
|
+
| `apiKey` | `string` | Yes | — | Your Updog license key. Validated on each open. |
|
|
108
|
+
| `columns` | `DataEditorColumn[]` | Yes | — | Column definitions. |
|
|
109
|
+
| `primaryKey` | `keyof TRow` | Yes | — | Column ID that uniquely identifies each row. |
|
|
110
|
+
| `mode` | `"modal"` \| `"inline"` | No | `"modal"` | Rendering mode. |
|
|
111
|
+
| `open` | `boolean` | Modal only | — | Controlled modal visibility. |
|
|
112
|
+
| `onClose` | `() => void` | Modal only | — | Called when the user closes the modal (X button or Escape). |
|
|
113
|
+
| `loadData` | `(onChunk) => Promise<void>` | No | — | Async data loader. Stream rows in chunks, optionally tagged by source. |
|
|
114
|
+
| `onComplete` | `(result, actions) => void` | No | — | Called on submit. See [Submission Result](#submission-result). |
|
|
115
|
+
| `variant` | `"editor"` \| `"uploader"` | No | `"editor"` | Initial view. `"uploader"` opens the import wizard first. |
|
|
116
|
+
| `translations` | `DataEditorTranslations` | No | — | Partial i18n overrides. |
|
|
117
|
+
| `locale` | `string` | No | `"en"` | BCP 47 locale tag. |
|
|
118
|
+
| `rtl` | `boolean` | No | `false` | Right-to-left layout. |
|
|
119
|
+
| `readonly` | `boolean` | No | `false` | Hide all editing UI. |
|
|
120
|
+
| `enableDeleteRow` | `"all"` \| `"new"` \| `false` | No | `false` | Row deletion policy. |
|
|
121
|
+
| `enableAddRow` | `boolean` | No | `true` | Show the "Add row" button. |
|
|
122
|
+
| `enableCreateColumn` | `boolean` | No | `true` | Allow creating columns for unmatched CSV headers during import. |
|
|
123
|
+
| `importFormats` | `DataEditorFormat[]` | No | all | Allowed import formats. `[]` disables import. |
|
|
124
|
+
| `exportFormats` | `DataEditorFormat[]` | No | all | Allowed export formats. `[]` disables export. |
|
|
125
|
+
| `remoteSources` | `RemoteSource[]` | No | — | Custom import buttons (Google Sheets, S3, etc.) rendered on the upload step. |
|
|
126
|
+
| `rowHeight` | `number` | No | `34` | Row height in pixels. |
|
|
127
|
+
| `headerHeight` | `number` | No | `36` | Header height in pixels. |
|
|
128
|
+
| `server` | `DataEditorServer<TRow>` | No | — | Server-delegated mode: SDK renders, your backend handles queries and mutations. |
|
|
129
|
+
| `chat` | `DataEditorChat<TRow>` | No | — | Bring-your-own AI chat panel. |
|
|
130
|
+
| `onColumnMatch` | `(headers, columns) => ...` | No | — | Override import column matching. |
|
|
131
|
+
| `onValueMatch` | `(valuesToMatch) => ...` | No | — | Override import value matching for `select` columns. |
|
|
132
|
+
| `synonyms` | `Record<string, string[]>` | No | — | Extra synonyms for column auto-matching. |
|
|
133
|
+
| `sampleData` | `Record<string, unknown>[]` | No | — | Rows used in the "Download Example" file. |
|
|
134
|
+
| `localStorage` | `false` \| `{ licenseGrant?: boolean }` | No | `{ licenseGrant: true }` | What the SDK caches in `localStorage`. |
|
|
135
|
+
| `onError` | `(error: UpdogError) => void` | No | — | Called on internal errors. Use for logging or Sentry. |
|
|
136
|
+
| `className` | `string` | No | — | CSS class on the wrapper element. |
|
|
96
137
|
|
|
97
138
|
## Column Configuration
|
|
98
139
|
|
|
99
140
|
```tsx
|
|
141
|
+
import { required, email } from "@updog/data-editor";
|
|
142
|
+
import type { DataEditorColumn } from "@updog/data-editor";
|
|
143
|
+
|
|
100
144
|
const columns: DataEditorColumn[] = [
|
|
101
145
|
{
|
|
102
146
|
id: "email",
|
|
103
147
|
title: "Email",
|
|
104
|
-
|
|
148
|
+
size: 260,
|
|
105
149
|
|
|
106
|
-
//
|
|
107
|
-
validate: (
|
|
108
|
-
if (!value) return { level: "error", message: "Required" };
|
|
109
|
-
if (!String(value).includes("@")) return { level: "warning", message: "Invalid email" };
|
|
110
|
-
return null;
|
|
111
|
-
},
|
|
150
|
+
// One validator, or an array. Each returns `{ level: "error", message }` or null.
|
|
151
|
+
validate: [required("Email is required"), email("Enter a valid email")],
|
|
112
152
|
|
|
113
|
-
//
|
|
153
|
+
// Flag duplicates in this column as errors.
|
|
114
154
|
unique: true,
|
|
115
155
|
|
|
116
|
-
//
|
|
156
|
+
// Revalidate these columns when this one changes.
|
|
117
157
|
dependentFields: ["confirmEmail"],
|
|
118
158
|
|
|
119
|
-
// Cell editor
|
|
120
|
-
editor: { type: "select", options: [
|
|
159
|
+
// Cell editor. Default is "text".
|
|
160
|
+
editor: { type: "select", options: ["US", "UK", "DE"] },
|
|
121
161
|
|
|
122
|
-
//
|
|
162
|
+
// Display-only formatting. Does not mutate stored data.
|
|
123
163
|
formatter: (value) => value.toLowerCase(),
|
|
124
164
|
|
|
125
|
-
//
|
|
165
|
+
// Runs when rows are uploaded to the editor. Mutates the stored value.
|
|
126
166
|
transformer: (value) => String(value).trim(),
|
|
127
167
|
|
|
128
|
-
//
|
|
168
|
+
// Sidebar filter control.
|
|
129
169
|
filter: { type: "select" },
|
|
170
|
+
|
|
171
|
+
// Lock cells in this column. `"default"` locks only default-source rows.
|
|
172
|
+
locked: "default",
|
|
130
173
|
},
|
|
131
174
|
];
|
|
132
175
|
```
|
|
133
176
|
|
|
177
|
+
**Cell editors**: `{ type: "text" }` (default), `{ type: "date", minDate?, maxDate? }`, `{ type: "select", options: string[] }`, `{ type: "number", decimalPlaces?, decimalSeparator?, thousandsSeparator?, allowChars? }`.
|
|
178
|
+
|
|
179
|
+
**Column filters**: `{ type: "select", label?, placeholder?, options?, multiple? }`, `{ type: "number-range", label? }`, `{ type: "date-range", label? }`.
|
|
180
|
+
|
|
134
181
|
## Built-in Validators
|
|
135
182
|
|
|
183
|
+
Validators are factory functions — call each one with the error message you want displayed.
|
|
184
|
+
|
|
136
185
|
```tsx
|
|
137
|
-
import {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
186
|
+
import {
|
|
187
|
+
required,
|
|
188
|
+
numeric,
|
|
189
|
+
email,
|
|
190
|
+
date,
|
|
191
|
+
oneOf,
|
|
192
|
+
endDateAfterStart,
|
|
193
|
+
} from "@updog/data-editor";
|
|
194
|
+
|
|
195
|
+
const columns = [
|
|
196
|
+
{ id: "name", title: "Name", validate: required("Required") },
|
|
197
|
+
{ id: "email", title: "Email", validate: [required("Required"), email("Invalid email")] },
|
|
198
|
+
{ id: "salary", title: "Salary", validate: numeric("Must be a number") },
|
|
199
|
+
{ id: "status", title: "Status", validate: oneOf(["active", "inactive"], "Invalid status") },
|
|
200
|
+
{
|
|
201
|
+
id: "startDate",
|
|
202
|
+
title: "Start",
|
|
203
|
+
validate: date("Use YYYY-MM-DD or DD/MM/YYYY"),
|
|
204
|
+
editor: { type: "date" },
|
|
205
|
+
dependentFields: ["endDate"],
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: "endDate",
|
|
209
|
+
title: "End",
|
|
210
|
+
validate: [date("Invalid date"), endDateAfterStart("startDate", "End must be on or after start")],
|
|
211
|
+
editor: { type: "date" },
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
A `ValidationError` with `level: "error"` flags the cell in the grid but does **not** block submission — invalid rows are delivered to `onComplete` tagged via `isValid: false`.
|
|
217
|
+
|
|
218
|
+
### Custom validators
|
|
219
|
+
|
|
220
|
+
A validator is `(value, row) => ValidationError | null`. The only `level` is `"error"`.
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
import type { CellValidator } from "@updog/data-editor";
|
|
224
|
+
|
|
225
|
+
const mustContainAt: CellValidator = (value) => {
|
|
226
|
+
if (!value) return null;
|
|
227
|
+
if (!String(value).includes("@")) return { level: "error", message: "Must contain @" };
|
|
228
|
+
return null;
|
|
229
|
+
};
|
|
155
230
|
```
|
|
156
231
|
|
|
157
232
|
## Data Loading
|
|
158
233
|
|
|
159
|
-
|
|
234
|
+
`loadData` is called once when the editor opens. Call `onChunk` one or more times to stream rows. The grid renders each chunk without blocking.
|
|
160
235
|
|
|
161
236
|
```tsx
|
|
162
|
-
|
|
163
|
-
|
|
237
|
+
// Single source
|
|
238
|
+
loadData={async (onChunk) => {
|
|
164
239
|
for (let page = 0; page < totalPages; page++) {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
onChunk(rows); // Each chunk is immediately rendered
|
|
240
|
+
const rows = await fetch(`/api/employees?page=${page}`).then((r) => r.json());
|
|
241
|
+
onChunk(rows);
|
|
168
242
|
}
|
|
169
|
-
}
|
|
243
|
+
}}
|
|
244
|
+
|
|
245
|
+
// Multiple sources — each chunk tagged with a source
|
|
246
|
+
loadData={async (onChunk) => {
|
|
247
|
+
onChunk(await fetchSalesforce(), { source: "Salesforce", done: true });
|
|
248
|
+
onChunk(await fetchHubSpot(), { source: "HubSpot", deletable: true, done: true });
|
|
249
|
+
}}
|
|
170
250
|
```
|
|
171
251
|
|
|
252
|
+
`ChunkSourceOptions`: `{ source: string; id?: string; deletable?: boolean; done?: boolean }`. Sources are auto-registered on first encounter. Chunks without options go to "Existing Data". When `loadData` resolves, any source still loading is finalized automatically.
|
|
253
|
+
|
|
172
254
|
## Submission Result
|
|
173
255
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
256
|
+
`onComplete(result, actions)` fires when the user submits.
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
type DataEditorResult<TRow> = {
|
|
260
|
+
sources: {
|
|
261
|
+
sourceId: string;
|
|
262
|
+
sourceName: string;
|
|
263
|
+
rows: {
|
|
264
|
+
row: TRow;
|
|
265
|
+
isNew: boolean; // imported or manually added this session
|
|
266
|
+
isChanged: boolean; // cell values differ from origin
|
|
267
|
+
isDeleted: boolean; // marked for deletion
|
|
268
|
+
isValid: boolean; // passes all validators
|
|
269
|
+
}[];
|
|
270
|
+
}[];
|
|
271
|
+
counts: { new: number; changed: number; deleted: number; invalid: number };
|
|
272
|
+
};
|
|
180
273
|
|
|
181
|
-
|
|
274
|
+
type DataEditorActions = {
|
|
275
|
+
reset: () => void; // discard changes and reload via loadData
|
|
182
276
|
};
|
|
183
277
|
```
|
|
184
278
|
|
|
185
|
-
|
|
279
|
+
Pristine backend rows (unchanged, not deleted, not new) are omitted from `sources[].rows` — they're no-ops. The flags are orthogonal: a row can be new, changed, and deleted at once. You own the routing rules:
|
|
186
280
|
|
|
187
|
-
|
|
281
|
+
```tsx
|
|
282
|
+
onComplete={async (result, actions) => {
|
|
283
|
+
for (const source of result.sources) {
|
|
284
|
+
const inserts = source.rows.filter((r) => r.isNew && !r.isDeleted && r.isValid);
|
|
285
|
+
const updates = source.rows.filter((r) => !r.isNew && r.isChanged && !r.isDeleted && r.isValid);
|
|
286
|
+
const deletes = source.rows.filter((r) => r.isDeleted && !r.isNew);
|
|
287
|
+
await persist(source.sourceId, { inserts, updates, deletes });
|
|
288
|
+
}
|
|
289
|
+
actions.reset();
|
|
290
|
+
}}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
If you never tagged sources via `loadData`, you'll get one entry with `sourceId: "backend"`. `result.counts` is an aggregate for summary UI — route with the per-row flags, not counts.
|
|
294
|
+
|
|
295
|
+
## Import Configuration
|
|
296
|
+
|
|
297
|
+
The import wizard guides users through uploading a file, mapping columns to your schema, and resolving value mismatches.
|
|
298
|
+
|
|
299
|
+
### Formats
|
|
300
|
+
|
|
301
|
+
Supported: `"csv"`, `"tsv"`, `"xlsx"`, `"json"`, `"xml"`.
|
|
188
302
|
|
|
189
303
|
```tsx
|
|
190
|
-
// Only allow CSV and XLSX import, disable export
|
|
191
304
|
<DataEditor
|
|
192
|
-
importFormats={["csv", "xlsx"]}
|
|
193
|
-
exportFormats={[]}
|
|
305
|
+
importFormats={["csv", "xlsx"]} // only CSV and XLSX
|
|
306
|
+
exportFormats={[]} // disable export entirely
|
|
194
307
|
/>
|
|
195
308
|
```
|
|
196
309
|
|
|
310
|
+
### Override column matching
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
onColumnMatch={async (headers, columns) => {
|
|
314
|
+
const mappings = await myMatchingService.match(headers, columns);
|
|
315
|
+
return mappings; // { csvHeader: columnId | null }
|
|
316
|
+
}}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Return a map of `{ csvHeader: columnId | null }`. Entries set to `null` or omitted fall back to built-in matching.
|
|
320
|
+
|
|
321
|
+
### Override value matching
|
|
322
|
+
|
|
323
|
+
For select columns, override imported-value → option mapping:
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
onValueMatch={async (valuesToMatch) => {
|
|
327
|
+
// valuesToMatch = { country: { importedValues: ["espana", "fr"], options: ["Spain", "France"] } }
|
|
328
|
+
return {
|
|
329
|
+
country: { espana: "Spain", fr: "France" },
|
|
330
|
+
};
|
|
331
|
+
}}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Values set to `null` skip auto-matching. Unmapped values fall back to built-in fuzzy matching.
|
|
335
|
+
|
|
336
|
+
### Synonyms
|
|
337
|
+
|
|
338
|
+
Extra aliases for the built-in column matcher:
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
synonyms={{
|
|
342
|
+
productSku: ["sku", "article_no", "item_code"],
|
|
343
|
+
firstName: ["first", "given_name", "fname"],
|
|
344
|
+
}}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Remote sources
|
|
348
|
+
|
|
349
|
+
Render custom import buttons for third-party sources. You own auth and picker logic; the SDK renders the button and processes the result.
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
const googleSheets: RemoteSource = {
|
|
353
|
+
id: "google-sheets",
|
|
354
|
+
label: "Google Sheets",
|
|
355
|
+
icon: "<svg>...</svg>",
|
|
356
|
+
fetch: async () => {
|
|
357
|
+
const data = await myGoogleSheetsLib.pick();
|
|
358
|
+
return data.rows; // Record<string, unknown>[] — or return a File
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
<DataEditor remoteSources={[googleSheets]} {...rest} />
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Return a `File` to parse via the standard CSV/XLSX/JSON/XML pipeline, or return structured records to skip parsing.
|
|
366
|
+
|
|
367
|
+
## Error Handling
|
|
368
|
+
|
|
369
|
+
Log SDK-internal errors through `onError`. The SDK recovers gracefully — use this for monitoring, not recovery.
|
|
370
|
+
|
|
371
|
+
```tsx
|
|
372
|
+
onError={(error) => {
|
|
373
|
+
Sentry.captureException(error.originalError ?? error, {
|
|
374
|
+
tags: { code: error.code, source: error.source },
|
|
375
|
+
});
|
|
376
|
+
}}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Error codes: `PARSE_ERROR`, `RENDER_ERROR`, `TRANSFORM_ERROR`, `VALIDATION_ERROR`, `WORKER_ERROR`, `COMMAND_ERROR`, `OPERATION_ERROR`.
|
|
380
|
+
|
|
197
381
|
## Non-React Usage
|
|
198
382
|
|
|
199
|
-
For Angular, Vue, or vanilla JS
|
|
383
|
+
For Angular, Vue, Svelte, or vanilla JS use the Web Component build:
|
|
200
384
|
|
|
201
385
|
```bash
|
|
202
386
|
npm install @updog/data-editor-wc
|
|
203
387
|
```
|
|
204
388
|
|
|
205
|
-
See [@updog/data-editor-wc](https://www.npmjs.com/package/@updog/data-editor-wc)
|
|
206
|
-
|
|
207
|
-
## Key Features
|
|
208
|
-
|
|
209
|
-
- Virtualized grid — smooth scrolling with 100k+ rows
|
|
210
|
-
- Multi-source data — backend data + multiple CSV/XLSX imports merged into one view
|
|
211
|
-
- Smart CSV column matching — auto-maps imported columns with 90%+ accuracy
|
|
212
|
-
- Undo/redo — Cmd+Z / Cmd+Shift+Z with batch support
|
|
213
|
-
- Find & replace — search across all columns with match highlighting
|
|
214
|
-
- Cell validation — custom validators, uniqueness constraints, cross-field dependencies
|
|
215
|
-
- Dirty tracking — distinguishes new vs edited rows, detects value reverts
|
|
389
|
+
See [@updog/data-editor-wc](https://www.npmjs.com/package/@updog/data-editor-wc).
|
|
216
390
|
|
|
217
391
|
## License
|
|
218
392
|
|
|
219
|
-
Commercial — see [LICENSE](./LICENSE) for the full terms of the Updog SDK Commercial License v1.0. Contact `admin@updog.tech` for enterprise licensing
|
|
393
|
+
Commercial — see [LICENSE](./LICENSE) for the full terms of the Updog SDK Commercial License v1.0. Contact `admin@updog.tech` for enterprise licensing. Third-party dependencies are listed in `THIRD_PARTY_NOTICES.txt`.
|