@marimo-team/islands 0.25.3-dev262 → 0.25.3-dev265

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 (45) hide show
  1. package/dist/{ConnectedDataExplorerComponent-DjlwdMQT.js → ConnectedDataExplorerComponent-C_hj7Poi.js} +6 -6
  2. package/dist/{ErrorBoundary-Bmn7yFxG.js → ErrorBoundary-DzYPyiOs.js} +13 -13
  3. package/dist/{any-language-editor-Be7dA2VK.js → any-language-editor-CFeWfC3o.js} +2 -2
  4. package/dist/{chat-ui-Bl_I-evE.js → chat-ui-DFxNgzXF.js} +10 -10
  5. package/dist/{check-CSvxZk5I.js → check-gUSZbRsl.js} +1 -1
  6. package/dist/{code-visibility-CIz56feH.js → code-visibility-CJExrSFe.js} +3 -3
  7. package/dist/{common-B736tDHW.js → common-5mM7a8cm.js} +15 -15
  8. package/dist/{config-mwEjAmvP.js → config-CmeG_Pb6.js} +1 -1
  9. package/dist/{copy-icon-DD8bdw4L.js → copy-icon-C2I8sw4h.js} +2 -2
  10. package/dist/{data-grid-overlay-editor-Dit5MC8K.js → data-grid-overlay-editor--Un2Lk6m.js} +1 -1
  11. package/dist/{dialog-BHYPX87J.js → dialog-BtprQCUw.js} +1 -1
  12. package/dist/{objectWithoutPropertiesLoose-ai5NUqGa.js → dist-X9CsotJl.js} +1 -23
  13. package/dist/{dropdown-menu-Blu7V6Mk.js → dropdown-menu-CkJ7ErY6.js} +10 -10
  14. package/dist/{error-entries-DFhMo601.js → error-entries-CMoaUX9V.js} +5 -5
  15. package/dist/{esm-CHjVSexr.js → esm-BDqqT86j.js} +123 -122
  16. package/dist/{feedback-button-BiQv1ZDP.js → feedback-button-C2NUXbzh.js} +6 -6
  17. package/dist/{glide-data-editor-DMDC25_Z.js → glide-data-editor-Dj9U-NK9.js} +6723 -2940
  18. package/dist/{glide-utils-qbkRZpEy.js → glide-utils-Ba9WD2IN.js} +78 -33
  19. package/dist/{globals-C6o26_z-.js → globals-CkbsxIrq.js} +2 -2
  20. package/dist/{html-to-image-BPs9Vo-H.js → html-to-image-D_5CMreP.js} +4415 -4415
  21. package/dist/main.js +5513 -5504
  22. package/dist/{number-overlay-editor-CMiX0eIR.js → number-overlay-editor-H8QAAmdr.js} +1 -1
  23. package/dist/objectWithoutPropertiesLoose-BRDcTsfB.js +23 -0
  24. package/dist/{process-output-c6xaHokq.js → process-output-CGFNVC60.js} +4 -4
  25. package/dist/{reveal-component-D9SrIaYo.js → reveal-component-BperrCFw.js} +6 -6
  26. package/dist/{select-CcidPqlM.js → select-C33FVPPN.js} +9 -9
  27. package/dist/{spec-ZwfZcgsm.js → spec-DH1lmF7Q.js} +4 -4
  28. package/dist/{strings-BYJNyKmg.js → strings-BCKpLFNU.js} +1 -1
  29. package/dist/{toast-CkB5Jq9K.js → toast-CY1_Qv41.js} +2 -2
  30. package/dist/{tooltip-Bzl4k00l.js → tooltip-BfcDQd2j.js} +1 -1
  31. package/dist/{useDateFormatter-CYo5fm8s.js → useDateFormatter-Dk_xFci4.js} +2 -2
  32. package/dist/{useLifecycle-GNgvNwI-.js → useLifecycle-JjxCC7Vu.js} +1 -1
  33. package/dist/{vega-component-DOtem7Hf.js → vega-component-C-lqjV0q.js} +2 -2
  34. package/package.json +2 -1
  35. package/src/plugins/impl/DataEditorPlugin.tsx +33 -8
  36. package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +93 -4
  37. package/src/plugins/impl/data-editor/__tests__/column-types.test.ts +37 -0
  38. package/src/plugins/impl/data-editor/__tests__/editor-state.test.ts +52 -1
  39. package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +113 -1
  40. package/src/plugins/impl/data-editor/__tests__/glide-utils.test.ts +116 -0
  41. package/src/plugins/impl/data-editor/column-types.ts +52 -0
  42. package/src/plugins/impl/data-editor/editor-state.ts +18 -2
  43. package/src/plugins/impl/data-editor/glide-data-editor.tsx +66 -8
  44. package/src/plugins/impl/data-editor/glide-utils.ts +30 -9
  45. package/src/plugins/impl/data-editor/types.ts +7 -0
