@updog/data-editor 0.1.4 → 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.
Files changed (3) hide show
  1. package/README.md +281 -107
  2. package/index.d.ts +303 -53
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,11 +1,13 @@
1
1
  # @updog/data-editor
2
2
 
3
- Enterprise-grade spreadsheet editor SDK for React. Designed for editing large datasets (100k+ rows) with multi-source data merging, file imports, validation, and undo/redo.
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 create an account and get a key. The editor will not render without one.
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 { DataEditor } from "@updog/data-editor";
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
- function App() {
23
- const [open, setOpen] = useState(false);
24
-
25
- const columns = [
26
- { id: "name", title: "Name", width: 150 },
27
- { id: "email", title: "Email", width: 260 },
28
- ];
26
+ type Employee = {
27
+ id: string;
28
+ name: string;
29
+ email: string;
30
+ role: string;
31
+ };
29
32
 
30
- const loadData = async (onChunk) => {
31
- const res = await fetch("/api/employees");
32
- onChunk(await res.json());
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
- const onComplete = (result) => {
36
- console.log(result.valid, result.invalid, result.deleted);
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-api-key"
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={loadData}
50
- onComplete={onComplete}
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-api-key"
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
- | `mode` | `"modal"` \| `"inline"` | No | `"modal"` | Rendering mode |
77
- | `apiKey` | `string` | Yes | — | License key |
78
- | `open` | `boolean` | Modal only | — | Controls modal visibility. Required in modal mode only |
79
- | `onClose` | `() => void` | Modal only | | Called when user initiates close. Required in modal mode only |
80
- | `columns` | `DataEditorColumn[]` | Yes | — | Column definitions |
81
- | `primaryKey` | `keyof TRow` | Yes | — | Unique row identifier column |
82
- | `loadData` | `(onChunk) => Promise<void>` | No | — | Async data loader with chunked delivery |
83
- | `onComplete` | `(result, actions) => void` | No | — | Called on submit with valid/invalid/deleted rows |
84
- | `variant` | `"editor"` \| `"uploader"` | No | `"editor"` | UI mode |
85
- | `translations` | `DataEditorTranslations` | No | — | i18n overrides |
86
- | `locale` | `string` | No | `"en"` | BCP 47 locale tag |
87
- | `enableDeleteRow` | `"all"` \| `"new"` \| `false` | No | `false` | Row deletion policy |
88
- | `enableAddRow` | `boolean` | No | `true` | Show "Add row" option |
89
- | `importFormats` | `DataEditorFormat[]` | No | all | Allowed import formats |
90
- | `exportFormats` | `DataEditorFormat[]` | No | all | Allowed export formats |
91
- | `rowHeight` | `number` | No | `34` | Row height in pixels |
92
- | `headerHeight` | `number` | No | `36` | Header height in pixels |
93
- | `readonly` | `boolean` | No | `false` | Hide write-oriented UI |
94
- | `rtl` | `boolean` | No | `false` | Right-to-left layout |
95
- | `className` | `string` | No | | CSS class for the modal |
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
- width: 260,
148
+ size: 260,
105
149
 
106
- // Validation single or array of validators
107
- validate: (value, row) => {
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
- // Uniqueness constraint
153
+ // Flag duplicates in this column as errors.
114
154
  unique: true,
115
155
 
116
- // Cross-field validation — revalidate these columns when this one changes
156
+ // Revalidate these columns when this one changes.
117
157
  dependentFields: ["confirmEmail"],
118
158
 
119
- // Cell editor: "text" (default), "date", or "select"
120
- editor: { type: "select", options: [{ id: "us", text: "United States" }] },
159
+ // Cell editor. Default is "text".
160
+ editor: { type: "select", options: ["US", "UK", "DE"] },
121
161
 
122
- // Visual formatting (display only, does not change data)
162
+ // Display-only formatting. Does not mutate stored data.
123
163
  formatter: (value) => value.toLowerCase(),
124
164
 
125
- // Data transformation on input
165
+ // Runs when rows are uploaded to the editor. Mutates the stored value.
126
166
  transformer: (value) => String(value).trim(),
127
167
 
128
- // Filter panel configuration
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 { useDataEditorValidators } from "@updog/data-editor";
138
-
139
- function App() {
140
- const { required, numeric, email, date, endDateAfterStart } = useDataEditorValidators();
141
-
142
- const columns = [
143
- { id: "email", title: "Email", validate: [required, email] },
144
- { id: "salary", title: "Salary", validate: numeric },
145
- { id: "startDate", title: "Start", validate: date, editor: { type: "date" } },
146
- {
147
- id: "endDate",
148
- title: "End",
149
- validate: [date, endDateAfterStart("startDate")],
150
- dependentFields: ["startDate"],
151
- editor: { type: "date" },
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
- Data is loaded in chunks to avoid blocking the UI:
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
- const loadData = async (onChunk) => {
163
- // Load from API in pages
237
+ // Single source
238
+ loadData={async (onChunk) => {
164
239
  for (let page = 0; page < totalPages; page++) {
165
- const res = await fetch(`/api/employees?page=${page}`);
166
- const rows = await res.json();
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
- ```tsx
175
- const onComplete = (result, actions) => {
176
- // result.valid — rows that pass all validation
177
- // result.invalid — rows with validation errors
178
- // result.deleted — rows marked for deletion
179
- // result.newCount, result.editedCount, result.deletedCount
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
- // actions.reset() clear all changes and reload
274
+ type DataEditorActions = {
275
+ reset: () => void; // discard changes and reload via loadData
182
276
  };
183
277
  ```
184
278
 
185
- ## Import/Export Formats
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
- Supported formats: `"csv"`, `"tsv"`, `"xlsx"`, `"json"`, `"xml"`.
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, use the Web Component package:
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) for details.
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 questions. Third-party dependencies bundled with this package are listed in `THIRD_PARTY_NOTICES.txt`.
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`.
package/index.d.ts CHANGED
@@ -818,11 +818,40 @@ declare class ServerEditBuilder<TRow extends DataEditorRow = DataEditorRow> {
818
818
  private viewContext;
819
819
  }
820
820
 
821
+ /**
822
+ * Categories of internal errors surfaced through the `onError` callback.
823
+ *
824
+ * - `PARSE_ERROR` — file parsing failed (corrupt CSV, invalid XLSX, etc.).
825
+ * - `RENDER_ERROR` — component render failed (caught by error boundary).
826
+ * - `TRANSFORM_ERROR` — data transformation failed (column transform, value mapping).
827
+ * - `VALIDATION_ERROR` — validation execution failed (validator threw instead of returning).
828
+ * - `WORKER_ERROR` — Web Worker failed (filter worker, chat transform worker).
829
+ * - `COMMAND_ERROR` — undo/redo command failed.
830
+ * - `OPERATION_ERROR` — general operation failed (bulk mutations, imports).
831
+ */
821
832
  type UpdogErrorCode = "PARSE_ERROR" | "RENDER_ERROR" | "TRANSFORM_ERROR" | "VALIDATION_ERROR" | "WORKER_ERROR" | "COMMAND_ERROR" | "OPERATION_ERROR";
833
+ /**
834
+ * An internal error caught by the SDK and passed to `onError`. The SDK
835
+ * recovers gracefully where possible — `onError` is for your logging and
836
+ * monitoring (Sentry, Datadog, etc.).
837
+ *
838
+ * @example
839
+ * ```ts
840
+ * onError={(error) => {
841
+ * Sentry.captureException(error.originalError ?? error, {
842
+ * tags: { code: error.code, source: error.source },
843
+ * });
844
+ * }}
845
+ * ```
846
+ */
822
847
  type UpdogError = {
848
+ /** The error category. */
823
849
  code: UpdogErrorCode;
850
+ /** Human-readable description. */
824
851
  message: string;
852
+ /** Module or subsystem that raised the error. */
825
853
  source: string;
854
+ /** The underlying thrown value, when available. */
826
855
  originalError?: unknown;
827
856
  };
828
857
 
@@ -1745,7 +1774,9 @@ type ColumnDelta = {
1745
1774
 
1746
1775
  /**
1747
1776
  * Base row shape. Each key is a column ID, each value is the cell data.
1748
- * Extend this with your own type for type-safe column access.
1777
+ * Extend this with your own type via the `<DataEditor<TRow>>` generic for
1778
+ * type-safe column access. When the generic is omitted, rows are typed as
1779
+ * `Record<string, unknown>`.
1749
1780
  *
1750
1781
  * @example
1751
1782
  * ```ts
@@ -1754,7 +1785,9 @@ type ColumnDelta = {
1754
1785
  * ```
1755
1786
  */
1756
1787
  type DataEditorRow = Record<string, unknown>;
1788
+ /** Sort direction. */
1757
1789
  type SortDirection = "asc" | "desc";
1790
+ /** Current sort state. `null` means no active sort. */
1758
1791
  type SortState = {
1759
1792
  columnId: string;
1760
1793
  direction: SortDirection;
@@ -1784,6 +1817,14 @@ type Filters = {
1784
1817
  }>;
1785
1818
  };
1786
1819
 
1820
+ /**
1821
+ * A single chunk in the stream returned from `DataEditorChat.onMessage`.
1822
+ *
1823
+ * - `status` — progress message shown while processing (e.g. "Analyzing 500 rows...").
1824
+ * - `message` — chat reply shown to the user.
1825
+ * - `rows` — updated rows to apply to the grid. Matched by `primaryKey`.
1826
+ * - `transform` — description of the transform applied.
1827
+ */
1787
1828
  type ChatResponseChunk<TRow extends DataEditorRow = DataEditorRow> = {
1788
1829
  type: "status";
1789
1830
  content: string;
@@ -1797,37 +1838,105 @@ type ChatResponseChunk<TRow extends DataEditorRow = DataEditorRow> = {
1797
1838
  type: "transform";
1798
1839
  content: string;
1799
1840
  };
1841
+ /** Status of a row in the chat sample, relative to its origin snapshot. */
1800
1842
  type ChatRowStatus = "new" | "edited" | "original";
1843
+ /** A sample row handed to the chat callback, with its current status and validation errors. */
1801
1844
  type ChatRow<TRow extends DataEditorRow = DataEditorRow> = {
1845
+ /** Row data keyed by column ID. */
1802
1846
  data: TRow;
1847
+ /** Whether the row was newly added, edited, or is unchanged. */
1803
1848
  status: ChatRowStatus;
1849
+ /** Validation errors keyed by column ID. */
1804
1850
  errors: Record<string, string[]>;
1851
+ /** The source this row belongs to. */
1805
1852
  source: string;
1806
1853
  };
1854
+ /** Aggregated error count across the current view, grouped by field and message. */
1807
1855
  type ChatErrorSummary = {
1856
+ /** Column ID where the error occurred. */
1808
1857
  field: string;
1858
+ /** The validation message. */
1809
1859
  message: string;
1860
+ /** How many rows hit this error. */
1810
1861
  count: number;
1862
+ /** A few example values that triggered the error. */
1811
1863
  examples: string[];
1812
1864
  };
1865
+ /**
1866
+ * Context about the current dataset, passed to `loadSuggestions` and extended
1867
+ * into `ChatContext` for `onMessage`.
1868
+ */
1813
1869
  type ChatDataContext<TRow extends DataEditorRow = DataEditorRow> = {
1870
+ /** Full column definitions. */
1814
1871
  columns: DataEditorColumn[];
1872
+ /** Row identifier field. */
1815
1873
  primaryKey: keyof TRow;
1874
+ /** Total rows in the dataset. */
1816
1875
  totalRowCount: number;
1876
+ /** Rows in the current filtered view. */
1817
1877
  filteredRowCount: number;
1878
+ /** Sample rows, with status and errors. Size controlled by `sampleSize`. */
1818
1879
  sample: ChatRow<TRow>[];
1880
+ /** Aggregated error counts by field and message. */
1819
1881
  errorSummary: ChatErrorSummary[];
1882
+ /** Access all rows. Use for full-dataset operations. */
1820
1883
  getRows: () => ChatRow<TRow>[];
1821
1884
  };
1885
+ /**
1886
+ * The full context passed to `DataEditorChat.onMessage` when the user sends
1887
+ * a prompt. Includes the prompt itself and all dataset context.
1888
+ */
1822
1889
  type ChatContext<TRow extends DataEditorRow = DataEditorRow> = ChatDataContext<TRow> & {
1890
+ /** The user's chat prompt. */
1823
1891
  message: string;
1824
1892
  };
1893
+ /**
1894
+ * Bring-your-own-AI chat configuration. When provided via the `chat` prop,
1895
+ * the editor shows a chat panel alongside the grid. You own the AI
1896
+ * integration; the SDK hands you dataset context and renders the streamed
1897
+ * response.
1898
+ *
1899
+ * @example
1900
+ * ```ts
1901
+ * chat={{
1902
+ * sampleSize: 50,
1903
+ * onMessage: async function* (context) {
1904
+ * yield { type: "status", content: "Thinking..." };
1905
+ * const res = await fetch("/api/ai", {
1906
+ * method: "POST",
1907
+ * body: JSON.stringify({
1908
+ * prompt: context.message,
1909
+ * columns: context.columns,
1910
+ * sample: context.sample.map(r => r.data),
1911
+ * errors: context.errorSummary,
1912
+ * }),
1913
+ * }).then(r => r.json());
1914
+ * yield { type: "rows", content: res.updatedRows };
1915
+ * yield { type: "transform", content: res.description };
1916
+ * yield { type: "message", content: res.reply };
1917
+ * },
1918
+ * }}
1919
+ * ```
1920
+ */
1825
1921
  type DataEditorChat<TRow extends DataEditorRow = DataEditorRow> = {
1922
+ /**
1923
+ * How many rows to include in the context sample. The SDK picks a
1924
+ * representative slice including rows with errors. When omitted, the SDK decides.
1925
+ */
1826
1926
  sampleSize?: number;
1927
+ /** Title shown above the suggestion list when the chat is empty. */
1827
1928
  emptyTitle?: string;
1929
+ /** How many suggestions to request from `loadSuggestions`. */
1828
1930
  suggestionsCount?: number;
1931
+ /** Optional prompt generator for the empty-state suggestion chips. */
1829
1932
  loadSuggestions?: (context: ChatDataContext<TRow>) => Promise<string[]>;
1933
+ /**
1934
+ * Called when the user sends a message. Receives full dataset context.
1935
+ * Returns an async iterable of response chunks — the SDK streams them
1936
+ * into the UI.
1937
+ */
1830
1938
  onMessage: (context: ChatContext<TRow>) => AsyncIterable<ChatResponseChunk<TRow>>;
1939
+ /** Called when the user cancels a pending request. Use to abort your API call. */
1831
1940
  onCancel?: () => void;
1832
1941
  };
1833
1942
 
@@ -1847,8 +1956,13 @@ type ValidationResult = ValidationError[] | null;
1847
1956
  * A function that validates a single cell value.
1848
1957
  * Return a `ValidationError` to flag a problem, or `null` if the value is valid.
1849
1958
  *
1959
+ * A `ValidationError` with `level: "error"` flags the cell in the grid but
1960
+ * does not block submission — invalid rows are delivered to `onComplete`
1961
+ * alongside valid ones, tagged via the `isValid` flag.
1962
+ *
1850
1963
  * Built-in validator factories: `required(msg)`, `numeric(msg)`, `email(msg)`,
1851
- * `date(msg)`, `endDateAfterStart(startField, msg)`. Import them from the package root.
1964
+ * `date(msg)`, `oneOf(values, msg)`, `endDateAfterStart(startField, msg)`.
1965
+ * Import them from the package root.
1852
1966
  *
1853
1967
  * @param value - The current cell value.
1854
1968
  * @param row - The full row, useful for cross-field checks.
@@ -1932,41 +2046,62 @@ type ColumnLockMode = "all" | "default";
1932
2046
  *
1933
2047
  * @example
1934
2048
  * ```ts
2049
+ * import { required, email, numeric } from "@updog/data-editor";
2050
+ *
1935
2051
  * const columns: DataEditorColumn[] = [
1936
- * { id: "name", title: "Full Name", width: 200, validate: required },
1937
- * { id: "email", title: "Email", width: 250, validate: [required, email], unique: true },
1938
- * { id: "role", title: "Role", editor: { type: "select", options: roleOptions } },
1939
- * { id: "salary", title: "Salary", validate: numeric, formatter: (v) => `$${v}` },
2052
+ * { id: "name", title: "Full Name", size: 200, validate: required("Name is required") },
2053
+ * { id: "email", title: "Email", size: 250, validate: [required("Email is required"), email("Invalid email")], unique: true },
2054
+ * { id: "role", title: "Role", editor: { type: "select", options: ["Admin", "Editor", "Viewer"] } },
2055
+ * { id: "salary", title: "Salary", validate: numeric("Must be a number"), formatter: (v) => v ? `$${v}` : "" },
1940
2056
  * ];
1941
2057
  * ```
1942
2058
  */
1943
2059
  type DataEditorColumn = {
1944
2060
  /** Unique column identifier. Must match the keys in your row data. */
1945
2061
  id: string;
1946
- /** Column header text. */
2062
+ /** Column header text shown to the user. */
1947
2063
  title: string;
1948
- /** One or more validators to run on every edit. */
2064
+ /**
2065
+ * One or more validators run on every edit. Each receives the cell value and
2066
+ * the full row, and returns a `ValidationError` to flag a problem or `null`
2067
+ * if valid. Errors do not block submission — see `CellValidator`.
2068
+ */
1949
2069
  validate?: CellValidator | CellValidator[];
1950
- /** When `true`, the editor flags duplicate values in this column as errors. */
2070
+ /**
2071
+ * When `true`, the editor flags duplicate values in this column as errors.
2072
+ * The error message is localized via the `translations` prop
2073
+ * (`dataEditor.validation.valueMustBeUnique`).
2074
+ */
1951
2075
  unique?: boolean;
1952
- /** Column IDs to revalidate when this column changes. Use for cross-field rules like "end date must be after start date". */
2076
+ /**
2077
+ * Column IDs to revalidate when this column changes. Use for cross-field
2078
+ * rules like "end date must be after start date".
2079
+ */
1953
2080
  dependentFields?: string[];
1954
2081
  /** Format the display value without changing stored data. E.g. add `$` prefix. */
1955
2082
  formatter?: (value: string) => string;
1956
- /** Transform the value before it enters the store. Runs on every edit and import. */
2083
+ /**
2084
+ * Transform a value before it enters the store. Runs when rows are uploaded
2085
+ * to the data editor.
2086
+ */
1957
2087
  transformer?: (value: unknown) => unknown;
1958
2088
  /** How the cell is edited. Defaults to text input. */
1959
2089
  editor?: CellEditor;
1960
2090
  /** Adds a filter control for this column in the sidebar Filters panel. */
1961
2091
  filter?: ColumnFilter;
1962
- /** Whether this column can be pinned to the left via the header context menu. @default true */
2092
+ /**
2093
+ * Whether this column can be pinned to the left (right in RTL) via the
2094
+ * header context menu. @default true
2095
+ */
1963
2096
  pinnable?: boolean;
1964
2097
  /** Column width in pixels. @default 150 */
1965
2098
  size?: number;
1966
2099
  /**
1967
- * Controls whether cells in this column are locked.
1968
- * - `true` | `"all"` locked for all rows.
1969
- * - `"default"` — locked only for default-source rows; non-default-source rows remain editable.
2100
+ * Controls whether cells in this column are locked. A column locked via
2101
+ * configuration cannot be unlocked from the UI.
2102
+ * - `true` | `"all"` — locked for every row.
2103
+ * - `"default"` — locked only for default-source rows; rows added manually,
2104
+ * duplicated, or imported remain editable.
1970
2105
  * - `false` | `undefined` — not locked.
1971
2106
  */
1972
2107
  locked?: boolean | ColumnLockMode;
@@ -2386,9 +2521,12 @@ type UndoRedoResult = {
2386
2521
  success: boolean;
2387
2522
  targetCell?: CellLocation;
2388
2523
  };
2389
- /** Actions available inside the `onComplete` callback. */
2524
+ /**
2525
+ * Actions available inside the `onComplete` callback. Call `reset()` after a
2526
+ * successful save to clear the editor and re-fetch via `loadData`.
2527
+ */
2390
2528
  type DataEditorActions = {
2391
- /** Discard all changes and reload the original data. */
2529
+ /** Discard all changes and reload the original data via `loadData`. */
2392
2530
  reset: () => void;
2393
2531
  };
2394
2532
  /**
@@ -2448,14 +2586,24 @@ type DataEditorResult<TRow extends DataEditorRow = DataEditorRow> = {
2448
2586
  invalid: number;
2449
2587
  };
2450
2588
  };
2589
+ /**
2590
+ * Options used to tag a chunk passed to `loadData`'s `onChunk` callback.
2591
+ * Sources are auto-registered on first encounter. Chunks without options go
2592
+ * to "Existing Data".
2593
+ */
2451
2594
  type ChunkSourceOptions = {
2452
- /** Display name for this data source. */
2595
+ /** Display name for this data source. Required when tagging a source. */
2453
2596
  source: string;
2454
- /** Stable identifier. Defaults to `source` value when omitted. */
2597
+ /** Stable identifier. Defaults to the `source` value when omitted. */
2455
2598
  id?: string;
2456
- /** Can the user delete this source? @default false */
2599
+ /** Whether the user can delete this source from the editor. @default false */
2457
2600
  deletable?: boolean;
2458
- /** Marks this source as finished loading. */
2601
+ /**
2602
+ * Marks this source as finished loading. Shows a completion state in the
2603
+ * UI. When the `loadData` promise resolves, any source still loading is
2604
+ * automatically finalized.
2605
+ * @default false
2606
+ */
2459
2607
  done?: boolean;
2460
2608
  };
2461
2609
  type DataSourceId = string;
@@ -2509,20 +2657,34 @@ type DataStoreSnapshot = {
2509
2657
  /** True when the "show deleted rows" filter is active (bin mode). */
2510
2658
  showOnlyDeletedRows: boolean;
2511
2659
  };
2512
- /** Per-column data passed to `onValueMatch`: the unique imported values and the allowed select options. */
2660
+ /**
2661
+ * Per-column input to `onValueMatch`: the unique imported values from the
2662
+ * file and the allowed select options defined on the column.
2663
+ */
2513
2664
  type ValueMatchInput = {
2665
+ /** Distinct values seen in the imported file for this column. */
2514
2666
  importedValues: string[];
2667
+ /** Allowed options from the column's `select` editor. */
2515
2668
  options: string[];
2516
2669
  };
2517
- /** Per-column result from `onValueMatch`: maps each imported value to an option value, or `null` to skip auto-matching. */
2670
+ /**
2671
+ * Per-column result from `onValueMatch`. Outer key = column ID, inner key =
2672
+ * imported value, inner value = chosen option, or `null` to skip
2673
+ * auto-matching for that value. Unmapped values fall back to built-in fuzzy
2674
+ * matching.
2675
+ */
2518
2676
  type ValueMatchOutput = Record<string, Record<string, string | null>>;
2519
- /** File formats supported for import and export. */
2677
+ /** File formats supported for import and export. Shared by `importFormats` and `exportFormats`. */
2520
2678
  type DataEditorFormat = "csv" | "tsv" | "xlsx" | "json" | "xml";
2521
2679
  /**
2522
2680
  * A client-defined remote data source (e.g. Google Sheets, S3, Dropbox).
2523
2681
  *
2524
- * The SDK renders a button per source and calls `fetch()` when clicked.
2525
- * The client owns all integration complexity — auth, pickers, downloads.
2682
+ * The SDK renders a button per source on the upload step and calls `fetch()`
2683
+ * when clicked. You own all integration complexity — auth, pickers,
2684
+ * downloads. The SDK just processes the result.
2685
+ *
2686
+ * Return a `File` to go through the standard parse pipeline
2687
+ * (CSV/XLSX/JSON/XML), or return structured records to skip parsing entirely.
2526
2688
  *
2527
2689
  * @example
2528
2690
  * ```ts
@@ -2546,7 +2708,10 @@ type RemoteSource = {
2546
2708
  icon: string;
2547
2709
  /** Optional tooltip text. */
2548
2710
  description?: string;
2549
- /** Returns a File (parsed via CSV/XLSX/JSON/XML pipeline) or structured records (used directly). */
2711
+ /**
2712
+ * Returns a `File` (parsed via the CSV/XLSX/JSON/XML pipeline) or structured
2713
+ * records (used directly, skipping parse).
2714
+ */
2550
2715
  fetch: () => Promise<File | Record<string, unknown>[]>;
2551
2716
  };
2552
2717
  /**
@@ -2561,8 +2726,9 @@ type DataEditorBaseProps<TRow extends DataEditorRow = DataEditorRow> = {
2561
2726
  /** Column definitions. Each entry describes one column in the grid. */
2562
2727
  columns: DataEditorColumn[];
2563
2728
  /**
2564
- * The column ID used to uniquely identify each row (e.g. `"id"` or `"employeeId"`).
2565
- * Used to match imported rows to existing data and to track edits.
2729
+ * The column ID used to uniquely identify each row (e.g. `"id"` or
2730
+ * `"employeeId"`). Used to match imported rows against existing data and to
2731
+ * track edits.
2566
2732
  */
2567
2733
  primaryKey: keyof TRow;
2568
2734
  /**
@@ -2616,37 +2782,78 @@ type DataEditorBaseProps<TRow extends DataEditorRow = DataEditorRow> = {
2616
2782
  * ```
2617
2783
  */
2618
2784
  onComplete?: (result: DataEditorResult<TRow>, actions: DataEditorActions) => void;
2619
- /** Controls the initial view. `"editor"` opens the grid, `"uploader"` opens the import wizard first. @default "editor" */
2785
+ /**
2786
+ * Controls the initial view. `"editor"` opens directly to the spreadsheet
2787
+ * grid. `"uploader"` opens the file import wizard first — the user uploads
2788
+ * a file, maps columns, fixes errors, then continues to the grid.
2789
+ * @default "editor"
2790
+ */
2620
2791
  variant?: DataEditorVariant;
2621
- /** Override any UI string. Pass a partial object — only the keys you provide are replaced. */
2792
+ /**
2793
+ * Override any UI string. Pass a partial object — only the keys you provide
2794
+ * are replaced. Every key lives under `dataEditor`.
2795
+ */
2622
2796
  translations?: DataEditorTranslations;
2623
- /** BCP 47 locale tag (e.g. `"en"`, `"fr"`, `"ar"`). Used for plural rules and locale-aware features. @default "en" */
2797
+ /**
2798
+ * BCP 47 locale tag (e.g. `"en"`, `"fr"`, `"ar"`). Used for plural rules
2799
+ * and locale-aware features.
2800
+ * @default "en"
2801
+ */
2624
2802
  locale?: string;
2625
2803
  /**
2626
2804
  * Controls row deletion via the right-click context menu.
2627
- * - `false` — deletion disabled (default).
2805
+ * - `false` — deletion disabled.
2628
2806
  * - `"new"` — only manually added or imported rows can be deleted.
2629
2807
  * - `"all"` — any row can be deleted.
2630
2808
  * @default false
2631
2809
  */
2632
2810
  enableDeleteRow?: "all" | "new" | false;
2633
- /** Show the "Add row" button in the data sources panel. @default true */
2811
+ /**
2812
+ * Show the "Add row" button in the data sources panel. When `false`, users
2813
+ * can only edit existing rows or import data — they can't manually add
2814
+ * blank rows.
2815
+ * @default true
2816
+ */
2634
2817
  enableAddRow?: boolean;
2635
- /** Which file formats the user can import. `undefined` allows all, `[]` disables import entirely. */
2818
+ /**
2819
+ * Which file formats the user can import. `undefined` allows all formats,
2820
+ * `[]` disables import entirely.
2821
+ */
2636
2822
  importFormats?: DataEditorFormat[];
2637
- /** Client-defined remote data sources rendered as buttons on the upload step. */
2823
+ /**
2824
+ * Client-defined remote data sources rendered as buttons on the upload
2825
+ * step. You own the integration; the SDK renders the button and processes
2826
+ * the result of `fetch()`.
2827
+ */
2638
2828
  remoteSources?: RemoteSource[];
2639
- /** Which file formats the user can export. `undefined` allows all, `[]` disables export entirely. */
2829
+ /**
2830
+ * Which file formats the user can export to. `undefined` allows all
2831
+ * formats, `[]` disables export entirely.
2832
+ */
2640
2833
  exportFormats?: DataEditorFormat[];
2641
2834
  /** Row height in pixels. @default 34 */
2642
2835
  rowHeight?: number;
2643
2836
  /** Header row height in pixels. @default 36 */
2644
2837
  headerHeight?: number;
2645
- /** When `true`, hides all editing UI (submit, add row, delete, import). The grid becomes view-only. */
2838
+ /**
2839
+ * When `true`, hides all editing UI (submit, add row, delete, import). The
2840
+ * grid becomes view-only.
2841
+ * @default false
2842
+ */
2646
2843
  readonly?: boolean;
2647
- /** Enable right-to-left layout for RTL languages. @default false */
2844
+ /**
2845
+ * Enable right-to-left layout. Set to `true` for Arabic, Hebrew, and other
2846
+ * RTL languages. Affects grid direction, text alignment, and scrollbar
2847
+ * position.
2848
+ * @default false
2849
+ */
2648
2850
  rtl?: boolean;
2649
- /** Allow creating new columns for unmatched CSV headers during import. @default true */
2851
+ /**
2852
+ * Allow creating new columns for unmatched CSV headers during import. When
2853
+ * enabled, users can keep data from columns that don't match your schema by
2854
+ * creating dynamic columns on the fly.
2855
+ * @default true
2856
+ */
2650
2857
  enableCreateColumn?: boolean;
2651
2858
  /** Override column matching during import. Return a map of `{ csvHeader: columnId | null }`. Unmapped or `null` entries fall back to built-in matching. */
2652
2859
  onColumnMatch?: (headers: string[], columns: DataEditorColumn[]) => Record<string, string | null> | Promise<Record<string, string | null>>;
@@ -2667,32 +2874,68 @@ type DataEditorBaseProps<TRow extends DataEditorRow = DataEditorRow> = {
2667
2874
  * ```
2668
2875
  */
2669
2876
  onValueMatch?: (valuesToMatch: Record<string, ValueMatchInput>) => ValueMatchOutput | Promise<ValueMatchOutput>;
2670
- /** Extra synonyms for column auto-matching, e.g. `{ productsku: ["sku", "articleno"] }`. */
2877
+ /**
2878
+ * Extra synonyms for column auto-matching. Keys are column IDs, values are
2879
+ * alternative names the matching engine should recognize.
2880
+ *
2881
+ * @example
2882
+ * ```ts
2883
+ * synonyms={{
2884
+ * productSku: ["sku", "article_no", "item_code"],
2885
+ * firstName: ["first", "given_name", "fname"],
2886
+ * }}
2887
+ * ```
2888
+ */
2671
2889
  synonyms?: Record<string, string[]>;
2672
- /** Sample rows included in the "Download Example" file. When omitted, a generic row is auto-generated from column config. */
2890
+ /**
2891
+ * Sample rows included in the "Download Example" file. When the user clicks
2892
+ * "Download Example" in the import wizard, the SDK generates a template
2893
+ * file with column headers and these rows. When omitted, a generic example
2894
+ * row is auto-generated from column definitions.
2895
+ */
2673
2896
  sampleData?: Record<string, unknown>[];
2674
- /** Bring Your Own AI chat. When provided, a chat panel is shown alongside the grid. */
2897
+ /**
2898
+ * Bring-your-own-AI chat. When provided, a chat panel is shown alongside
2899
+ * the grid. You own the AI integration; the SDK hands you dataset context
2900
+ * and renders the streamed response.
2901
+ */
2675
2902
  chat?: DataEditorChat<TRow>;
2676
2903
  };
2677
2904
  /**
2678
- * Controls what the editor stores in `localStorage`.
2679
- * Set to `false` to disable all local storage usage.
2905
+ * Controls what the editor stores in `localStorage`. Set to `false` to
2906
+ * disable all local storage usage.
2907
+ *
2908
+ * @default { licenseGrant: true }
2680
2909
  */
2681
2910
  type DataEditorLocalStorage = false | {
2682
- /** Cache the license validation result to skip re-validation on reload. @default true */
2911
+ /**
2912
+ * Cache the license validation result to skip re-validation on reload.
2913
+ * @default true
2914
+ */
2683
2915
  licenseGrant?: boolean;
2684
2916
  };
2685
- /** Rendering mode: `"modal"` wraps in a dialog overlay, `"inline"` renders directly in the DOM. */
2917
+ /**
2918
+ * Rendering mode. `"modal"` wraps the editor in a full-screen dialog
2919
+ * overlay. `"inline"` renders it directly in the DOM.
2920
+ */
2686
2921
  type DataEditorMode = "modal" | "inline";
2687
2922
  /** Shared props present in both modal and inline modes. */
2688
2923
  type DataEditorCommonProps<TRow extends DataEditorRow = DataEditorRow> = DataEditorBaseProps<TRow> & {
2689
- /** Your Updog license key. Validates on each open. */
2924
+ /** Your Updog license key. Validated on each open. */
2690
2925
  apiKey: string;
2691
- /** Controls what the editor stores in `localStorage`. Set to `false` to disable. */
2926
+ /**
2927
+ * Controls what the editor stores in `localStorage`. Set to `false` to
2928
+ * disable all local storage usage.
2929
+ * @default { licenseGrant: true }
2930
+ */
2692
2931
  localStorage?: DataEditorLocalStorage;
2693
- /** CSS class added to the wrapper element. */
2932
+ /** CSS class added to the wrapper element. Use for scoped styling overrides. */
2694
2933
  className?: string;
2695
- /** Called when the SDK catches an internal error. Use for logging, Sentry, etc. Client mode only. */
2934
+ /**
2935
+ * Called when the SDK catches an internal error. The SDK recovers
2936
+ * gracefully where possible — use this for your logging and monitoring
2937
+ * (Sentry, Datadog, etc.). Client mode only.
2938
+ */
2696
2939
  onError?: (error: UpdogError) => void;
2697
2940
  };
2698
2941
  /**
@@ -2702,9 +2945,16 @@ type DataEditorCommonProps<TRow extends DataEditorRow = DataEditorRow> = DataEdi
2702
2945
  type DataEditorModalProps<TRow extends DataEditorRow = DataEditorRow> = DataEditorCommonProps<TRow> & {
2703
2946
  /** Rendering mode. @default "modal" */
2704
2947
  mode?: "modal";
2705
- /** When `true`, the editor modal is visible. This is a controlled prop. Required in modal mode. */
2948
+ /**
2949
+ * Controls modal visibility. This is a controlled prop — you manage the
2950
+ * state. Required in modal mode.
2951
+ */
2706
2952
  open: boolean;
2707
- /** Called when the user closes the modal (clicks X or presses Escape). Required in modal mode. */
2953
+ /**
2954
+ * Called when the user closes the modal (X button or Escape key). If the
2955
+ * user has unsaved changes, the SDK shows a confirmation dialog before
2956
+ * calling `onClose`. Required in modal mode.
2957
+ */
2708
2958
  onClose: () => void;
2709
2959
  };
2710
2960
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@updog/data-editor",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Enterprise-grade spreadsheet editor SDK for React. Edit large datasets with validation, undo/redo, CSV/XLSX import, and multi-source merging.",
5
5
  "author": "Mikhail Kutateladze <admin@updog.tech>",
6
6
  "homepage": "https://updog.tech",