@updog/data-editor 0.1.17 → 0.1.19

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 (4) hide show
  1. package/LICENSE +4 -2
  2. package/README.md +75 -34
  3. package/index.js +22 -12
  4. package/package.json +1 -1
package/LICENSE CHANGED
@@ -232,8 +232,10 @@ Sections 5, 8, 11, 12, and 14 survive termination of this License.
232
232
  --------------------------------------------------------------------------------
233
233
 
234
234
  This License shall be governed by and construed in accordance with the laws of
235
- the United Arab Emirates. Any disputes arising under this License shall be
236
- subject to the exclusive jurisdiction of the courts of the United Arab Emirates.
235
+ the Dubai International Financial Centre (DIFC), as applied by the DIFC Courts.
236
+ Any dispute arising out of or in connection with this License, including any
237
+ question regarding its existence, validity, or termination, shall be subject to
238
+ the exclusive jurisdiction of the courts of the DIFC.
237
239
 
238
240
  --------------------------------------------------------------------------------
239
241
  15. EXPORT COMPLIANCE
package/README.md CHANGED
@@ -29,7 +29,7 @@ npm install @updog/data-editor
29
29
 
30
30
  ```tsx
31
31
  import { useState } from "react";
32
- import { DataEditor, required, email } from "@updog/data-editor";
32
+ import { DataEditor } from "@updog/data-editor";
33
33
  import "@updog/data-editor/styles.css";
34
34
  import type { DataEditorColumn } from "@updog/data-editor";
35
35
 
@@ -41,9 +41,26 @@ type Employee = {
41
41
  };
42
42
 
43
43
  const columns: DataEditorColumn[] = [
44
- { id: "name", title: "Full Name", size: 200, validate: required("Name is required") },
45
- { id: "email", title: "Email", size: 250, validate: [required("Email is required"), email("Invalid email")] },
46
- { id: "role", title: "Role", editor: { type: "select", options: ["Admin", "Editor", "Viewer"] } },
44
+ {
45
+ id: "name",
46
+ title: "Full Name",
47
+ size: 200,
48
+ validators: [{ type: "required", message: "Name is required" }],
49
+ },
50
+ {
51
+ id: "email",
52
+ title: "Email",
53
+ size: 250,
54
+ validators: [
55
+ { type: "required", message: "Email is required" },
56
+ { type: "email", message: "Invalid email" },
57
+ ],
58
+ },
59
+ {
60
+ id: "role",
61
+ title: "Role",
62
+ editor: { type: "select", options: ["Admin", "Editor", "Viewer"] },
63
+ },
47
64
  ];
48
65
 
49
66
  export function App() {
@@ -147,7 +164,6 @@ In inline mode, `open` and `onClose` don't apply.
147
164
  ## Column Configuration
148
165
 
149
166
  ```tsx
150
- import { required, email } from "@updog/data-editor";
151
167
  import type { DataEditorColumn } from "@updog/data-editor";
152
168
 