@@ -1,6 +1,6 @@
1
1
  import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { t } from "./react-sHtZdjeP.js";
3
- import { a as n } from "./glide-data-editor-DMDC25_Z.js";
3
+ import { a as n } from "./glide-data-editor-Dj9U-NK9.js";
4
4
  //#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.3.0_27cffe7f1bc30287d59ac27db3240c5b/node_modules/@glideapps/glide-data-grid/dist/esm/internal/data-grid-overlay-editor/private/number-overlay-editor-style.js
5
5
  var r = /* @__PURE__ */ e(t()), i = /*#__PURE__*/ n("div")({
6
6
  name: "NumberOverlayEditorStyle",
@@ -0,0 +1,23 @@
1
+ //#region ../node_modules/.pnpm/@babel+runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/extends.js
2
+ function e() {
3
+ return e = Object.assign ? Object.assign.bind() : function(e) {
4
+ for (var t = 1; t < arguments.length; t++) {
5
+ var n = arguments[t];
6
+ for (var r in n) ({}).hasOwnProperty.call(n, r) && (e[r] = n[r]);
7
+ }
8
+ return e;
9
+ }, e.apply(null, arguments);
10
+ }
11
+ //#endregion
12
+ //#region ../node_modules/.pnpm/@babel+runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js
13
+ function t(e, t) {
14
+ if (e == null) return {};
15
+ var n = {};
16
+ for (var r in e) if ({}.hasOwnProperty.call(e, r)) {
17
+ if (t.indexOf(r) !== -1) continue;
18
+ n[r] = e[r];
19
+ }
20
+ return n;
21
+ }
22
+ //#endregion
23
+ export { e as n, t };
@@ -1,7 +1,7 @@
1
- import { k as e } from "./html-to-image-BPs9Vo-H.js";
2
- import { m as t } from "./tooltip-Bzl4k00l.js";
3
- import { $ as n, et as r } from "./error-entries-DFhMo601.js";
4
- import { t as i } from "./strings-BYJNyKmg.js";
1
+ import { k as e } from "./html-to-image-D_5CMreP.js";
2
+ import { h as t } from "./tooltip-BfcDQd2j.js";
3
+ import { $ as n, et as r } from "./error-entries-CMoaUX9V.js";
4
+ import { t as i } from "./strings-BCKpLFNU.js";
5
5
  import { n as a } from "./semaphore-89jsoHtS.js";
6
6
  var o = t("file", [["path", {
7
7
  d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
@@ -2,16 +2,16 @@ import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { c as t, g as n, h as r, m as i, t as a } from "./button-0-RqY1DW.js";
3
3
  import { t as o } from "./react-sHtZdjeP.js";
4
4
  import { t as s } from "./compiler-runtime-CldRdSsv.js";
5
- import { m as c, t as l } from "./tooltip-Bzl4k00l.js";
6
- import { $ as u, Q as d, et as f, i as p, l as m, n as h, on as g, r as _, sn as v, t as y, u as b, un as x } from "./common-B736tDHW.js";
7
- import { M as S } from "./select-CcidPqlM.js";
8
- import { Y as ee } from "./dropdown-menu-Blu7V6Mk.js";
9
- import { Pt as C } from "./error-entries-DFhMo601.js";
5
+ import { h as c, t as l } from "./tooltip-BfcDQd2j.js";
6
+ import { $ as u, Q as d, et as f, i as p, l as m, n as h, on as g, r as _, sn as v, t as y, u as b, un as x } from "./common-5mM7a8cm.js";
7
+ import { M as S } from "./select-C33FVPPN.js";
8
+ import { Y as ee } from "./dropdown-menu-CkJ7ErY6.js";
9
+ import { Pt as C } from "./error-entries-CMoaUX9V.js";
10
10
  import { t as w } from "./jsx-runtime-3BD1kT0s.js";
11
11
  import { r as T } from "./use-restore-focus-CmflVLWJ.js";
12
12
  import { D as E, t as D, x as O } from "./utils-DXkj82sK.js";
13
13
  import { o as k } from "./static-state-BldGd99T.js";
14
- import { a as A, n as te, t as ne } from "./code-visibility-CIz56feH.js";
14
+ import { a as A, n as te, t as ne } from "./code-visibility-CJExrSFe.js";
15
15
  /* empty css */
16
16
  var j = c("sticky-note", [["path", {
17
17
  d: "M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",
@@ -2,16 +2,16 @@ import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { h as t, m as n, p as r, s as i, u as a } from "./button-0-RqY1DW.js";
3
3
  import { t as o } from "./react-sHtZdjeP.js";
4
4
  import { t as s } from "./compiler-runtime-CldRdSsv.js";
5
- import { c, f as l, l as u, m as d, s as f, u as p } from "./tooltip-Bzl4k00l.js";
6
- import { t as m } from "./check-CSvxZk5I.js";
7
- import { a as h, s as g } from "./strings-BYJNyKmg.js";
5
+ import { c, h as l, l as u, p as d, s as f, u as p } from "./tooltip-BfcDQd2j.js";
6
+ import { t as m } from "./check-gUSZbRsl.js";
7
+ import { a as h, s as g } from "./strings-BCKpLFNU.js";
8
8
  import { t as _ } from "./react-dom-DUmH0GXv.js";
9
9
  import { t as v } from "./jsx-runtime-3BD1kT0s.js";
10
10
  import { A as y, C as b, D as x, F as S, I as C, M as w, N as T, P as E, S as ee, a as D, c as O, j as k, k as A, o as j, v as te, x as ne, y as re } from "./use-restore-focus-CmflVLWJ.js";
11
- var M = d("chevron-down", [["path", {
11
+ var M = l("chevron-down", [["path", {
12
12
  d: "m6 9 6 6 6-6",
13
13
  key: "qrunsl"
14
- }]]), ie = d("chevron-up", [["path", {
14
+ }]]), ie = l("chevron-up", [["path", {
15
15
  d: "m18 15-6-6-6 6",
16
16
  key: "153udz"
17
17
  }]]), N = /* @__PURE__ */ e(_(), 1), P = /* @__PURE__ */ e(o(), 1), F = v(), I = Object.defineProperty, ae = (e, t) => I(e, "name", {
@@ -682,7 +682,7 @@ var xe = /* @__PURE__ */ U((e) => {
682
682
  ref: t
683
683
  });
684
684
  }, "SelectSeparator")), ot = "SelectBubbleInput", st = /* @__PURE__ */ P.forwardRef(/* @__PURE__ */ U(function({ __scopeSelect: e, ...t }, n) {
685
- let i = q(ot, e), { value: a, onValueChange: o, required: s, disabled: c, name: u, autoComplete: d, form: f, userSelection: p } = i, { nativeOptions: m, nativeSelectKey: h } = i, g = P.useRef(null), _ = r(n, g), v = a ?? "", y = P.useRef(v), b = P.useRef(p), x = P.useRef(!1), S = Array.from(m).some((e) => (e.props.value ?? "") === "");
685
+ let i = q(ot, e), { value: a, onValueChange: o, required: s, disabled: c, name: l, autoComplete: u, form: f, userSelection: p } = i, { nativeOptions: m, nativeSelectKey: h } = i, g = P.useRef(null), _ = r(n, g), v = a ?? "", y = P.useRef(v), b = P.useRef(p), x = P.useRef(!1), S = Array.from(m).some((e) => (e.props.value ?? "") === "");
686
686
  return P.useEffect(() => {
687
687
  let e = g.current;
688
688
  if (!e) return;
@@ -709,8 +709,8 @@ var xe = /* @__PURE__ */ U((e) => {
709
709
  "aria-hidden": !0,
710
710
  required: s,
711
711
  tabIndex: -1,
712
- name: u,
713
- autoComplete: d,
712
+ name: l,
713
+ autoComplete: u,
714
714
  disabled: c,
715
715
  form: f,
716
716
  onChange: (e) => {
@@ -718,7 +718,7 @@ var xe = /* @__PURE__ */ U((e) => {
718
718
  },
719
719
  ...t,
720
720
  style: {
721
- ...l,
721
+ ...d,
722
722
  ...t.style
723
723
  },
724
724
  ref: _,
@@ -1,7 +1,7 @@
1
- import { m as e } from "./tooltip-Bzl4k00l.js";
2
- import { o as t } from "./useLifecycle-GNgvNwI-.js";
3
- import { i as n, o as r } from "./useDateFormatter-CYo5fm8s.js";
4
- import { i } from "./strings-BYJNyKmg.js";
1
+ import { h as e } from "./tooltip-BfcDQd2j.js";
2
+ import { o as t } from "./useLifecycle-JjxCC7Vu.js";
3
+ import { i as n, o as r } from "./useDateFormatter-Dk_xFci4.js";
4
+ import { i } from "./strings-BCKpLFNU.js";
5
5
  var a = e("align-center-vertical", [
6
6
  ["path", {
7
7
  d: "M12 2v20",
@@ -1,7 +1,7 @@
1
1
  import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { g as t, p as n, u as r } from "./button-0-RqY1DW.js";
3
3
  import { t as i } from "./react-sHtZdjeP.js";
4
- import { m as a } from "./tooltip-Bzl4k00l.js";
4
+ import { h as a } from "./tooltip-BfcDQd2j.js";
5
5
  import { t as o } from "./jsx-runtime-3BD1kT0s.js";
6
6
  import { F as s, P as c } from "./use-restore-focus-CmflVLWJ.js";
7
7
  import { t as l } from "./invariant-DLmrl-hA.js";
@@ -2,8 +2,8 @@ import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { h as t, p as n, s as r } from "./button-0-RqY1DW.js";
3
3
  import { t as i } from "./react-sHtZdjeP.js";
4
4
  import { t as a } from "./compiler-runtime-CldRdSsv.js";
5
- import { p as o } from "./tooltip-Bzl4k00l.js";
6
- import { a as s, s as c } from "./strings-BYJNyKmg.js";
5
+ import { m as o } from "./tooltip-BfcDQd2j.js";
6
+ import { a as s, s as c } from "./strings-BCKpLFNU.js";
7
7
  import { t as l } from "./react-dom-DUmH0GXv.js";
8
8
  import { t as u } from "./jsx-runtime-3BD1kT0s.js";
9
9
  import { D as d, F as f, I as p, L as m, M as h, N as g, O as _, P as v, S as y, j as b, k as x } from "./use-restore-focus-CmflVLWJ.js";
@@ -1727,4 +1727,4 @@ var cr = (e) => {
1727
1727
  }), t[18] = a, t[19] = f, t[20] = p, t[21] = m) : m = t[21], m;
1728
1728
  };
1729
1729
  //#endregion
1730
- export { ir as a, hn as c, ie as d, P as f, nr as i, yn as l, A as m, sr as n, or as o, te as p, rr as r, pn as s, cr as t, un as u };
1730
+ export { ir as a, hn as c, Ft as d, ie as f, A as h, nr as i, yn as l, te as m, sr as n, or as o, P as p, rr as r, pn as s, cr as t, un as u };
@@ -1,7 +1,7 @@
1
1
  import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { t } from "./react-sHtZdjeP.js";
3
- import { m as n } from "./tooltip-Bzl4k00l.js";
4
- import { C as r } from "./select-CcidPqlM.js";
3
+ import { h as n } from "./tooltip-BfcDQd2j.js";
4
+ import { C as r } from "./select-C33FVPPN.js";
5
5
  var i = n("chart-pie", [["path", {
6
6
  d: "M21 12c.552 0 1.005-.449.95-.998a10 10 0 0 0-8.953-8.951c-.55-.055-.998.398-.998.95v8a1 1 0 0 0 1 1z",
7
7
  key: "pzmjnu"
@@ -2,7 +2,7 @@ import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { h as t, s as n } from "./button-0-RqY1DW.js";
3
3
  import { t as r } from "./react-sHtZdjeP.js";
4
4
  import { t as i } from "./compiler-runtime-CldRdSsv.js";
5
- import { m as a } from "./tooltip-Bzl4k00l.js";
5
+ import { h as a } from "./tooltip-BfcDQd2j.js";
6
6
  import { t as o } from "./jsx-runtime-3BD1kT0s.js";
7
7
  var s = a("calendar", [
8
8
  ["path", {
@@ -2,8 +2,8 @@ import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { a as t, g as n, h as r, m as i } from "./button-0-RqY1DW.js";
3
3
  import { t as a } from "./react-sHtZdjeP.js";
4
4
  import { t as o } from "./compiler-runtime-CldRdSsv.js";
5
- import { t as s } from "./tooltip-Bzl4k00l.js";
6
- import { l as c, t as l } from "./config-mwEjAmvP.js";
5
+ import { t as s } from "./tooltip-BfcDQd2j.js";
6
+ import { l as c, t as l } from "./config-CmeG_Pb6.js";
7
7
  import { t as u } from "./jsx-runtime-3BD1kT0s.js";
8
8
  import { n as d } from "./error-banner-DbRFOd2O.js";
9
9
  import { t as f } from "./debounce-DrhhziJv.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.25.3-dev262",
3
+ "version": "0.25.3-dev265",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -44,6 +44,7 @@
44
44
  "@dnd-kit/sortable": "^10.0.0",
45
45
  "@dnd-kit/utilities": "^3.2.2",
46
46
  "@glideapps/glide-data-grid": "6.0.4-alpha24",
47
+ "@glideapps/glide-data-grid-cells": "6.0.4-alpha24",
47
48
  "@hookform/resolvers": "^5.9.1",
48
49
  "@img-comparison-slider/react": "^8.0.2",
49
50
  "@internationalized/date": "^3.12.4",
@@ -1,6 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import glideCss from "@glideapps/glide-data-grid/dist/index.css?inline";
4
+ import glideCellsCss from "@glideapps/glide-data-grid-cells/dist/index.css?inline";
4
5
  import React, { useEffect, useState } from "react";
5
6
  import { z } from "zod";
6
7
  import { inferFieldTypes } from "@/components/data-table/columns";
@@ -15,9 +16,15 @@ import { useAsyncData } from "@/hooks/useAsyncData";
15
16
  import { createPlugin } from "../core/builder";
16
17
  import type { Setter } from "../types";
17
18
  import { columnToFieldTypesSchema } from "./data-frames/schema";
19
+ import { normalizeConfiguredColumns } from "./data-editor/column-types";
18
20
  import { orderColumnFields } from "./data-editor/data-utils";
19
21
  import { applyEditorEdits } from "./data-editor/editor-state";
20
- import type { EditorRow, EditorState, Edits } from "./data-editor/types";
22
+ import type {
23
+ ColumnType,
24
+ EditorRow,
25
+ EditorState,
26
+ Edits,
27
+ } from "./data-editor/types";
21
28
  import { vegaLoadData } from "./vega/loader";
22
29
  import { getVegaFieldTypes } from "./vega/utils";
23
30
 
@@ -30,7 +37,7 @@ const LazyDataEditor = React.lazy(
30
37
  );
31
38
 
32
39
  export const DataEditorPlugin = createPlugin<Edits>("marimo-data-editor", {
33
- cssStyles: [glideCss],
40
+ cssStyles: [glideCss, glideCellsCss],
34
41
  })
35
42
  .withData(
36
43
  z.object({
@@ -48,6 +55,12 @@ export const DataEditorPlugin = createPlugin<Edits>("marimo-data-editor", {
48
55
  fieldTypes: columnToFieldTypesSchema.nullish(),
49
56
  columnNames: z.array(z.string()).default([]),
50
57
  editableColumns: z.union([z.array(z.string()), z.literal("all")]),
58
+ columnTypes: z
59
+ .record(
60
+ z.string(),
61
+ z.union([z.literal("boolean"), z.array(z.string())]),
62
+ )
63
+ .default({}),
51
64
  columnSizingMode: z.enum(["auto", "fit"]).default("auto"), // TODO: Remove this
52
65
  }),
53
66
  )
@@ -61,6 +74,7 @@ export const DataEditorPlugin = createPlugin<Edits>("marimo-data-editor", {
61
74
  edits={props.value}
62
75
  onEdits={props.setValue}
63
76
  editableColumns={props.data.editableColumns}
77
+ columnTypes={props.data.columnTypes}
64
78
  />
65
79
  );
66
80
  });
@@ -72,6 +86,7 @@ interface Props {
72
86
  onEdits: Setter<Edits>;
73
87
  editableColumns: string[] | "all";
74
88
  columnNames: string[];
89
+ columnTypes: Record<string, ColumnType>;
75
90
  }
76
91
 
77
92
  const LoadingDataEditor = (props: Props) => {
@@ -95,14 +110,23 @@ const LoadingDataEditor = (props: Props) => {
95
110
  { handleBigIntAndNumberLike: true },
96
111
  );
97
112
 
113
+ const columnTypes = new Map(Object.entries(props.columnTypes));
114
+ const inferredFields = toFieldTypes(
115
+ props.fieldTypes ?? inferFieldTypes(localData),
116
+ );
117
+ for (const [columnName, columnType] of columnTypes) {
118
+ inferredFields.set(
119
+ columnName,
120
+ columnType === "boolean" ? "boolean" : "string",
121
+ );
122
+ }
123
+
98
124
  return {
99
- data: localData,
100
- columnFields: orderColumnFields(
101
- toFieldTypes(props.fieldTypes ?? inferFieldTypes(localData)),
102
- props.columnNames,
103
- ),
125
+ data: normalizeConfiguredColumns(localData, columnTypes),
126
+ columnFields: orderColumnFields(inferredFields, props.columnNames),
127
+ columnTypes,
104
128
  } satisfies EditorState;
105
- }, [props.fieldTypes, props.columnNames, props.data]);
129
+ }, [props.fieldTypes, props.columnNames, props.columnTypes, props.data]);
106
130
 
107
131
  useEffect(() => {
108
132
  if (loadedState !== undefined) {
@@ -133,6 +157,7 @@ const LoadingDataEditor = (props: Props) => {
133
157
  <LazyDataEditor
134
158
  data={editorState.data}
135
159
  columnFields={editorState.columnFields}
160
+ columnTypes={editorState.columnTypes}
136
161
  editableColumns={props.editableColumns}
137
162
  onAddEdits={(edits) => {
138
163
  setEditorState((state) =>
@@ -21,11 +21,24 @@ vi.mock("../vega/loader", () => ({
21
21
  vi.mock("../data-editor/glide-data-editor", async () => {
22
22
  const React = await import("react");
23
23
  return {
24
- default: (props: { data: EditorRow[] }) =>
24
+ default: (props: { data: EditorRow[]; columnFields: unknown }) =>
25
25
  React.createElement(
26
- "pre",
27
- { "data-testid": "editor-data" },
28
- JSON.stringify(props.data),
26
+ React.Fragment,
27
+ null,
28
+ React.createElement(
29
+ "pre",
30
+ { "data-testid": "editor-data" },
31
+ JSON.stringify(props.data),
32
+ ),
33
+ React.createElement(
34
+ "pre",
35
+ { "data-testid": "editor-fields" },
36
+ JSON.stringify(
37
+ props.columnFields instanceof Map
38
+ ? [...props.columnFields]
39
+ : props.columnFields,
40
+ ),
41
+ ),
29
42
  ),
30
43
  };
31
44
  });
@@ -62,6 +75,19 @@ describe("DataEditorPlugin", () => {
62
75
  expect(result.fieldTypes).toEqual([["geom", ["unknown", "geometry"]]]);
63
76
  });
64
77
 
78
+ it("parses dropdown column types", () => {
79
+ const result = DataEditorPlugin.validator.parse({
80
+ initialValue: { edits: [] },
81
+ label: null,
82
+ data: [],
83
+ fieldTypes: null,
84
+ editableColumns: "all",
85
+ columnTypes: { status: ["open", "closed"] },
86
+ });
87
+
88
+ expect(result.columnTypes).toEqual({ status: ["open", "closed"] });
89
+ });
90
+
65
91
  it("keeps the geometry field type", () => {
66
92
  const result = DataEditorPlugin.validator.parse({
67
93
  initialValue: { edits: [] },
@@ -107,6 +133,69 @@ describe("DataEditorPlugin", () => {
107
133
  });
108
134
  });
109
135
 
136
+ it("normalizes explicitly configured boolean columns", async () => {
137
+ const data = DataEditorPlugin.validator.parse({
138
+ initialValue: { edits: [] },
139
+ label: null,
140
+ data: [{ active: "No" }, { active: "YES" }],
141
+ fieldTypes: null,
142
+ columnNames: ["active"],
143
+ columnTypes: { active: "boolean" },
144
+ editableColumns: "all",
145
+ });
146
+ render(renderPlugin(data, { edits: [] }));
147
+
148
+ await waitFor(() => {
149
+ expect(screen.getByTestId("editor-data")).toHaveTextContent(
150
+ JSON.stringify([{ active: false }, { active: true }]),
151
+ );
152
+ });
153
+ });
154
+
155
+ it("keeps numeric-looking dropdown values as option strings", async () => {
156
+ // Vega's CSV auto-parse turns "1" into 1 when no field types are sent.
157
+ mocks.loadData.mockResolvedValueOnce([{ level: 1 }, { level: 2 }]);
158
+ const data = DataEditorPlugin.validator.parse({
159
+ initialValue: { edits: [] },
160
+ label: null,
161
+ data: "data.csv",
162
+ fieldTypes: null,
163
+ columnNames: ["level"],
164
+ columnTypes: { level: ["1", "2", "3"] },
165
+ editableColumns: "all",
166
+ });
167
+ render(renderPlugin(data, { edits: [] }));
168
+
169
+ await waitFor(() => {
170
+ expect(screen.getByTestId("editor-data")).toHaveTextContent(
171
+ JSON.stringify([{ level: "1" }, { level: "2" }]),
172
+ );
173
+ });
174
+ });
175
+
176
+ it("treats dropdown columns as strings over a numeric field type", async () => {
177
+ mocks.loadData.mockResolvedValueOnce([]);
178
+ const data = DataEditorPlugin.validator.parse({
179
+ initialValue: { edits: [] },
180
+ label: null,
181
+ data: "data.csv",
182
+ fieldTypes: [["status", ["integer", "int64"]]],
183
+ columnNames: ["status"],
184
+ columnTypes: { status: ["open", "closed"] },
185
+ editableColumns: "all",
186
+ });
187
+ render(renderPlugin(data, { edits: [] }));
188
+
189
+ await waitFor(() => {
190
+ expect(screen.getByTestId("editor-fields").textContent).toContain(
191
+ '"string"',
192
+ );
193
+ });
194
+ expect(screen.getByTestId("editor-fields").textContent).not.toContain(
195
+ '"integer"',
196
+ );
197
+ });
198
+
110
199
  it("applies edit updates after data has loaded", async () => {
111
200
  const data = DataEditorPlugin.validator.parse({
112
201
  initialValue: { edits: [] },
@@ -0,0 +1,37 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+ import { vegaLoadData, vegaLoader } from "../../vega/loader";
5
+ import { normalizeConfiguredColumns } from "../column-types";
6
+ import type { EditorRow } from "../types";
7
+
8
+ describe("normalizeConfiguredColumns", () => {
9
+ afterEach(() => {
10
+ vi.restoreAllMocks();
11
+ });
12
+
13
+ it("normalizes large integer boolean values from the CSV loader", async () => {
14
+ vi.spyOn(vegaLoader, "load").mockResolvedValueOnce(
15
+ "done,label\n9007199254740992,01\n-9007199254740992,02\n0,03\n",
16
+ );
17
+
18
+ const data = await vegaLoadData<EditorRow>(
19
+ "data.csv",
20
+ { type: "csv", parse: { done: "integer", label: "string" } },
21
+ { handleBigIntAndNumberLike: true },
22
+ );
23
+
24
+ expect(data).toEqual([
25
+ { done: BigInt("9007199254740992"), label: "01" },
26
+ { done: BigInt("-9007199254740992"), label: "02" },
27
+ { done: 0, label: "03" },
28
+ ]);
29
+ expect(
30
+ normalizeConfiguredColumns(data, new Map([["done", "boolean"]])),
31
+ ).toEqual([
32
+ { done: true, label: "01" },
33
+ { done: true, label: "02" },
34
+ { done: false, label: "03" },
35
+ ]);
36
+ });
37
+ });
@@ -7,7 +7,11 @@ import { applyEditorEdits } from "../editor-state";
7
7
  describe("applyEditorEdits", () => {
8
8
  it("replays edits against an empty table", () => {
9
9
  const result = applyEditorEdits(
10
- { data: [], columnFields: new Map([["value", "unknown"]]) },
10
+ {
11
+ data: [],
12
+ columnFields: new Map([["value", "unknown"]]),
13
+ columnTypes: new Map(),
14
+ },
11
15
  [{ rowIdx: 0, columnId: "value", value: "first" }],
12
16
  );
13
17
 
@@ -22,6 +26,7 @@ describe("applyEditorEdits", () => {
22
26
  ["A", "number"],
23
27
  ["B", "string"],
24
28
  ]) as FieldTypes,
29
+ columnTypes: new Map(),
25
30
  },
26
31
  [
27
32
  { rowIdx: 0, type: "remove" },
@@ -41,6 +46,7 @@ describe("applyEditorEdits", () => {
41
46
  ["A", "string"],
42
47
  ["B", "string"],
43
48
  ]) as FieldTypes,
49
+ columnTypes: new Map(),
44
50
  },
45
51
  [
46
52
  { columnIdx: 0, type: "remove" },
@@ -52,6 +58,27 @@ describe("applyEditorEdits", () => {
52
58
  expect([...result.columnFields]).toEqual([["D", "string"]]);
53
59
  });
54
60
 
61
+ it("preserves categorical configuration on rename and deletes it on removal", () => {
62
+ const state = {
63
+ data: [{ status: "open", other: "x" }],
64
+ columnFields: new Map([
65
+ ["status", "string"],
66
+ ["other", "string"],
67
+ ]) as FieldTypes,
68
+ columnTypes: new Map([["status", ["open", "closed"]]]),
69
+ };
70
+
71
+ const renamed = applyEditorEdits(state, [
72
+ { columnIdx: 0, type: "rename", newName: "state" },
73
+ ]);
74
+ expect([...renamed.columnTypes]).toEqual([["state", ["open", "closed"]]]);
75
+
76
+ const removed = applyEditorEdits(renamed, [
77
+ { columnIdx: 0, type: "remove" },
78
+ ]);
79
+ expect([...removed.columnTypes]).toEqual([]);
80
+ });
81
+
55
82
  it("preserves inserted column order through later edits", () => {
56
83
  const result = applyEditorEdits(
57
84
  {
@@ -60,6 +87,7 @@ describe("applyEditorEdits", () => {
60
87
  ["A", "string"],
61
88
  ["C", "string"],
62
89
  ]) as FieldTypes,
90
+ columnTypes: new Map(),
63
91
  },
64
92
  [
65
93
  {
@@ -81,10 +109,32 @@ describe("applyEditorEdits", () => {
81
109
  ]);
82
110
  });
83
111
 
112
+ it("preserves boolean configuration on rename and deletes it on removal", () => {
113
+ const state = {
114
+ data: [{ A: true, B: "b" }],
115
+ columnFields: new Map([
116
+ ["A", "boolean"],
117
+ ["B", "string"],
118
+ ]) as FieldTypes,
119
+ columnTypes: new Map([["A", "boolean" as const]]),
120
+ };
121
+
122
+ const renamed = applyEditorEdits(state, [
123
+ { columnIdx: 0, type: "rename", newName: "C" },
124
+ ]);
125
+ expect([...renamed.columnTypes]).toEqual([["C", "boolean"]]);
126
+
127
+ const removed = applyEditorEdits(renamed, [
128
+ { columnIdx: 0, type: "remove" },
129
+ ]);
130
+ expect([...removed.columnTypes]).toEqual([]);
131
+ });
132
+
84
133
  it("does not mutate the previous state", () => {
85
134
  const state = {
86
135
  data: [{ A: "before" }],
87
136
  columnFields: new Map([["A", "string"]]) as FieldTypes,
137
+ columnTypes: new Map(),
88
138
  };
89
139
 
90
140
  const result = applyEditorEdits(state, [
@@ -101,6 +151,7 @@ describe("applyEditorEdits", () => {
101
151
  const state = {
102
152
  data: [{ A: "unchanged" }],
103
153
  columnFields: new Map([["A", "string"]]) as FieldTypes,
154
+ columnTypes: new Map(),
104
155
  };
105
156
 
106
157
  expect(