@updog/data-editor 0.1.31 → 0.1.33

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/README.md +10 -7
  2. package/index.d.ts +6 -1
  3. package/index.js +112 -104
  4. package/package.json +9 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # @updog/data-editor
1
+ # React CSV Importer & Spreadsheet Editor
2
2
 
3
- Client-side CSV 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.
3
+ > `@updog/data-editor`: client-side CSV 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
4
 
5
5
  ## What is @updog/data-editor
6
6
 
@@ -145,13 +145,13 @@ In inline mode, `open` and `onClose` don't apply.
145
145
  | `readonly` | `boolean` | No | `false` | Hide all editing UI. |
146
146
  | `enableDeleteRow` | `"all"` \| `"new"` \| `false` | No | `false` | Row deletion policy. |
147
147
  | `enableAddRow` | `boolean` | No | `true` | Show the "Add row" button. |
148
+ | `enableAddSource` | `boolean` | No | `true` | Show the "Add data source" button. |
148
149
  | `enableCreateColumn` | `boolean` | No | `true` | Allow creating columns for unmatched CSV headers during import. |
149
- | `importFormats` | `DataEditorFormat[]` | No | all | Allowed import formats. `[]` disables import. |
150
- | `exportFormats` | `DataEditorFormat[]` | No | all | Allowed export formats. `[]` disables export. |
150
+ | `importFormats` | `DataEditorFormat[] \| false` | No | all | Allowed import formats. `false` or `[]` disables import. |
151
+ | `exportFormats` | `DataEditorFormat[] \| false` | No | all | Allowed export formats. `false` or `[]` disables export. |
151
152
  | `remoteSources` | `RemoteSource[]` | No | — | Custom import buttons (Google Sheets, S3, etc.) rendered on the upload step. |
152
153
  | `rowHeight` | `number` | No | `34` | Row height in pixels. |
153
154
  | `headerHeight` | `number` | No | `36` | Header height in pixels. |
154
- | `server` | `DataEditorServer<TRow>` | No | — | Server-delegated mode: SDK renders, your backend handles queries and mutations. |
155
155
  | `chat` | `DataEditorChat<TRow>` | No | — | Bring-your-own AI chat panel. |
156
156
  | `onColumnMatch` | `(headers, columns) => ...` | No | — | Override import column matching. |
157
157
  | `onValueMatch` | `(valuesToMatch) => ...` | No | — | Override import value matching for `select` columns. |
@@ -196,6 +196,9 @@ const columns: DataEditorColumn[] = [
196
196
  // Sidebar filter control.
197
197
  filter: { type: "select" },
198
198
 
199
+ // Allow pinning this column to the leading edge via the header menu. Default true.
200
+ pinnable: true,
201
+
199
202
  // Lock cells in this column. `"default"` locks only default-source rows.
200
203
  locked: "default",
201
204
  },