153
169
  const columns: DataEditorColumn[] = [
@@ -156,8 +172,11 @@ const columns: DataEditorColumn[] = [
156
172
  title: "Email",
157
173
  size: 260,
158
174
 
159
- // One validator, or an array. Each returns `{ level: "error", message }` or null.
160
- validate: [required("Email is required"), email("Enter a valid email")],
175
+ // Declarative validators. See "Built-in Validators" for the full list.
176
+ validators: [
177
+ { type: "required", message: "Email is required" },
178
+ { type: "email", message: "Enter a valid email" },
179
+ ],
161
180
 
162
181
  // Flag duplicates in this column as errors.
163
182
  unique: true,
@@ -189,36 +208,42 @@ const columns: DataEditorColumn[] = [
189
208
 
190
209
  ## Built-in Validators
191
210
 
192
- Validators are factory functions call each one with the error message you want displayed.
211
+ Validators are declarative objects passed in the `validators` array on each column. The SDK ships a TS interpreter for the client; Updog Scale ships a matching Go interpreter for server mode. Both runtimes are pinned to the same fixture set, so a column definition produces identical pass/fail results regardless of where it runs.
212
+
213
+ | `type` | Fields | Behavior |
214
+ |---|---|---|
215
+ | `"required"` | `message?` | Value must be non-empty. |
216
+ | `"email"` | `message?` | Value must look like an email address. |
217
+ | `"numeric"` | `message?` | Value must parse as a number. Commas are stripped before parsing. |
218
+ | `"regex"` | `pattern`, `flags?`, `message?` | Value must match the regular expression. Empty values are skipped. |
219
+ | `"range"` | `min?`, `max?`, `message?` | Value must be numerically within `[min, max]`. Either bound is optional. |
220
+ | `"oneOf"` | `values: string[]`, `message?` | Value must be one of the listed strings. |
221
+ | `"date"` | `format?: "YYYY-MM-DD" \| "DD/MM/YYYY"`, `message?` | Value must parse as a date in the given format. When `format` is omitted, either format is accepted. |
193
222
 
194
223
  ```tsx
195
- import {
196
- required,
197
- numeric,
198
- email,
199
- date,
200
- oneOf,
201
- endDateAfterStart,
202
- } from "@updog/data-editor";
203
-
204
- const columns = [
205
- { id: "name", title: "Name", validate: required("Required") },
206
- { id: "email", title: "Email", validate: [required("Required"), email("Invalid email")] },
207
- { id: "salary", title: "Salary", validate: numeric("Must be a number") },
208
- { id: "status", title: "Status", validate: oneOf(["active", "inactive"], "Invalid status") },
224
+ const columns: DataEditorColumn[] = [
225
+ { id: "name", title: "Name", validators: [{ type: "required", message: "Required" }] },
226
+ {
227
+ id: "email",
228
+ title: "Email",
229
+ validators: [
230
+ { type: "required", message: "Required" },
231
+ { type: "email", message: "Invalid email" },
232
+ ],
233
+ },
234
+ { id: "salary", title: "Salary", validators: [{ type: "numeric", message: "Must be a number" }] },
235
+ {
236
+ id: "status",
237
+ title: "Status",
238
+ validators: [{ type: "oneOf", values: ["active", "inactive"], message: "Invalid status" }],
239
+ },
209
240
  {
210
241
  id: "startDate",
211
242
  title: "Start",
212
- validate: date("Use YYYY-MM-DD or DD/MM/YYYY"),
243
+ validators: [{ type: "date", format: "YYYY-MM-DD", message: "Use YYYY-MM-DD" }],
213
244
  editor: { type: "date" },
214
245
  dependentFields: ["endDate"],
215
246
  },
216
- {
217
- id: "endDate",
218
- title: "End",
219
- validate: [date("Invalid date"), endDateAfterStart("startDate", "End must be on or after start")],
220
- editor: { type: "date" },
221
- },
222
247
  ];
223
248
  ```
224
249
 
@@ -226,18 +251,34 @@ A `ValidationError` with `level: "error"` flags the cell in the grid but does **
226
251
 
227
252
  ### Custom validators
228
253
 
229
- A validator is `(value, row) => ValidationError | null`. The only `level` is `"error"`.
254
+ For cross-field checks or anything not covered by the built-ins, wrap a `CellValidator` function in `{ type: "function", fn }`. Function validators are client-mode only — they're dropped (with a one-time warning) when the SDK serializes the schema for server mode. Use `dependentFields` on the column to trigger re-validation when another column changes.
230
255
 
231
256
  ```tsx
232
- import type { CellValidator } from "@updog/data-editor";
257
+ import type { CellValidator, DataEditorColumn } from "@updog/data-editor";
233
258
 
234
- const mustContainAt: CellValidator = (value) => {
235
- if (!value) return null;
236
- if (!String(value).includes("@")) return { level: "error", message: "Must contain @" };
259
+ const endAfterStart: CellValidator = (value, row) => {
260
+ if (!value || !row.startDate) return null;
261
+ if (String(value) < String(row.startDate)) {
262
+ return { level: "error", message: "End must be on or after start" };
263
+ }
237
264
  return null;
238
265
  };
266
+
267
+ const columns: DataEditorColumn[] = [
268
+ {
269
+ id: "endDate",
270
+ title: "End",
271
+ editor: { type: "date" },
272
+ validators: [
273
+ { type: "date", message: "Invalid date" },
274
+ { type: "function", fn: endAfterStart },
275
+ ],
276
+ },
277
+ ];
239
278
  ```
240
279
 
280
+ The only `level` is `"error"`. Return `null` to indicate the value is valid.
281
+
241
282
  ## Data Loading
242
283
 
243
284
  `loadData` is called once when the editor opens. Call `onChunk` one or more times to stream rows. The grid renders each chunk without blocking.
package/index.js CHANGED
@@ -22104,17 +22104,27 @@ var KS = ({ source: e }) => {
22104
22104
  })]
22105
22105
  })
