@updog/data-editor-wc 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 +364 -128
- package/index.css +1 -1
- package/index.d.ts +299 -52
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# @updog/data-editor-wc
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Client-side data importer and spreadsheet editor SDK, shipped as a Web Component. 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
|
+
Framework-agnostic — works with vanilla JS, Angular, Vue, Svelte, or any framework that renders HTML.
|
|
6
|
+
|
|
7
|
+
> **Using React?** Use the first-class React SDK instead — [`@updog/data-editor`](https://www.npmjs.com/package/@updog/data-editor) — for typed props, hooks-friendly integration, and a smaller bundle (shares your app's React).
|
|
4
8
|
|
|
5
9
|
## Requirements
|
|
6
10
|
|
|
7
|
-
- An API key — this is a **commercial SDK**. Sign up at [updog.tech](https://updog.tech) to get one. The editor will not render without it.
|
|
8
11
|
- Any modern evergreen browser (Chrome, Firefox, Safari, Edge).
|
|
12
|
+
- An API key — this is a **commercial SDK**. Sign up at [updog.tech](https://updog.tech) to get one.
|
|
9
13
|
|
|
10
14
|
## Installation
|
|
11
15
|
|
|
@@ -13,205 +17,427 @@ Web Component wrapper for the Updog Data Editor. Framework-agnostic — works wi
|
|
|
13
17
|
npm install @updog/data-editor-wc
|
|
14
18
|
```
|
|
15
19
|
|
|
16
|
-
##
|
|
20
|
+
## Register
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
import "@updog/data-editor-wc";
|
|
24
|
+
import "@updog/data-editor-wc/styles.css";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Defines `<updog-editor>` globally. Import once, typically at your app entry.
|
|
28
|
+
|
|
29
|
+
How you wire the element into a framework (Angular's `CUSTOM_ELEMENTS_SCHEMA`, Vue's `compilerOptions.isCustomElement`, Svelte's `svelte:element`, etc.) depends on your stack. Once registered, the rest of this guide is the same.
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
17
32
|
|
|
18
33
|
```html
|
|
34
|
+
<button id="open-btn">Open Editor</button>
|
|
35
|
+
<updog-editor id="editor"></updog-editor>
|
|
36
|
+
|
|
19
37
|
<script type="module">
|
|
20
38
|
import "@updog/data-editor-wc";
|
|
21
39
|
import "@updog/data-editor-wc/styles.css";
|
|
40
|
+
import { required, email } from "@updog/data-editor-wc";
|
|
41
|
+
|
|
42
|
+
const editor = document.getElementById("editor");
|
|
43
|
+
|
|
44
|
+
editor.configure({
|
|
45
|
+
apiKey: "your-license-key",
|
|
46
|
+
primaryKey: "id",
|
|
47
|
+
columns: [
|
|
48
|
+
{ id: "name", title: "Full Name", size: 200, validate: required("Name is required") },
|
|
49
|
+
{ id: "email", title: "Email", size: 250, validate: [required("Email is required"), email("Invalid email")] },
|
|
50
|
+
{ id: "role", title: "Role", editor: { type: "select", options: ["Admin", "Editor", "Viewer"] } },
|
|
51
|
+
],
|
|
52
|
+
loadData: async (onChunk) => {
|
|
53
|
+
const res = await fetch("/api/employees");
|
|
54
|
+
onChunk(await res.json());
|
|
55
|
+
},
|
|
56
|
+
onComplete: async (result, actions) => {
|
|
57
|
+
for (const source of result.sources) {
|
|
58
|
+
const inserts = source.rows.filter((r) => r.isNew && !r.isDeleted && r.isValid);
|
|
59
|
+
const updates = source.rows.filter((r) => !r.isNew && r.isChanged && !r.isDeleted && r.isValid);
|
|
60
|
+
const deletes = source.rows.filter((r) => r.isDeleted && !r.isNew);
|
|
61
|
+
await persist(source.sourceId, { inserts, updates, deletes });
|
|
62
|
+
}
|
|
63
|
+
actions.reset();
|
|
64
|
+
editor.hide();
|
|
65
|
+
},
|
|
66
|
+
});
|
|
22
67
|
|
|
23
|
-
|
|
68
|
+
document.getElementById("open-btn").addEventListener("click", () => editor.show());
|
|
69
|
+
editor.addEventListener("close", () => editor.hide());
|
|
70
|
+
</script>
|
|
71
|
+
```
|
|
24
72
|
|
|
25
|
-
|
|
26
|
-
{ id: "name", title: "Name", width: 150 },
|
|
27
|
-
{ id: "email", title: "Email", width: 260 },
|
|
28
|
-
];
|
|
29
|
-
editor.primaryKey = "id";
|
|
30
|
-
editor.apiKey = "your-api-key";
|
|
73
|
+
## Setting Props
|
|
31
74
|
|
|
32
|
-
|
|
33
|
-
const res = await fetch("/api/data");
|
|
34
|
-
onChunk(await res.json());
|
|
35
|
-
};
|
|
75
|
+
Two ways to configure the element: HTML attributes for primitives, JS properties for everything else.
|
|
36
76
|
|
|
37
|
-
|
|
38
|
-
console.log("Submitted:", result);
|
|
39
|
-
editor.hide();
|
|
40
|
-
};
|
|
77
|
+
### HTML attributes
|
|
41
78
|
|
|
42
|
-
|
|
43
|
-
document.getElementById("open-btn").addEventListener("click", () => editor.show());
|
|
44
|
-
</script>
|
|
79
|
+
Use for primitive values. The element observes these and re-renders on change.
|
|
45
80
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
81
|
+
| Attribute | Maps to | Type |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `api-key` | `apiKey` | string |
|
|
84
|
+
| `primary-key` | `primaryKey` | string |
|
|
85
|
+
| `locale` | `locale` | string |
|
|
86
|
+
| `variant` | `variant` | `"editor"` \| `"uploader"` |
|
|
87
|
+
| `mode` | `mode` | `"modal"` \| `"inline"` |
|
|
88
|
+
| `open` | `open` | boolean (presence = true) |
|
|
89
|
+
| `rtl` | `rtl` | boolean (presence = true) |
|
|
90
|
+
| `readonly` | `readonly` | boolean (presence = true) |
|
|
49
91
|
|
|
50
|
-
|
|
92
|
+
```html
|
|
93
|
+
<updog-editor api-key="your-key" primary-key="id" variant="uploader" readonly></updog-editor>
|
|
94
|
+
```
|
|
51
95
|
|
|
52
|
-
|
|
96
|
+
### JS properties
|
|
53
97
|
|
|
54
|
-
|
|
55
|
-
|---|---|---|
|
|
56
|
-
| `api-key` | string | License key |
|
|
57
|
-
| `primary-key` | string | Row identifier column |
|
|
58
|
-
| `locale` | string | BCP 47 locale tag (default: `"en"`) |
|
|
59
|
-
| `variant` | `"editor"` \| `"uploader"` | UI mode |
|
|
60
|
-
| `mode` | `"modal"` \| `"inline"` | Rendering mode (default: `"modal"`) |
|
|
61
|
-
| `open` | boolean (presence) | Whether the editor modal is open (modal mode only) |
|
|
62
|
-
| `rtl` | boolean (presence) | Right-to-left layout |
|
|
63
|
-
| `readonly` | boolean (presence) | Hide write-oriented UI |
|
|
98
|
+
Everything else — objects, functions, arrays — must be set as a JS property or via `configure()`.
|
|
64
99
|
|
|
65
|
-
|
|
100
|
+
```js
|
|
101
|
+
const editor = document.querySelector("updog-editor");
|
|
102
|
+
editor.columns = [...];
|
|
103
|
+
editor.loadData = async (onChunk) => { ... };
|
|
104
|
+
editor.onComplete = (result, actions) => { ... };
|
|
105
|
+
```
|
|
66
106
|
|
|
67
|
-
|
|
107
|
+
### `configure(props)`
|
|
68
108
|
|
|
69
|
-
|
|
70
|
-
|---|---|
|
|
71
|
-
| `columns` | `DataEditorColumn[]` |
|
|
72
|
-
| `loadData` | `(onChunk: (rows) => void) => Promise<void>` |
|
|
73
|
-
| `onComplete` | `(result, actions) => void` |
|
|
74
|
-
| `translations` | `DataEditorTranslations` |
|
|
75
|
-
| `importFormats` | `("csv" \| "tsv" \| "xlsx" \| "json" \| "xml")[]` |
|
|
76
|
-
| `exportFormats` | `("csv" \| "tsv" \| "xlsx" \| "json" \| "xml")[]` |
|
|
77
|
-
| `enableDeleteRow` | `"all"` \| `"new"` \| `false` |
|
|
78
|
-
| `enableAddRow` | `boolean` |
|
|
79
|
-
| `localStorage` | `false \| { licenseGrant?: boolean }` |
|
|
80
|
-
| `rowHeight` | `number` (default: 34) |
|
|
81
|
-
| `headerHeight` | `number` (default: 36) |
|
|
82
|
-
|
|
83
|
-
All properties can also be set in bulk via `configure()`:
|
|
109
|
+
Set multiple properties at once. Useful during setup. Accepts every prop except `onClose`. Property updates are batched — the editor re-renders once per microtask regardless of how many properties you set.
|
|
84
110
|
|
|
85
111
|
```js
|
|
86
112
|
editor.configure({
|
|
87
|
-
|
|
113
|
+
apiKey: "your-key",
|
|
88
114
|
primaryKey: "id",
|
|
89
|
-
|
|
115
|
+
columns: [...],
|
|
116
|
+
loadData: async (onChunk) => onChunk(await fetchRows()),
|
|
117
|
+
onComplete: (result, actions) => { save(result); actions.reset(); },
|
|
90
118
|
});
|
|
91
119
|
```
|
|
92
120
|
|
|
121
|
+
## Inline Mode
|
|
122
|
+
|
|
123
|
+
Render the editor directly in the DOM without a modal overlay:
|
|
124
|
+
|
|
125
|
+
```html
|
|
126
|
+
<updog-editor mode="inline" api-key="your-key"></updog-editor>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
In inline mode, `show()` / `hide()` and the `open` attribute don't apply. The `close` event is not fired.
|
|
130
|
+
|
|
131
|
+
## Props
|
|
132
|
+
|
|
133
|
+
| Prop | Type | Required | Default | Description |
|
|
134
|
+
|---|---|---|---|---|
|
|
135
|
+
| `apiKey` | `string` | Yes | — | Your Updog license key. Validated on each open. |
|
|
136
|
+
| `columns` | `DataEditorColumn[]` | Yes | — | Column definitions. |
|
|
137
|
+
| `primaryKey` | `string` | Yes | — | Column ID that uniquely identifies each row. |
|
|
138
|
+
| `mode` | `"modal"` \| `"inline"` | No | `"modal"` | Rendering mode. |
|
|
139
|
+
| `open` | `boolean` | Modal only | — | Controlled modal visibility. Equivalent to `show()` / `hide()`. |
|
|
140
|
+
| `loadData` | `(onChunk) => Promise<void>` | No | — | Async data loader. Stream rows in chunks, optionally tagged by source. |
|
|
141
|
+
| `onComplete` | `(result, actions) => void` | No | — | Called on submit. See [Submission Result](#submission-result). |
|
|
142
|
+
| `variant` | `"editor"` \| `"uploader"` | No | `"editor"` | Initial view. `"uploader"` opens the import wizard first. |
|
|
143
|
+
| `translations` | `DataEditorTranslations` | No | — | Partial i18n overrides. |
|
|
144
|
+
| `locale` | `string` | No | `"en"` | BCP 47 locale tag. |
|
|
145
|
+
| `rtl` | `boolean` | No | `false` | Right-to-left layout. |
|
|
146
|
+
| `readonly` | `boolean` | No | `false` | Hide all editing UI. |
|
|
147
|
+
| `enableDeleteRow` | `"all"` \| `"new"` \| `false` | No | `false` | Row deletion policy. |
|
|
148
|
+
| `enableAddRow` | `boolean` | No | `true` | Show the "Add row" button. |
|
|
149
|
+
| `enableCreateColumn` | `boolean` | No | `true` | Allow creating columns for unmatched CSV headers during import. |
|
|
150
|
+
| `importFormats` | `DataEditorFormat[]` | No | all | Allowed import formats. `[]` disables import. |
|
|
151
|
+
| `exportFormats` | `DataEditorFormat[]` | No | all | Allowed export formats. `[]` disables export. |
|
|
152
|
+
| `remoteSources` | `RemoteSource[]` | No | — | Custom import buttons (Google Sheets, S3, etc.) rendered on the upload step. |
|
|
153
|
+
| `rowHeight` | `number` | No | `34` | Row height in pixels. |
|
|
154
|
+
| `headerHeight` | `number` | No | `36` | Header height in pixels. |
|
|
155
|
+
| `server` | `DataEditorServer` | No | — | Server-delegated mode: SDK renders, your backend handles queries and mutations. |
|
|
156
|
+
| `chat` | `DataEditorChat` | No | — | Bring-your-own AI chat panel. |
|
|
157
|
+
| `onColumnMatch` | `(headers, columns) => ...` | No | — | Override import column matching. |
|
|
158
|
+
| `onValueMatch` | `(valuesToMatch) => ...` | No | — | Override import value matching for `select` columns. |
|
|
159
|
+
| `synonyms` | `Record<string, string[]>` | No | — | Extra synonyms for column auto-matching. |
|
|
160
|
+
| `sampleData` | `Record<string, unknown>[]` | No | — | Rows used in the "Download Example" file. |
|
|
161
|
+
| `localStorage` | `false` \| `{ licenseGrant?: boolean }` | No | `{ licenseGrant: true }` | What the SDK caches in `localStorage`. |
|
|
162
|
+
| `onError` | `(error: UpdogError) => void` | No | — | Called on internal errors. Use for logging or Sentry. |
|
|
163
|
+
| `className` | `string` | No | — | CSS class on the wrapper element. |
|
|
164
|
+
|
|
93
165
|
## Methods
|
|
94
166
|
|
|
95
167
|
| Method | Description |
|
|
96
168
|
|---|---|
|
|
97
|
-
| `show()` | Opens the editor modal |
|
|
98
|
-
| `hide()` | Closes the editor modal |
|
|
99
|
-
| `configure(props)` | Bulk-set any props |
|
|
169
|
+
| `show()` | Opens the editor modal. Equivalent to setting `open` to `true`. No-op in `mode="inline"`. |
|
|
170
|
+
| `hide()` | Closes the editor modal. Equivalent to setting `open` to `false`. No-op in `mode="inline"`. |
|
|
171
|
+
| `configure(props)` | Bulk-set any props. See [`configure(props)`](#configureprops). |
|
|
100
172
|
|
|
101
173
|
## Events
|
|
102
174
|
|
|
103
|
-
|
|
104
|
-
|---|---|
|
|
105
|
-
| `close` | Fired when the user closes the editor from inside (X button, discard). Call `editor.hide()` in response. |
|
|
175
|
+
### `close`
|
|
106
176
|
|
|
107
|
-
|
|
177
|
+
Fires when the user closes the modal (X button or Escape key). Replaces the React `onClose` prop — the WC dispatches a bubbling `CustomEvent` instead. Not fired in `mode="inline"`.
|
|
108
178
|
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
|
|
179
|
+
```js
|
|
180
|
+
editor.addEventListener("close", () => editor.hide());
|
|
181
|
+
```
|
|
112
182
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
183
|
+
## Column Configuration
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
import { required, email } from "@updog/data-editor-wc";
|
|
187
|
+
|
|
188
|
+
editor.columns = [
|
|
189
|
+
{
|
|
190
|
+
id: "email",
|
|
191
|
+
title: "Email",
|
|
192
|
+
size: 260,
|
|
193
|
+
|
|
194
|
+
// One validator, or an array. Each returns `{ level: "error", message }` or null.
|
|
195
|
+
validate: [required("Email is required"), email("Enter a valid email")],
|
|
196
|
+
|
|
197
|
+
// Flag duplicates in this column as errors.
|
|
198
|
+
unique: true,
|
|
199
|
+
|
|
200
|
+
// Revalidate these columns when this one changes.
|
|
201
|
+
dependentFields: ["confirmEmail"],
|
|
202
|
+
|
|
203
|
+
// Cell editor. Default is "text".
|
|
204
|
+
editor: { type: "select", options: ["US", "UK", "DE"] },
|
|
205
|
+
|
|
206
|
+
// Display-only formatting. Does not mutate stored data.
|
|
207
|
+
formatter: (value) => value.toLowerCase(),
|
|
208
|
+
|
|
209
|
+
// Runs when rows are uploaded to the editor. Mutates the stored value.
|
|
210
|
+
transformer: (value) => String(value).trim(),
|
|
211
|
+
|
|
212
|
+
// Sidebar filter control.
|
|
213
|
+
filter: { type: "select" },
|
|
214
|
+
|
|
215
|
+
// Lock cells in this column. `"default"` locks only default-source rows.
|
|
216
|
+
locked: "default",
|
|
217
|
+
},
|
|
218
|
+
];
|
|
116
219
|
```
|
|
117
220
|
|
|
118
|
-
|
|
119
|
-
|
|
221
|
+
**Cell editors**: `{ type: "text" }` (default), `{ type: "date", minDate?, maxDate? }`, `{ type: "select", options: string[] }`, `{ type: "number", decimalPlaces?, decimalSeparator?, thousandsSeparator?, allowChars? }`.
|
|
222
|
+
|
|
223
|
+
**Column filters**: `{ type: "select", label?, placeholder?, options?, multiple? }`, `{ type: "number-range", label? }`, `{ type: "date-range", label? }`.
|
|
224
|
+
|
|
225
|
+
## Built-in Validators
|
|
226
|
+
|
|
227
|
+
Validators are factory functions — call each one with the error message you want displayed.
|
|
228
|
+
|
|
229
|
+
```js
|
|
230
|
+
import {
|
|
231
|
+
required,
|
|
232
|
+
numeric,
|
|
233
|
+
email,
|
|
234
|
+
date,
|
|
235
|
+
oneOf,
|
|
236
|
+
endDateAfterStart,
|
|
237
|
+
} from "@updog/data-editor-wc";
|
|
238
|
+
|
|
239
|
+
editor.columns = [
|
|
240
|
+
{ id: "name", title: "Name", validate: required("Required") },
|
|
241
|
+
{ id: "email", title: "Email", validate: [required("Required"), email("Invalid email")] },
|
|
242
|
+
{ id: "salary", title: "Salary", validate: numeric("Must be a number") },
|
|
243
|
+
{ id: "status", title: "Status", validate: oneOf(["active", "inactive"], "Invalid status") },
|
|
244
|
+
{
|
|
245
|
+
id: "startDate",
|
|
246
|
+
title: "Start",
|
|
247
|
+
validate: date("Use YYYY-MM-DD or DD/MM/YYYY"),
|
|
248
|
+
editor: { type: "date" },
|
|
249
|
+
dependentFields: ["endDate"],
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: "endDate",
|
|
253
|
+
title: "End",
|
|
254
|
+
validate: [date("Invalid date"), endDateAfterStart("startDate", "End must be on or after start")],
|
|
255
|
+
editor: { type: "date" },
|
|
256
|
+
},
|
|
257
|
+
];
|
|
120
258
|
```
|
|
121
259
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
260
|
+
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`.
|
|
261
|
+
|
|
262
|
+
### Custom validators
|
|
263
|
+
|
|
264
|
+
A validator is `(value, row) => ValidationError | null`. The only `level` is `"error"`.
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
const mustContainAt = (value) => {
|
|
268
|
+
if (!value) return null;
|
|
269
|
+
if (!String(value).includes("@")) return { level: "error", message: "Must contain @" };
|
|
270
|
+
return null;
|
|
271
|
+
};
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Data Loading
|
|
275
|
+
|
|
276
|
+
`loadData` is called once when the editor opens. Call `onChunk` one or more times to stream rows. The grid renders each chunk without blocking.
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
// Single source
|
|
280
|
+
editor.loadData = async (onChunk) => {
|
|
281
|
+
for (let page = 0; page < totalPages; page++) {
|
|
282
|
+
const rows = await fetch(`/api/employees?page=${page}`).then((r) => r.json());
|
|
283
|
+
onChunk(rows);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Multiple sources — each chunk tagged with a source
|
|
288
|
+
editor.loadData = async (onChunk) => {
|
|
289
|
+
onChunk(await fetchSalesforce(), { source: "Salesforce", done: true });
|
|
290
|
+
onChunk(await fetchHubSpot(), { source: "HubSpot", deletable: true, done: true });
|
|
291
|
+
};
|
|
132
292
|
```
|
|
133
293
|
|
|
134
|
-
|
|
294
|
+
`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.
|
|
295
|
+
|
|
296
|
+
## Submission Result
|
|
297
|
+
|
|
298
|
+
`onComplete(result, actions)` fires when the user submits.
|
|
135
299
|
|
|
136
300
|
```ts
|
|
137
|
-
|
|
138
|
-
|
|
301
|
+
type DataEditorResult = {
|
|
302
|
+
sources: {
|
|
303
|
+
sourceId: string;
|
|
304
|
+
sourceName: string;
|
|
305
|
+
rows: {
|
|
306
|
+
row: Record<string, unknown>;
|
|
307
|
+
isNew: boolean; // imported or manually added this session
|
|
308
|
+
isChanged: boolean; // cell values differ from origin
|
|
309
|
+
isDeleted: boolean; // marked for deletion
|
|
310
|
+
isValid: boolean; // passes all validators
|
|
311
|
+
}[];
|
|
312
|
+
}[];
|
|
313
|
+
counts: { new: number; changed: number; deleted: number; invalid: number };
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
type DataEditorActions = {
|
|
317
|
+
reset: () => void; // discard changes and reload via loadData
|
|
318
|
+
};
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
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:
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
editor.onComplete = async (result, actions) => {
|
|
325
|
+
for (const source of result.sources) {
|
|
326
|
+
const inserts = source.rows.filter((r) => r.isNew && !r.isDeleted && r.isValid);
|
|
327
|
+
const updates = source.rows.filter((r) => !r.isNew && r.isChanged && !r.isDeleted && r.isValid);
|
|
328
|
+
const deletes = source.rows.filter((r) => r.isDeleted && !r.isNew);
|
|
329
|
+
await persist(source.sourceId, { inserts, updates, deletes });
|
|
330
|
+
}
|
|
331
|
+
actions.reset();
|
|
332
|
+
};
|
|
139
333
|
```
|
|
140
334
|
|
|
141
|
-
|
|
142
|
-
<template>
|
|
143
|
-
<updog-editor ref="editor" api-key="xxx" @close="editor.hide()" />
|
|
144
|
-
</template>
|
|
335
|
+
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.
|
|
145
336
|
|
|
146
|
-
|
|
147
|
-
import { ref, onMounted } from "vue";
|
|
337
|
+
## Import Configuration
|
|
148
338
|
|
|
149
|
-
|
|
339
|
+
The import wizard guides users through uploading a file, mapping columns to your schema, and resolving value mismatches.
|
|
150
340
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
341
|
+
### Formats
|
|
342
|
+
|
|
343
|
+
Supported: `"csv"`, `"tsv"`, `"xlsx"`, `"json"`, `"xml"`.
|
|
344
|
+
|
|
345
|
+
```js
|
|
346
|
+
editor.importFormats = ["csv", "xlsx"]; // only CSV and XLSX
|
|
347
|
+
editor.exportFormats = []; // disable export entirely
|
|
158
348
|
```
|
|
159
349
|
|
|
160
|
-
|
|
350
|
+
### Override column matching
|
|
161
351
|
|
|
162
352
|
```js
|
|
163
|
-
editor.
|
|
353
|
+
editor.onColumnMatch = async (headers, columns) => {
|
|
354
|
+
const mappings = await myMatchingService.match(headers, columns);
|
|
355
|
+
return mappings; // { csvHeader: columnId | null }
|
|
356
|
+
};
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Return a map of `{ csvHeader: columnId | null }`. Entries set to `null` or omitted fall back to built-in matching.
|
|
360
|
+
|
|
361
|
+
### Override value matching
|
|
362
|
+
|
|
363
|
+
For select columns, override imported-value → option mapping:
|
|
364
|
+
|
|
365
|
+
```js
|
|
366
|
+
editor.onValueMatch = async (valuesToMatch) => {
|
|
367
|
+
// valuesToMatch = { country: { importedValues: ["espana", "fr"], options: ["Spain", "France"] } }
|
|
368
|
+
return {
|
|
369
|
+
country: { espana: "Spain", fr: "France" },
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Values set to `null` skip auto-matching. Unmapped values fall back to built-in fuzzy matching.
|
|
375
|
+
|
|
376
|
+
### Synonyms
|
|
377
|
+
|
|
378
|
+
Extra aliases for the built-in column matcher:
|
|
379
|
+
|
|
380
|
+
```js
|
|
381
|
+
editor.synonyms = {
|
|
382
|
+
productSku: ["sku", "article_no", "item_code"],
|
|
383
|
+
firstName: ["first", "given_name", "fname"],
|
|
384
|
+
};
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Remote sources
|
|
388
|
+
|
|
389
|
+
Render custom import buttons for third-party sources. You own auth and picker logic; the SDK renders the button and processes the result.
|
|
390
|
+
|
|
391
|
+
```js
|
|
392
|
+
editor.remoteSources = [
|
|
164
393
|
{
|
|
165
|
-
id: "
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return null;
|
|
394
|
+
id: "google-sheets",
|
|
395
|
+
label: "Google Sheets",
|
|
396
|
+
icon: "<svg>...</svg>",
|
|
397
|
+
fetch: async () => {
|
|
398
|
+
const data = await myGoogleSheetsLib.pick();
|
|
399
|
+
return data.rows; // Record<string, unknown>[] — or return a File
|
|
172
400
|
},
|
|
173
|
-
unique: true,
|
|
174
|
-
// Cell editor type: "text" (default), "date", or "select"
|
|
175
|
-
editor: { type: "text" },
|
|
176
|
-
// Visual formatting (display only)
|
|
177
|
-
formatter: (value) => value.toLowerCase(),
|
|
178
|
-
// Data transformation on input
|
|
179
|
-
transformer: (value) => String(value).trim(),
|
|
180
|
-
// Filter panel configuration
|
|
181
|
-
filter: { type: "select" },
|
|
182
401
|
},
|
|
183
402
|
];
|
|
184
403
|
```
|
|
185
404
|
|
|
186
|
-
|
|
405
|
+
Return a `File` to parse via the standard CSV/XLSX/JSON/XML pipeline, or return structured records to skip parsing.
|
|
187
406
|
|
|
188
|
-
|
|
407
|
+
## Error Handling
|
|
189
408
|
|
|
190
|
-
|
|
191
|
-
|
|
409
|
+
Log SDK-internal errors through `onError`. The SDK recovers gracefully — use this for monitoring, not recovery.
|
|
410
|
+
|
|
411
|
+
```js
|
|
412
|
+
editor.onError = (error) => {
|
|
413
|
+
Sentry.captureException(error.originalError ?? error, {
|
|
414
|
+
tags: { code: error.code, source: error.source },
|
|
415
|
+
});
|
|
416
|
+
};
|
|
192
417
|
```
|
|
193
418
|
|
|
194
|
-
|
|
419
|
+
Error codes: `PARSE_ERROR`, `RENDER_ERROR`, `TRANSFORM_ERROR`, `VALIDATION_ERROR`, `WORKER_ERROR`, `COMMAND_ERROR`, `OPERATION_ERROR`.
|
|
195
420
|
|
|
196
421
|
## Styling
|
|
197
422
|
|
|
198
|
-
`<updog-editor>` renders in **light DOM by design** — no Shadow DOM isolation. This is intentional: you can override any editor style with
|
|
423
|
+
`<updog-editor>` renders in **light DOM by design** — no Shadow DOM isolation. This is intentional: you can override any editor style with CSS targeting descendants of `updog-editor`.
|
|
199
424
|
|
|
200
425
|
### CSS variables
|
|
201
426
|
|
|
202
|
-
The editor reads colors
|
|
427
|
+
The editor reads colors from CSS custom properties. Override them on `:root` or on the element itself:
|
|
203
428
|
|
|
204
429
|
```css
|
|
205
430
|
updog-editor {
|
|
206
|
-
--updog-
|
|
207
|
-
--updog-
|
|
208
|
-
--updog-radius-md: 8px;
|
|
431
|
+
--updog-grid-cell-bg-idle: #ffffff;
|
|
432
|
+
--updog-grid-header-bg-idle: #f8f9fa;
|
|
209
433
|
}
|
|
210
434
|
```
|
|
211
435
|
|
|
436
|
+
See the [Styling reference](https://docs.updog.tech/styling/) for the full variable list.
|
|
437
|
+
|
|
212
438
|
### Class names
|
|
213
439
|
|
|
214
|
-
Internal elements use BEM-style class names prefixed with `updog__`. Target them
|
|
440
|
+
Internal elements use BEM-style class names prefixed with `updog__`. Target them for deep customization:
|
|
215
441
|
|
|
216
442
|
```css
|
|
217
443
|
updog-editor .updog__button--primary { background: purple; }
|
|
@@ -228,9 +454,19 @@ body *:not(updog-editor *) { /* your global rules */ }
|
|
|
228
454
|
## Notes
|
|
229
455
|
|
|
230
456
|
- Fonts are inherited from your page. The editor uses `var(--updog-font-family)` which defaults to system fonts.
|
|
231
|
-
- In modal mode, the editor portals to `document.body` as a full-screen overlay. In inline mode, it renders in
|
|
457
|
+
- In modal mode, the editor portals to `document.body` as a full-screen overlay. In inline mode, it renders in place with no overlay.
|
|
232
458
|
- Multiple `<updog-editor>` instances on the same page are fully independent.
|
|
233
459
|
|
|
460
|
+
## Using React?
|
|
461
|
+
|
|
462
|
+
Use the first-class React SDK instead:
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
npm install @updog/data-editor
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
See [@updog/data-editor](https://www.npmjs.com/package/@updog/data-editor).
|
|
469
|
+
|
|
234
470
|
## License
|
|
235
471
|
|
|
236
|
-
Commercial — see [LICENSE](./LICENSE) for the full terms of the Updog SDK Commercial License v1.0. Contact `admin@updog.tech` for enterprise licensing
|
|
472
|
+
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 bundled with this package are listed in `THIRD_PARTY_NOTICES.txt`.
|