@@ -208,7 +211,7 @@ const columns: DataEditorColumn[] = [
208
211
 
209
212
  ## Built-in Validators
210
213
 
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.
214
+ Validators are declarative objects passed in the `validators` array on each column. They run on every edit, entirely in the browser.
212
215
 
213
216
  | `type` | Fields | Behavior |
214
217
  |---|---|---|
@@ -251,7 +254,7 @@ A `ValidationError` with `level: "error"` flags the cell in the grid but does **
251
254
 
252
255
  ### Custom validators
253
256
 
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.
257
+ For cross-field checks or anything not covered by the built-ins, wrap a `CellValidator` function in `{ type: "function", fn }`. Use `dependentFields` on the column to trigger re-validation when another column changes.
255
258
 
256
259
  ```tsx
257
260
  import type { CellValidator, DataEditorColumn } from "@updog/data-editor";
package/index.d.ts CHANGED
@@ -132,6 +132,7 @@ declare var export_default = {
132
132
  },
133
133
  footer: {
134
134
  submit: "Submit",
135
+ submitBlockedByErrors: "Fix errors before submitting",
135
136
  import: "Import",
136
137
  next: "Next",
137
138
  cancel: "Cancel",
@@ -2917,6 +2918,11 @@ type DataEditorBaseProps<TRow extends DataEditorRow = DataEditorRow> = {
2917
2918
  * @default true
2918
2919
  */
2919
2920
  enableAddSource?: boolean;
2921
+ /**
2922
+ * Disable submit while any row has a validation error.
2923
+ * @default false
2924
+ */
2925
+ blockSubmitOnError?: boolean;
2920
2926
  /**
2921
2927
  * Which file formats the user can import. `undefined` allows all formats,
2922
2928
  * `false` disables import entirely.
@@ -3152,7 +3158,6 @@ declare function exportDataEditor<TRow extends DataEditorRow>(params: ExportPara
3152
3158
  * onClose={() => setIsOpen(false)}
3153
3159
  * columns={columns}
3154
3160
  * primaryKey="id"
3155
- * server={{ url: scaleUrl }}
3156
3161
  * loadData={async (onChunk) => onChunk(await fetchRows())}
3157
3162
  * onComplete={async (result) => { await saveChanges(result); }}
3158
3163
  * />
package/index.js CHANGED
@@ -121,6 +121,7 @@ var Ur = {
121
121
  },
122
122
  footer: {
123
123
  submit: "Submit",
124
+ submitBlockedByErrors: "Fix errors before submitting",
124
125
  import: "Import",
125
126
  next: "Next",
126
127
  cancel: "Cancel",
@@ -10007,71 +10008,72 @@ function gl(e, t, n, r) {
10007
10008
  }
10008
10009
  //#endregion
10009
10010
  //#region src/context/useViewModel.tsx
10010
- function _l({ columns: e, primaryKey: t, loadData: n, scaleClient: r, onComplete: i, variant: a, showUploader: o, setShowUploader: s, storeRef: c, enableDeleteRow: l, enableAddRow: u, enableAddSource: f, importFormats: m, exportFormats: h, rowHeight: g, headerHeight: _, readonly: v, enableCreateColumn: x, onColumnMatch: S, onValueMatch: C, synonyms: w, sampleData: T, chat: E, remoteSources: D, onError: O, errorHandler: k }) {
10011
- let { t: A } = J(), j = r ? "server" : "client", M = sl(j, r, A("dataEditor.dataSources.existingData"), k ?? new zo(O));
10012
- M.setSchemaColumns(e);
10013
- let { addDynamicColumns: N, removeDynamicColumn: P, updateColumn: F } = dl(M), I = d((e) => {
10014
- M.setDynamicColumns(e);
10015
- }, [M]), L = b(M.subscribe, M.getEffectiveColumns), { navigateToCell: R, navigateByIndex: z, navigateToCellRef: B, resetScrollRef: V, scrollToGridTop: H } = fl(M), { validator: ee, validatorRef: U, findReplace: W } = gl(M, L, r?.findAndReplace, z), { search: te, setSearch: ne, matchCase: re, setMatchCase: ie, matchEntireCell: ae, setMatchEntireCell: oe, searchMode: se, setSearchMode: ce, filterColumns: le, setFilterColumns: ue, filtersResetKey: de, resetFilters: fe } = ml(M, W, R, H);
10016
- ul(M, U, n, j);
10017
- let { onVisibleRangeChange: pe } = hl(M, j), { portalRef: me } = pl(), [he, ge] = y(null);
10018
- return p(() => (c && (c.current = M), () => {
10011
+ function _l({ columns: e, primaryKey: t, loadData: n, scaleClient: r, onComplete: i, variant: a, showUploader: o, setShowUploader: s, storeRef: c, enableDeleteRow: l, enableAddRow: u, enableAddSource: f, blockSubmitOnError: m, importFormats: h, exportFormats: g, rowHeight: _, headerHeight: v, readonly: x, enableCreateColumn: S, onColumnMatch: C, onValueMatch: w, synonyms: T, sampleData: E, chat: D, remoteSources: O, onError: k, errorHandler: A }) {
10012
+ let { t: j } = J(), M = r ? "server" : "client", N = sl(M, r, j("dataEditor.dataSources.existingData"), A ?? new zo(k));
10013
+ N.setSchemaColumns(e);
10014
+ let { addDynamicColumns: P, removeDynamicColumn: F, updateColumn: I } = dl(N), L = d((e) => {
10015
+ N.setDynamicColumns(e);
10016
+ }, [N]), R = b(N.subscribe, N.getEffectiveColumns), { navigateToCell: z, navigateByIndex: B, navigateToCellRef: V, resetScrollRef: H, scrollToGridTop: ee } = fl(N), { validator: U, validatorRef: W, findReplace: te } = gl(N, R, r?.findAndReplace, B), { search: ne, setSearch: re, matchCase: ie, setMatchCase: ae, matchEntireCell: oe, setMatchEntireCell: se, searchMode: ce, setSearchMode: le, filterColumns: ue, setFilterColumns: de, filtersResetKey: fe, resetFilters: pe } = ml(N, te, z, ee);
10017
+ ul(N, W, n, M);
10018
+ let { onVisibleRangeChange: me } = hl(N, M), { portalRef: he } = pl(), [ge, _e] = y(null);
10019
+ return p(() => (c && (c.current = N), () => {
10019
10020
  c && (c.current = null);
10020
- }), [M, c]), p(() => {
10021
- t && M.setPrimaryKey(t);
10022
- }, [M, t]), {
10023
- mode: j,
10021
+ }), [N, c]), p(() => {
10022
+ t && N.setPrimaryKey(t);
10023
+ }, [N, t]), {
10024
+ mode: M,
10024
10025
  scaleClient: r,
10025
- columns: L,
10026
+ columns: R,
10026
10027
  primaryKey: t,
10027
10028
  enableDeleteRow: l ?? !1,
10028
10029
  enableAddRow: u ?? !0,
10029
10030
  enableAddSource: f ?? !0,
10030
- importFormats: m ?? Vo,
10031
- exportFormats: h ?? Vo,
10032
- rowHeight: g ?? 34,
10033
- headerHeight: _ ?? 36,
10034
- store: M,
10035
- validator: ee,
10036
- findReplace: W,
10031
+ blockSubmitOnError: m ?? !1,
10032
+ importFormats: h ?? Vo,
10033
+ exportFormats: g ?? Vo,
10034
+ rowHeight: _ ?? 34,
10035
+ headerHeight: v ?? 36,
10036
+ store: N,
10037
+ validator: U,
10038
+ findReplace: te,
10037
10039
  onComplete: i,
10038
10040
  showUploader: o,
10039
10041
  setShowUploader: s,
10040
10042
  variant: a ?? "editor",
10041
- search: te,
10042
- setSearch: ne,
10043
- matchCase: re,
10044
- setMatchCase: ie,
10045
- matchEntireCell: ae,
10046
- setMatchEntireCell: oe,
10047
- searchMode: se,
10048
- setSearchMode: ce,
10049
- filterColumns: le,
10050
- setFilterColumns: ue,
10051
- navigateToCell: R,
10052
- navigateToCellRef: B,
10053
- resetScrollRef: V,
10054
- scrollToGridTop: H,
10055
- portalRef: me,
10056
- readonly: v ?? !1,
10057
- filtersResetKey: de,
10058
- resetFilters: fe,
10043
+ search: ne,
10044
+ setSearch: re,
10045
+ matchCase: ie,
10046
+ setMatchCase: ae,
10047
+ matchEntireCell: oe,
10048
+ setMatchEntireCell: se,
10049
+ searchMode: ce,
10050
+ setSearchMode: le,
10051
+ filterColumns: ue,
10052
+ setFilterColumns: de,
10053
+ navigateToCell: z,
10054
+ navigateToCellRef: V,
10055
+ resetScrollRef: H,
10056
+ scrollToGridTop: ee,
10057
+ portalRef: he,
10058
+ readonly: x ?? !1,
10059
+ filtersResetKey: fe,
10060
+ resetFilters: pe,
10059
10061
  originalColumns: e,
10060
- dynamicColumns: M.getDynamicColumns(),
10061
- setDynamicColumns: I,
10062
- addDynamicColumns: N,
10063
- removeDynamicColumn: P,
10064
- updateColumn: F,
10065
- gridClipboard: he,
10066
- setGridClipboard: ge,
10067
- enableCreateColumn: x ?? !0,
10068
- onColumnMatch: S,
10069
- onValueMatch: C,
10070
- synonyms: w,
10071
- sampleData: T,
10072
- onVisibleRangeChange: pe,
10073
- chat: E,
10074
- remoteSources: D ?? []
10062
+ dynamicColumns: N.getDynamicColumns(),
10063
+ setDynamicColumns: L,
10064
+ addDynamicColumns: P,
10065
+ removeDynamicColumn: F,
10066
+ updateColumn: I,
10067
+ gridClipboard: ge,
10068
+ setGridClipboard: _e,
10069
+ enableCreateColumn: S ?? !0,
10070
+ onColumnMatch: C,
10071
+ onValueMatch: w,
10072
+ synonyms: T,
10073
+ sampleData: E,
10074
+ onVisibleRangeChange: me,
10075
+ chat: D,
10076
+ remoteSources: O ?? []
10075
10077
  };
10076
10078
  }
10077
10079
  //#endregion
@@ -13397,74 +13399,74 @@ var Yh = ({ open: e, onClose: t, onConfirm: n, loading: r = !1 }) => {
13397
13399
  })
13398
13400
  });
13399
13401
  }, Zh = () => {
13400
- let { store: e, columns: t, onComplete: n, exportFormats: r, readonly: i, navigateToCell: a } = Q(), { t: o, rtl: s } = J(), { canUndo: c, canRedo: l, undo: u, redo: f } = Yo(e, a), { dirtyRowCount: p, deletedRowCount: m, isLoading: h, sources: g, phase: v } = Bo(e), b = p > 0 || m > 0, w = h || g.some((e) => e.isLoading), T = v === "processing", [E, D] = y(!1), { value: O, setTrue: k, setFalse: A } = Ro(!1), [j, M] = y(!1), N = d(async () => {
13402
+ let { store: e, columns: t, onComplete: n, exportFormats: r, readonly: i, navigateToCell: a, blockSubmitOnError: o } = Q(), { t: s, rtl: c } = J(), { canUndo: l, canRedo: u, undo: f, redo: p } = Yo(e, a), { dirtyRowCount: m, deletedRowCount: h, errorCount: g, isLoading: v, sources: b, phase: w } = Bo(e), T = m > 0 || h > 0, E = v || b.some((e) => e.isLoading), D = w === "processing", O = o && g > 0, [k, A] = y(!1), { value: j, setTrue: M, setFalse: N } = Ro(!1), [P, F] = y(!1), I = d(async () => {
13401
13403
  if (!n) return;
13402
13404
  let t = e.getResultBySource();
13403
- M(!0);
13405
+ F(!0);
13404
13406
  try {
13405
- await n(t), e.clear(), A();
13407
+ await n(t), e.clear(), N();
13406
13408
  } catch {} finally {
13407
- M(!1);
13409
+ F(!1);
13408
13410
  }
13409
13411
  }, [
13410
13412
  e,
13411
13413
  n,
13412
- A
13413
- ]), P = o("dataEditor.footer.export"), F = o("dataEditor.footer.exportAll"), I = o("dataEditor.footer.exportFiltered"), R = o("dataEditor.footer.csvFormat"), z = o("dataEditor.footer.excelFormat"), B = o("dataEditor.footer.jsonFormat"), V = o("dataEditor.footer.tsvFormat"), H = o("dataEditor.footer.xmlFormat"), ee = _(() => [
13414
+ N
13415
+ ]), R = s("dataEditor.footer.export"), z = s("dataEditor.footer.exportAll"), B = s("dataEditor.footer.exportFiltered"), V = s("dataEditor.footer.csvFormat"), H = s("dataEditor.footer.excelFormat"), ee = s("dataEditor.footer.jsonFormat"), U = s("dataEditor.footer.tsvFormat"), W = s("dataEditor.footer.xmlFormat"), te = _(() => [
13414
13416
  {
13415
13417
  menuId: "csv",
13416
13418
  format: "csv",
13417
- text: R
13419
+ text: V
13418
13420
  },
13419
13421
  {
13420
13422
  menuId: "tsv",
13421
13423
  format: "tsv",
13422
- text: V
13424
+ text: U
13423
13425
  },
13424
13426
  {
13425
13427
  menuId: "excel",
13426
13428
  format: "xlsx",
13427
- text: z
13429
+ text: H
13428
13430
  },
13429
13431
  {
13430
13432
  menuId: "json",
13431
13433
  format: "json",
13432
- text: B
13434
+ text: ee
13433
13435
  },
13434
13436
  {
13435
13437
  menuId: "xml",
13436
13438
  format: "xml",
13437
- text: H
13439
+ text: W
13438
13440
  }
13439
13441
  ], [
13440
- R,
13441
13442
  V,
13442
- z,
13443
- B,
13444
- H
13445
- ]), U = r !== !1 && (!e.isServer() || e.hasServerExport), W = _(() => {
13446
- let e = r === !1 ? [] : ee.filter((e) => r.includes(e.format));
13443
+ U,
13444
+ H,
13445
+ ee,
13446
+ W
13447
+ ]), ne = r !== !1 && (!e.isServer() || e.hasServerExport), re = _(() => {
13448
+ let e = r === !1 ? [] : te.filter((e) => r.includes(e.format));
13447
13449
  return [{
13448
13450
  id: "export-all",
13449
- text: F,
13451
+ text: z,
13450
13452
  options: e.map((e) => ({
13451
13453
  id: `all-${e.menuId}`,
13452
13454
  text: e.text
13453
13455
  }))
13454
13456
  }, {
13455
13457
  id: "export-filtered",
13456
- text: I,
13458
+ text: B,
13457
13459
  options: e.map((e) => ({
13458
13460
  id: `filtered-${e.menuId}`,
13459
13461
  text: e.text
13460
13462
  }))
13461
13463
  }];
13462
13464
  }, [
13463
- F,
13464
- I,
13465
- ee,
13465
+ z,
13466
+ B,
13467
+ te,
13466
13468
  r
13467
- ]), te = d((n) => {
13469
+ ]), ie = d((n) => {
13468
13470
  let [r, i] = n.split("-"), a = {
13469
13471
  csv: "csv",
13470
13472
  tsv: "tsv",
@@ -13472,70 +13474,75 @@ var Yh = ({ open: e, onClose: t, onConfirm: n, loading: r = !1 }) => {
13472
13474
  json: "json",
13473
13475
  xml: "xml"
13474
13476
  };
13475
- D(!0), (e.isServer() ? e.serverExport(a[i], r === "all", s) : Jh({
13477
+ A(!0), (e.isServer() ? e.serverExport(a[i], r === "all", c) : Jh({
13476
13478
  store: e,
13477
13479
  columns: t,
13478
13480
  format: a[i],
13479
13481
  scope: r,
13480
- rtl: s
13481
- })).finally(() => D(!1));
13482
+ rtl: c
13483
+ })).finally(() => A(!1));
13482
13484
  }, [
13483
13485
  e,
13484
13486
  t,
13485
- s
13486
- ]), ne = o("dataEditor.footer.submit");
13487
+ c
13488
+ ]), ae = s("dataEditor.footer.submit"), oe = !T || D || P || O;
13487
13489
  return /* @__PURE__ */ C(x, { children: [/* @__PURE__ */ C("footer", {
13488
13490
  className: "updog__data-editor-footer",
13489
13491
  children: [/* @__PURE__ */ S(Xh, {}), /* @__PURE__ */ C("div", {
13490
13492
  className: "updog__data-editor-footer__actions",
13491
13493
  children: [
13492
13494
  !i && /* @__PURE__ */ C(x, { children: [/* @__PURE__ */ S(ua, {
13493
- content: o("dataEditor.undoRedo.undo"),
13495
+ content: s("dataEditor.undoRedo.undo"),
13494
13496
  children: /* @__PURE__ */ S(Z, {
13495
13497
  variant: "ghost",
13496
13498
  color: "neutral",
13497
- disabled: !c || T,
13498
- onClick: u,
13499
- "aria-label": o("dataEditor.undoRedo.undo"),
13499
+ disabled: !l || D,
13500
+ onClick: f,
13501
+ "aria-label": s("dataEditor.undoRedo.undo"),
13500
13502
  children: /* @__PURE__ */ S(Ne, { className: "rtl-mirror" })
13501
13503
  })
13502
13504
  }), /* @__PURE__ */ S(ua, {
13503
- content: o("dataEditor.undoRedo.redo"),
13505
+ content: s("dataEditor.undoRedo.redo"),
13504
13506
  children: /* @__PURE__ */ S(Z, {
13505
13507
  variant: "ghost",
13506
13508
  color: "neutral",
13507
- disabled: !l || T,
13508
- onClick: f,
13509
- "aria-label": o("dataEditor.undoRedo.redo"),
13509
+ disabled: !u || D,
13510
+ onClick: p,
13511
+ "aria-label": s("dataEditor.undoRedo.redo"),
13510
13512
  children: /* @__PURE__ */ S(xe, { className: "rtl-mirror" })
13511
13513
  })
13512
13514
  })] }),
13513
- U && /* @__PURE__ */ S(Pa, {
13514
- options: W,
13515
- onSelect: te,
13515
+ ne && /* @__PURE__ */ S(Pa, {
13516
+ options: re,
13517
+ onSelect: ie,
13516
13518
  placement: "top-end",
13517
13519
  children: /* @__PURE__ */ S(X, {
13518
13520
  variant: "outlined",
13519
13521
  color: "neutral",
13520
- loading: E,
13521
- disabled: w || T,
13522
+ loading: k,
13523
+ disabled: E || D,
13522
13524
  spinnerPosition: "end",
13523
13525
  endIcon: /* @__PURE__ */ S(L, {}),
13524
- children: P
13526
+ children: R
13525
13527
  })
13526
13528
  }),
13527
- !i && /* @__PURE__ */ S(X, {
13528
- disabled: !b || T || j,
13529
- onClick: k,
13530
- children: ne
13529
+ !i && /* @__PURE__ */ S(ua, {
13530
+ disabled: !O,
13531
+ content: s("dataEditor.footer.submitBlockedByErrors"),
13532
+ placement: "top-end",
13533
+ children: /* @__PURE__ */ S(X, {
13534
+ disabled: oe,
13535
+ onClick: M,
13536
+ children: ae
13537
+ })
13531
13538
  })
13532
13539
  ]
13533
13540
  })]
13534
13541
  }), /* @__PURE__ */ S(Yh, {
13535
- open: O,
13536
- onClose: A,
13537
- onConfirm: N,
13538
- loading: j
13542
+ open: j,
13543
+ onClose: N,
13544
+ onConfirm: I,
13545
+ loading: P
13539
13546
  })] });
13540
13547
  }, Qh = ({ initialValues: e, existingNames: t, onClose: n, onSubmit: r }) => {
13541
13548
  let { t: i } = J(), a = e.id !== void 0, o = _(() => {
@@ -25842,6 +25849,7 @@ function QT(e) {
25842
25849
  enableDeleteRow: e.enableDeleteRow,
25843
25850
  enableAddRow: e.enableAddRow,
25844
25851
  enableAddSource: e.enableAddSource,
25852
+ blockSubmitOnError: e.blockSubmitOnError,
25845
25853
  importFormats: e.importFormats,
25846
25854
  exportFormats: e.exportFormats,
25847
25855
  readonly: e.readonly,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@updog/data-editor",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
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",
@@ -52,8 +52,11 @@
52
52
  "keywords": [
53
53
  "csv-importer",
54
54
  "csv-import",
55
+ "importer",
55
56
  "data-import",
56
57
  "data-import-sdk",
58
+ "column-mapping",
59
+ "spreadsheet",
57
60
  "spreadsheet-editor",
58
61
  "react",
59
62
  "react-csv",
@@ -62,9 +65,13 @@
62
65
  "data-editor",
63
66
  "csv",
64
67
  "xlsx",
68
+ "excel",
65
69
  "grid",
66
70
  "table",
67
- "validation"
71
+ "validation",
72
+ "flatfile-alternative",
73
+ "dromo-alternative",
74
+ "usecsv-alternative"
68
75
  ],
69
76
  "publishConfig": {
70
77
  "access": "public",