@updog/data-editor 0.1.44 → 0.1.46

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/index.d.ts +10 -3
  2. package/index.js +184 -111
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -111,6 +111,7 @@ declare var export_default = {
111
111
  modal: {
112
112
  importTitle: "Import Data",
113
113
  editTitle: "Edit Data",
114
+ viewTitle: "View Data",
114
115
  },
115
116
  confirmClose: {
116
117
  title: "Discard changes?",
@@ -2873,7 +2874,7 @@ type RemoteSource = {
2873
2874
  * - `"editor"` — opens directly into the spreadsheet grid.
2874
2875
  * - `"uploader"` — opens the file import wizard first, then transitions to the grid.
2875
2876
  */
2876
- type DataEditorVariant = "editor" | "uploader";
2877
+ type DataEditorVariant = "editor" | "uploader" | "viewer";
2877
2878
  /** Base configuration shared by the public `DataEditorProps` and the internal provider. */
2878
2879
  type DataEditorBaseProps<TRow extends DataEditorRow = DataEditorRow> = {
2879
2880
  /** Column definitions. Each entry describes one column in the grid. */
@@ -3031,14 +3032,20 @@ type DataEditorBaseProps<TRow extends DataEditorRow = DataEditorRow> = {
3031
3032
  */
3032
3033
  onValueMatch?: (valuesToMatch: Record<string, ValueMatchInput>) => ValueMatchOutput | Promise<ValueMatchOutput>;
3033
3034
  /**
3034
- * Extra synonyms for column auto-matching. Keys are column IDs, values are
3035
- * alternative names the matching engine should recognize.
3035
+ * Extra synonyms layered on top of the built-ins for both column matching
3036
+ * (header vs column) and value matching (imported value vs select option).
3037
+ * Each key is the canonical target (a column ID/title, or a select option
3038
+ * value); the array lists aliases the matching engine should treat as
3039
+ * equivalent. Merged with the built-ins as a union per key.
3036
3040
  *
3037
3041
  * @example
3038
3042
  * ```ts
3039
3043
  * synonyms={{
3044
+ * // column aliases
3040
3045
  * productSku: ["sku", "article_no", "item_code"],
3041
3046
  * firstName: ["first", "given_name", "fname"],
3047
+ * // value aliases (e.g. for a "status" select)
3048
+ * Active: ["live", "enabled"],
3042
3049
  * }}
3043
3050
  * ```
3044
3051
  */
package/index.js CHANGED
@@ -97,7 +97,8 @@ var Hr = {
97
97
  dataEditor: {
98
98
  modal: {
99
99
  importTitle: "Import Data",
100
- editTitle: "Edit Data"
100
+ editTitle: "Edit Data",
101
+ viewTitle: "View Data"
101
102
  },
102
103
  confirmClose: {
103
104
  title: "Discard changes?",
@@ -10296,6 +10297,7 @@ function Bl({ columns: e, primaryKey: t, loadData: n, scaleClient: r, onComplete
10296
10297
  setPendingImportFile: xe,
10297
10298
  openUploaderWithFile: Se,
10298
10299
  variant: a ?? "editor",
10300
+ isViewer: a === "viewer",
10299
10301
  search: ie,
10300
10302
  setSearch: ae,
10301
10303
  matchCase: oe,
@@ -10311,7 +10313,7 @@ function Bl({ columns: e, primaryKey: t, loadData: n, scaleClient: r, onComplete
10311
10313
  resetScrollRef: ee,
10312
10314
  scrollToGridTop: te,
10313
10315
  portalRef: _e,
10314
- readonly: y ?? !1,
10316
+ readonly: a === "viewer" ? !0 : y ?? !1,
10315
10317
  filtersResetKey: me,
10316
10318
  resetFilters: he,
10317
10319
  originalColumns: e,
@@ -11584,7 +11586,7 @@ var Hu = 9, Uu = 3, Wu = [3, 2], Gu = 1, Ku = 6, qu = .8, Ju = .5, Yu = 4, Xu =
11584
11586
  paintHeaderCell(e, t, n) {
11585
11587
  let { ctx: r, theme: i, layout: a, textCache: o, columns: s, controller: c, sortState: l } = e, u = c.selection, d = a.pinnedCount, f = a.rtl, p = a.getColumnX(t, n), m = a.getColumnWidth(t), h = a.contentToCanvasX(p, m);
11586
11588
  u.isColumnSelected(t) ? (r.fillStyle = i.headerBgSelected, vu(r, h, 0, m, 36)) : u.isColumnHighlighted(t) && (r.fillStyle = i.headerBgActive, vu(r, h, 0, m, 36));
11587
- let g = s[t], _ = t < d, v = l?.columnId === g.id, y = e.store.isColumnLocked(g.id), b = 0;
11589
+ let g = s[t], _ = t < d, v = l?.columnId === g.id, y = !c.readonly && e.store.isColumnLocked(g.id), b = 0;
11588
11590
  y && b++, _ && b++, v && b++;
11589
11591
  let x = b > 0 ? b * 14 + (b - 1) * Ku + Ku : 0, S = m - Hu * 2 - x;
11590
11592
  r.font = i.headerFont, r.fillStyle = i.headerContentIdle;
@@ -20585,39 +20587,39 @@ var Z_ = ({ text: e, onSelect: t }) => /* @__PURE__ */ C(J, {
20585
20587
  /* @__PURE__ */ C(rv, {})
20586
20588
  ]
20587
20589
  }) }), ov = () => {
20588
- let { store: e, columns: t, chat: n, enableAddRow: r, readonly: i, navigateToCell: a, importFormats: o, openUploaderWithFile: s } = X(), { rowCount: c, filteredCount: l, isLoading: u } = Bo(e), { t: d } = K(), p = !u && l === 0, m = r && !i && c === 0, h = o !== !1 && o.length > 0, g = f(async () => {
20589
- a(await Xl(e, t, d("dataEditor.dataSources.manuallyAdded")), t[0].id);
20590
+ let { store: e, columns: t, chat: n, enableAddRow: r, readonly: i, isViewer: a, navigateToCell: o, importFormats: s, openUploaderWithFile: c } = X(), { rowCount: l, filteredCount: u, isLoading: d } = Bo(e), { t: p } = K(), m = !d && u === 0, h = r && !i && l === 0, g = s !== !1 && s.length > 0, _ = f(async () => {
20591
+ o(await Xl(e, t, p("dataEditor.dataSources.manuallyAdded")), t[0].id);
20590
20592
  }, [
20591
20593
  e,
20592
20594
  t,
20593
- a,
20594
- d
20595
- ]), _ = v(() => {
20596
- if (m) return {
20597
- content: d("dataEditor.dataSources.addRow"),
20595
+ o,
20596
+ p
20597
+ ]), y = v(() => {
20598
+ if (h) return {
20599
+ content: p("dataEditor.dataSources.addRow"),
20598
20600
  startIcon: /* @__PURE__ */ C(Ce, { size: "1rem" }),
20599
20601
  variant: "filled",
20600
- onClick: g
20602
+ onClick: _
20601
20603
  };
20602
20604
  }, [
20603
- m,
20604
- g,
20605
- d
20606
- ]), y = v(() => Wl(o === !1 ? [] : o), [o]), b = f((e) => {
20605
+ h,
20606
+ _,
20607
+ p
20608
+ ]), b = v(() => Wl(s === !1 ? [] : s), [s]), x = f((e) => {
20607
20609
  let t = e[0];
20608
- t && s(t);
20609
- }, [s]), { getRootProps: x, isDragActive: S } = ft({
20610
- accept: y,
20610
+ t && c(t);
20611
+ }, [c]), { getRootProps: S, isDragActive: T } = ft({
20612
+ accept: b,
20611
20613
  maxFiles: 1,
20612
20614
  noClick: !0,
20613
20615
  noKeyboard: !0,
20614
- disabled: i || !h,
20615
- onDrop: b
20616
+ disabled: i || !g,
20617
+ onDrop: x
20616
20618
  });
20617
20619
  return /* @__PURE__ */ C(Jl, { children: /* @__PURE__ */ w("div", {
20618
- ...x({ className: "updog__data-editor-inner" }),
20620
+ ...S({ className: "updog__data-editor-inner" }),
20619
20621
  children: [
20620
- /* @__PURE__ */ C(Gl, { children: /* @__PURE__ */ C("div", {
20622
+ !a && /* @__PURE__ */ C(Gl, { children: /* @__PURE__ */ C("div", {
20621
20623
  className: "updog__data-editor-aside",
20622
20624
  children: /* @__PURE__ */ w("div", {
20623
20625
  className: "updog__data-editor-aside__content updog__scrollbar",
@@ -20631,18 +20633,18 @@ var Z_ = ({ text: e, onSelect: t }) => /* @__PURE__ */ C(J, {
20631
20633
  /* @__PURE__ */ w(Kl, { children: [
20632
20634
  /* @__PURE__ */ w("div", {
20633
20635
  className: "updog__data-editor-grid-area",
20634
- children: [/* @__PURE__ */ C(h_, {}), p && /* @__PURE__ */ C(Oa, {
20636
+ children: [/* @__PURE__ */ C(h_, {}), m && /* @__PURE__ */ C(Oa, {
20635
20637
  className: "updog-grid__empty-state",
20636
- title: d("dataEditor.grid.emptyTitle"),
20637
- text: d("dataEditor.grid.emptyText"),
20638
- buttonProps: _
20638
+ title: p("dataEditor.grid.emptyTitle"),
20639
+ text: p("dataEditor.grid.emptyText"),
20640
+ buttonProps: y
20639
20641
  })]
20640
20642
  }),
20641
20643
  /* @__PURE__ */ C(Yl, {}),
20642
- /* @__PURE__ */ C(cu, {})
20644
+ !a && /* @__PURE__ */ C(cu, {})
20643
20645
  ] }),
20644
- S && /* @__PURE__ */ C(ql, {}),
20645
- n && /* @__PURE__ */ C(av, {})
20646
+ T && /* @__PURE__ */ C(ql, {}),
20647
+ n && !a && /* @__PURE__ */ C(av, {})
20646
20648
  ]
20647
20649
  }) });
20648
20650
  }, sv = "RENDER_ERROR", cv = (e) => {
@@ -20720,77 +20722,7 @@ var Z_ = ({ text: e, onSelect: t }) => /* @__PURE__ */ C(J, {
20720
20722
  ]
20721
20723
  })]
20722
20724
  });
20723
- }, fv = 60, pv = (e) => e.toLowerCase().replace(/[\s_\-.]+/g, "").trim(), mv = (e) => e.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[\s_\-.]+/g, " ").toLowerCase().split(" ").filter(Boolean), hv = (e, t) => {
20724
- let n = new Set(mv(e)), r = new Set(mv(t));
20725
- if (n.size === 0 || r.size === 0) return 0;
20726
- let i = 0;
20727
- for (let e of n) r.has(e) && i++;
20728
- return i / Math.max(n.size, r.size);
20729
- }, gv = (e, t) => {
20730
- if (e.length === 0) return t.length;
20731
- if (t.length === 0) return e.length;
20732
- e.length > t.length && ([e, t] = [t, e]);
20733
- let n = e.length, r = t.length, i = Array(n + 1);
20734
- for (let e = 0; e <= n; e++) i[e] = e;
20735
- for (let a = 1; a <= r; a++) {
20736
- let r = i[0];
20737
- i[0] = a;
20738
- for (let o = 1; o <= n; o++) {
20739
- let n = e[o - 1] === t[a - 1] ? 0 : 1, s = i[o];
20740
- i[o] = Math.min(i[o] + 1, i[o - 1] + 1, r + n), r = s;
20741
- }
20742
- }
20743
- return i[n];
20744
- }, _v = (e) => e <= 4 ? 1 : e <= 8 ? 2 : e <= 15 ? 3 : 4, vv = (e, t) => {
20745
- let n = Math.max(e.length, t.length);
20746
- if (n === 0) return !0;
20747
- let r = _v(n);
20748
- return gv(e, t) <= r;
20749
- }, yv = (e, t) => {
20750
- let n = pv(e), r = pv(t);
20751
- return (n.length <= r.length ? n : r).length < 4 ? !1 : n.includes(r) || r.includes(n);
20752
- }, bv = (e, t, n) => {
20753
- let r = pv(e), i = pv(t);
20754
- for (let [e, t] of Object.entries(n)) {
20755
- let n = [e, ...t], a = n.includes(r), o = n.includes(i);
20756
- if (a && o) return !0;
20757
- }
20758
- return !1;
20759
- }, xv = (e, t, n) => {
20760
- let r = pv(e), i = pv(t);
20761
- if (r === i) return 100;
20762
- if (n && bv(e, t, n)) return 90;
20763
- if (yv(e, t)) return 80;
20764
- let a = hv(e, t);
20765
- return a >= .5 ? 70 : vv(r, i) ? 65 : a > 0 ? Math.round(a * 60) : 0;
20766
- }, Sv = (e, t, n, r) => {
20767
- let i = xv(e, t, r), a = xv(e, n, r);
20768
- return Math.max(i, a);
20769
- }, Cv = (e, t, n = fv) => {
20770
- let r, i = 0;
20771
- for (let n of t) {
20772
- let t = xv(e, n);
20773
- t > i && (i = t, r = n);
20774
- }
20775
- return i >= n ? r : void 0;
20776
- }, wv = (e) => {
20777
- let t = {};
20778
- for (let [n, { importedValues: r, options: i }] of Object.entries(e)) {
20779
- let e = {};
20780
- for (let t of r) e[t] = Cv(t, i);
20781
- t[n] = e;
20782
- }
20783
- return t;
20784
- }, Tv = async (e, t) => {
20785
- if (!t) return wv(e);
20786
- let n = await t(e), r = {};
20787
- for (let [t, { importedValues: i, options: a }] of Object.entries(e)) {
20788
- let e = n[t], o = {};
20789
- for (let t of i) e && t in e ? o[t] = e[t] ?? void 0 : o[t] = Cv(t, a);
20790
- r[t] = o;
20791
- }
20792
- return r;
20793
- }, Ev = "action:create", Dv = {
20725
+ }, fv = {
20794
20726
  prefix: ["salutation", "honorific"],
20795
20727
  firstname: [
20796
20728
  "fname",
@@ -20986,7 +20918,9 @@ var Z_ = ({ text: e, onSelect: t }) => /* @__PURE__ */ C(J, {
20986
20918
  "supervisor",
20987
20919
  "linemanager",
20988
20920
  "reportingmanager",
20989
- "managername"
20921
+ "managername",
20922
+ "mgr",
20923
+ "mngr"
20990
20924
  ],
20991
20925
  managerid: [
20992
20926
  "supervisorid",
@@ -21317,8 +21251,76 @@ var Z_ = ({ text: e, onSelect: t }) => /* @__PURE__ */ C(J, {
21317
21251
  "languagespoken",
21318
21252
  "nativelanguage",
21319
21253
  "languageproficiency"
21320
- ]
21321
- }, Ov = (e, t) => {
21254
+ ],
21255
+ male: [
21256
+ "m",
21257
+ "man",
21258
+ "boy"
21259
+ ],
21260
+ female: [
21261
+ "f",
21262
+ "woman",
21263
+ "girl"
21264
+ ],
21265
+ other: [
21266
+ "nonbinary",
21267
+ "nb",
21268
+ "diverse",
21269
+ "prefernottosay"
21270
+ ],
21271
+ junior: [
21272
+ "jr",
21273
+ "jun",
21274
+ "entry",
21275
+ "entrylevel",
21276
+ "grad",
21277
+ "graduate"
21278
+ ],
21279
+ midlevel: [
21280
+ "mid",
21281
+ "intermediate",
21282
+ "middle"
21283
+ ],
21284
+ senior: ["sr", "sen"],
21285
+ associate: ["assoc", "asc"],
21286
+ principal: ["princ", "principle"],
21287
+ lead: [
21288
+ "teamlead",
21289
+ "techlead",
21290
+ "tl"
21291
+ ],
21292
+ director: ["dir"],
21293
+ executive: ["exec"],
21294
+ intern: ["internship", "trainee"],
21295
+ fulltime: ["ft", "full"],
21296
+ parttime: ["pt", "part"],
21297
+ contract: [
21298
+ "contractor",
21299
+ "contractual",
21300
+ "freelance",
21301
+ "freelancer"
21302
+ ],
21303
+ temporary: ["temp"],
21304
+ permanent: ["perm"],
21305
+ active: ["enabled", "current"],
21306
+ inactive: [
21307
+ "disabled",
21308
+ "terminated",
21309
+ "former"
21310
+ ],
21311
+ onleave: ["leave"],
21312
+ pending: ["awaiting"],
21313
+ single: ["unmarried"],
21314
+ married: ["wed"],
21315
+ divorced: ["div"],
21316
+ widowed: ["widow", "widower"],
21317
+ low: ["lo"],
21318
+ medium: ["med"],
21319
+ high: ["hi"],
21320
+ critical: ["crit", "urgent"],
21321
+ yes: ["y", "true"],
21322
+ no: ["n", "false"]
21323
+ }, pv = (e, t) => {
21322
21324
  if (!t) return e;
21323
21325
  let n = { ...e };
21324
21326
  for (let [e, r] of Object.entries(t)) if (e in n) {
@@ -21327,7 +21329,77 @@ var Z_ = ({ text: e, onSelect: t }) => /* @__PURE__ */ C(J, {
21327
21329
  n[e] = [...t];
21328
21330
  } else n[e] = r;
21329
21331
  return n;
21330
- }, kv = (e, t, n) => Sv(e, t.id, t.title, n), Av = (e, t, n, r, i = 60) => {
21332
+ }, mv = 60, hv = (e) => e.toLowerCase().replace(/[\s_\-.]+/g, "").trim(), gv = (e) => e.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[\s_\-.]+/g, " ").toLowerCase().split(" ").filter(Boolean), _v = (e, t) => {
21333
+ let n = new Set(gv(e)), r = new Set(gv(t));
21334
+ if (n.size === 0 || r.size === 0) return 0;
21335
+ let i = 0;
21336
+ for (let e of n) r.has(e) && i++;
21337
+ return i / Math.max(n.size, r.size);
21338
+ }, vv = (e, t) => {
21339
+ if (e.length === 0) return t.length;
21340
+ if (t.length === 0) return e.length;
21341
+ e.length > t.length && ([e, t] = [t, e]);
21342
+ let n = e.length, r = t.length, i = Array(n + 1);
21343
+ for (let e = 0; e <= n; e++) i[e] = e;
21344
+ for (let a = 1; a <= r; a++) {
21345
+ let r = i[0];
21346
+ i[0] = a;
21347
+ for (let o = 1; o <= n; o++) {
21348
+ let n = e[o - 1] === t[a - 1] ? 0 : 1, s = i[o];
21349
+ i[o] = Math.min(i[o] + 1, i[o - 1] + 1, r + n), r = s;
21350
+ }
21351
+ }
21352
+ return i[n];
21353
+ }, yv = (e) => e <= 4 ? 1 : e <= 8 ? 2 : e <= 15 ? 3 : 4, bv = (e, t) => {
21354
+ let n = Math.max(e.length, t.length);
21355
+ if (n === 0) return !0;
21356
+ let r = yv(n);
21357
+ return vv(e, t) <= r;
21358
+ }, xv = (e, t) => {
21359
+ let n = hv(e), r = hv(t);
21360
+ return (n.length <= r.length ? n : r).length < 4 ? !1 : n.includes(r) || r.includes(n);
21361
+ }, Sv = (e, t, n) => {
21362
+ let r = hv(e), i = hv(t);
21363
+ for (let [e, t] of Object.entries(n)) {
21364
+ let n = [e, ...t], a = n.includes(r), o = n.includes(i);
21365
+ if (a && o) return !0;
21366
+ }
21367
+ return !1;
21368
+ }, Cv = (e, t, n) => {
21369
+ let r = hv(e), i = hv(t);
21370
+ if (r === i) return 100;
21371
+ if (n && Sv(e, t, n)) return 90;
21372
+ if (xv(e, t)) return 80;
21373
+ let a = _v(e, t);
21374
+ return a >= .5 ? 70 : bv(r, i) ? 65 : a > 0 ? Math.round(a * 60) : 0;
21375
+ }, wv = (e, t, n, r) => {
21376
+ let i = Cv(e, t, r), a = Cv(e, n, r);
21377
+ return Math.max(i, a);
21378
+ }, Tv = (e, t, n = fv, r = mv) => {
21379
+ let i, a = 0;
21380
+ for (let r of t) {
21381
+ let t = Cv(e, r, n);
21382
+ t > a && (a = t, i = r);
21383
+ }
21384
+ return a >= r ? i : void 0;
21385
+ }, Ev = (e, t) => {
21386
+ let n = pv(fv, t), r = {};
21387
+ for (let [t, { importedValues: i, options: a }] of Object.entries(e)) {
21388
+ let e = {};
21389
+ for (let t of i) e[t] = Tv(t, a, n);
21390
+ r[t] = e;
21391
+ }
21392
+ return r;
21393
+ }, Dv = async (e, t, n) => {
21394
+ if (!t) return Ev(e, n);
21395
+ let r = pv(fv, n), i = await t(e), a = {};
21396
+ for (let [t, { importedValues: n, options: o }] of Object.entries(e)) {
21397
+ let e = i[t], s = {};
21398
+ for (let t of n) e && t in e ? s[t] = e[t] ?? void 0 : s[t] = Tv(t, o, r);
21399
+ a[t] = s;
21400
+ }
21401
+ return a;
21402
+ }, Ov = "action:create", kv = (e, t, n) => wv(e, t.id, t.title, n), Av = (e, t, n, r, i = 60) => {
21331
21403
  let a = null, o = 0;
21332
21404
  for (let i of t) {
21333
21405
  if (n.has(i.id)) continue;
@@ -21347,13 +21419,13 @@ var Z_ = ({ text: e, onSelect: t }) => /* @__PURE__ */ C(J, {
21347
21419
  ...s
21348
21420
  };
21349
21421
  }, Mv = (e, t, n) => {
21350
- let r = Ov(Dv, n), i = {}, a = /* @__PURE__ */ new Set();
21422
+ let r = pv(fv, n), i = {}, a = /* @__PURE__ */ new Set();
21351
21423
  for (let n of e) {
21352
- let e = pv(n);
21424
+ let e = hv(n);
21353
21425
  for (let r of t) {
21354
21426
  let t = r.id;
21355
21427
  if (a.has(t)) continue;
21356
- let o = pv(t), s = pv(r.title);
21428
+ let o = hv(t), s = hv(r.title);
21357
21429
  if (e === o || e === s) {
21358
21430
  i[n] = t, a.add(t);
21359
21431
  break;
@@ -21478,7 +21550,7 @@ var Z_ = ({ text: e, onSelect: t }) => /* @__PURE__ */ C(J, {
21478
21550
  id: `${r}_d`,
21479
21551
  type: "divider"
21480
21552
  }, {
21481
- id: Ev,
21553
+ id: Ov,
21482
21554
  text: a,
21483
21555
  icon: /* @__PURE__ */ C(Ce, { size: "1rem" })
21484
21556
  }), [r, c];
@@ -22568,14 +22640,15 @@ function wy() {
22568
22640
  return;
22569
22641
  }
22570
22642
  let t = ++Le.current;
22571
- Tv(e, c).then((e) => {
22643
+ Dv(e, c, l).then((e) => {
22572
22644
  Le.current === t && Ie(e);
22573
22645
  });
22574
22646
  }, [
22575
22647
  S,
22576
22648
  T,
22577
22649
  n,
22578
- c
22650
+ c,
22651
+ l
22579
22652
  ]);
22580
22653
  let Re = v(() => {
22581
22654
  let e = {};
@@ -23246,7 +23319,7 @@ function sb(e) {
23246
23319
  v,
23247
23320
  T,
23248
23321
  e.readonly
23249
- ]), D = f(() => T?.(), [T]), O = n(p ? "dataEditor.modal.importTitle" : "dataEditor.modal.editTitle");
23322
+ ]), D = f(() => T?.(), [T]), O = p ? n("dataEditor.modal.importTitle") : e.variant === "viewer" ? n("dataEditor.modal.viewTitle") : n("dataEditor.modal.editTitle");
23250
23323
  m(() => {
23251
23324
  t === "modal" && !o && h(e.variant === "uploader");
23252
23325
  }, [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@updog/data-editor",
3
- "version": "0.1.44",
3
+ "version": "0.1.46",
4
4
  "description": "Client-side CSV importer and spreadsheet editor 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",