22106
22106
  }), nC = ({ filtersResetKey: e }) => {
22107
- let { store: t, scrollToGridTop: n } = X(), { t: r } = q(), { filteredErrorCount: i, errorMessageCounts: a } = Mo(t), [o, s] = b(() => new Set(t.getFilters().errorMessageFilters ?? []));
22107
+ let { store: t, scrollToGridTop: n } = X(), { t: r } = q(), { filteredErrorCount: i, errorMessageCounts: a } = Mo(t), [o, s] = b(() => new Set(t.getFilters().errorMessageFilters ?? [])), [c, l] = b(!1);
22108
22108
  m(() => {
22109
- s(new Set(t.getFilters().errorMessageFilters ?? []));
22109
+ s(new Set(t.getFilters().errorMessageFilters ?? [])), l(!1);
22110
22110
  }, [e, t]);
22111
- let c = Object.keys(a), l = c.length > 0 && c.every((e) => o.has(e)), u = c.some((e) => o.has(e)) && !l, d = f((e) => {
22112
- s(e ? new Set(c) : /* @__PURE__ */ new Set()), n(), t.setFilters({ errorMessageFilters: e ? c : [] });
22111
+ let u = Object.keys(a), d = u.length > 0;
22112
+ m(() => {
22113
+ !c || !d || (s(new Set(u)), t.setFilters({ errorMessageFilters: u }), l(!1));
22113
22114
  }, [
22114
22115
  c,
22116
+ d,
22117
+ u,
22118
+ t
22119
+ ]);
22120
+ let p = d && u.every((e) => o.has(e)), h = u.some((e) => o.has(e)), g = d ? p : c, _ = d && h && !p, v = f((e) => {
22121
+ l(e), d && (s(e ? new Set(u) : /* @__PURE__ */ new Set()), n(), t.setFilters({ errorMessageFilters: e ? u : [] }));
22122
+ }, [
22123
+ d,
22124
+ u,
22115
22125
  n,
22116
22126
  t
22117
- ]), p = f((e, r) => {
22127
+ ]), y = f((e, r) => {
22118
22128
  s((i) => {
22119
22129
  let a = new Set(i);
22120
22130
  return r ? a.add(e) : a.delete(e), t.setFilters({ errorMessageFilters: [...a] }), n(), a;
@@ -22126,9 +22136,9 @@ var KS = ({ source: e }) => {
22126
22136
  trigger: ({ toggle: e, isOpen: t }) => {
22127
22137
  let n = r(t ? "dataEditor.filters.showLess" : "dataEditor.filters.showMore");
22128
22138
  return /* @__PURE__ */ C(Ni, {
22129
- checked: l,
22130
- indeterminate: u,
22131
- onChange: d,
22139
+ checked: g,
22140
+ indeterminate: _,
22141
+ onChange: v,
22132
22142
  checkboxPosition: "start",
22133
22143
  children: /* @__PURE__ */ w("div", {
22134
22144
  className: "updog__error-filter-section__checkbox-label",
@@ -22137,7 +22147,7 @@ var KS = ({ source: e }) => {
22137
22147
  className: "updog__error-filter-section__truncated",
22138
22148
  children: r("dataEditor.filters.rowsWithErrors")
22139
22149
  }),
22140
- c.length > 0 && /* @__PURE__ */ C(la, {
22150
+ u.length > 0 && /* @__PURE__ */ C(la, {
22141
22151
  content: n,
22142
22152
  children: /* @__PURE__ */ C(ui, {
22143
22153
  variant: "ghost",
@@ -22160,13 +22170,13 @@ var KS = ({ source: e }) => {
22160
22170
  })
22161
22171
  });
22162
22172
  },
22163
- content: c.length > 0 && /* @__PURE__ */ C("div", {
22173
+ content: u.length > 0 && /* @__PURE__ */ C("div", {
22164
22174
  className: "updog__error-filter-section__children",
22165
- children: c.map((e) => /* @__PURE__ */ C(tC, {
22175
+ children: u.map((e) => /* @__PURE__ */ C(tC, {
22166
22176
  message: e,
22167
22177
  count: a[e] ?? 0,
22168
22178
  checked: o.has(e),
22169
- onChange: p
22179
+ onChange: y
22170
22180
  }, e))
22171
22181
  })
22172
22182
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@updog/data-editor",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Client-side CSV importer and spreadsheet editor SDK for React. Import CSV, Excel, JSON, TSV, and XML, match columns, validate, and edit 1M+ rows entirely in the browser.",
5
5
  "author": "Mikhail Kutateladze <admin@updog.tech>",
6
6
  "homepage": "https://updog.tech",