@marimo-team/islands 0.25.3-dev2 → 0.25.3-dev4
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/dist/{ErrorBoundary-DWEpirf3.js → ErrorBoundary-CuU5CuVr.js} +1 -1
- package/dist/{code-visibility-CLNfgCjd.js → code-visibility-Lu0dkrFO.js} +1 -1
- package/dist/{common-fyD_6mT2.js → common-DiWGWDIM.js} +5654 -5583
- package/dist/{data-grid-overlay-editor-BmqASKep.js → data-grid-overlay-editor-ga7ytgbj.js} +1 -1
- package/dist/{feedback-button-fND8YT59.js → feedback-button-DQA-pzxi.js} +2 -2
- package/dist/{glide-data-editor-BkjP14aq.js → glide-data-editor-Bv75MH6g.js} +3 -3
- package/dist/{globals-BN2O0F8N.js → globals-EDQGFIvi.js} +1 -1
- package/dist/main.js +6 -6
- package/dist/{number-overlay-editor-BEBWJnbL.js → number-overlay-editor-BCP3MxhC.js} +1 -1
- package/dist/{reveal-component-DK69bOki.js → reveal-component-DO5BJB9l.js} +2 -2
- package/package.json +1 -1
- package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
- package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
- package/src/components/data-table/context-menu.tsx +125 -26
- package/src/components/data-table/date-filter-inputs.tsx +15 -42
- package/src/components/data-table/utils.ts +18 -0
- package/src/utils/__tests__/dates.test.ts +116 -0
- package/src/utils/dates.ts +94 -0
|
@@ -2,7 +2,7 @@ import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
|
|
|
2
2
|
import { t } from "./react-sHtZdjeP.js";
|
|
3
3
|
import { t as n } from "./react-dom-DUmH0GXv.js";
|
|
4
4
|
import { c as r, l as i, u as a } from "./data-grid-types-BH_r7g7l.js";
|
|
5
|
-
import { a as o, i as s, n as c, r as l } from "./glide-data-editor-
|
|
5
|
+
import { a as o, i as s, n as c, r as l } from "./glide-data-editor-Bv75MH6g.js";
|
|
6
6
|
//#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_aca11a353ce3b112922852200b9c160f/node_modules/@glideapps/glide-data-grid/dist/esm/internal/data-grid-overlay-editor/data-grid-overlay-editor-style.js
|
|
7
7
|
var u = /* @__PURE__ */ e(t(), 1), d = /* @__PURE__ */ e(n(), 1), f = /*#__PURE__*/ o("div")({
|
|
8
8
|
name: "DataGridOverlayEditorStyle",
|
|
@@ -5,12 +5,12 @@ import { t as i } from "./compiler-runtime-CldRdSsv.js";
|
|
|
5
5
|
import { t as a } from "./copy-icon-Ft7t9bqz.js";
|
|
6
6
|
import { Ct as o, Ft as s, H as c, L as l, Lt as u, V as d, d as f, un as p } from "./cells-C8iCYvLU.js";
|
|
7
7
|
import { b as m } from "./select-BButFi-d.js";
|
|
8
|
-
import { n as h, t as ee } from "./globals-
|
|
8
|
+
import { n as h, t as ee } from "./globals-EDQGFIvi.js";
|
|
9
9
|
import { t as g } from "./jsx-runtime-3BD1kT0s.js";
|
|
10
10
|
import { C as _, _ as te, i as v } from "./useTheme-CRnjkku0.js";
|
|
11
11
|
import { a as ne, i as re, n as ie, r as ae } from "./dialog-CEBFZNyc.js";
|
|
12
12
|
import { t as oe } from "./useAsyncData-Deh5kszT.js";
|
|
13
|
-
import { t as se } from "./code-visibility-
|
|
13
|
+
import { t as se } from "./code-visibility-Lu0dkrFO.js";
|
|
14
14
|
import { i as ce, n as le } from "./error-entries-CzoYkIQZ.js";
|
|
15
15
|
//#region src/components/ui/skeleton.tsx
|
|
16
16
|
var y = /* @__PURE__ */ e(r(), 1), b = i(), x = g(), ue = (e) => {
|
|
@@ -6,7 +6,7 @@ import { i as s, n as c, r as l } from "./copy-icon-Ft7t9bqz.js";
|
|
|
6
6
|
import { C as u, c as d, i as f, n as p, s as m, t as h, x as g } from "./select-BButFi-d.js";
|
|
7
7
|
import { d as _, h as v, i as y, l as b, n as x, o as S, s as C, t as w, u as T } from "./dropdown-menu-CP_qCYHC.js";
|
|
8
8
|
import { c as E, d as D, l as O, n as k, o as A, s as ee, t as j, u as te } from "./glide-utils-DJyqh0EM.js";
|
|
9
|
-
import { n as ne, t as re } from "./ErrorBoundary-
|
|
9
|
+
import { n as ne, t as re } from "./ErrorBoundary-CuU5CuVr.js";
|
|
10
10
|
import { t as M } from "./react-dom-DUmH0GXv.js";
|
|
11
11
|
import { t as N } from "./jsx-runtime-3BD1kT0s.js";
|
|
12
12
|
import { t as ie } from "./label-BDKF3Afc.js";
|
|
@@ -7062,7 +7062,7 @@ function rs(e, t, n, r, i) {
|
|
|
7062
7062
|
}
|
|
7063
7063
|
//#endregion
|
|
7064
7064
|
//#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_aca11a353ce3b112922852200b9c160f/node_modules/@glideapps/glide-data-grid/dist/esm/data-editor/data-editor.js
|
|
7065
|
-
var is = /* @__PURE__ */ e(_r(), 1), as = /* @__PURE__ */ e(xr(), 1), os = K.lazy(async () => await import("./data-grid-overlay-editor-
|
|
7065
|
+
var is = /* @__PURE__ */ e(_r(), 1), as = /* @__PURE__ */ e(xr(), 1), os = K.lazy(async () => await import("./data-grid-overlay-editor-ga7ytgbj.js")), ss = 0;
|
|
7066
7066
|
function cs(e) {
|
|
7067
7067
|
return (0, is.default)((0, as.default)((0, as.default)(e).filter((e) => e.span !== void 0).map((e) => (0, Za.default)((e.span?.[0] ?? 0) + 1, (e.span?.[1] ?? 0) + 1))));
|
|
7068
7068
|
}
|
|
@@ -9293,7 +9293,7 @@ function Vs(e, t, n, r, i) {
|
|
|
9293
9293
|
}
|
|
9294
9294
|
//#endregion
|
|
9295
9295
|
//#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_aca11a353ce3b112922852200b9c160f/node_modules/@glideapps/glide-data-grid/dist/esm/cells/number-cell.js
|
|
9296
|
-
var Hs = K.lazy(async () => await import("./number-overlay-editor-
|
|
9296
|
+
var Hs = K.lazy(async () => await import("./number-overlay-editor-BCP3MxhC.js")), Us = {
|
|
9297
9297
|
getAccessibilityString: (e) => e.data?.toString() ?? "",
|
|
9298
9298
|
kind: W.Number,
|
|
9299
9299
|
needsHover: (e) => e.hoverEffect === !0,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { h as e } from "./tooltip-BO1REbqs.js";
|
|
2
2
|
import { _ as t } from "./useTheme-CRnjkku0.js";
|
|
3
|
-
import { r as n } from "./code-visibility-
|
|
3
|
+
import { r as n } from "./code-visibility-Lu0dkrFO.js";
|
|
4
4
|
var r = e("triangle-alert", [
|
|
5
5
|
["path", {
|
|
6
6
|
d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",
|
package/dist/main.js
CHANGED
|
@@ -7,7 +7,7 @@ import { C as w, F as T, G as E, I as D, J as O, K as k, L as A, P as j, R as M,
|
|
|
7
7
|
import { a as oe, d as se, f as ce, h as V, i as le, n as ue, o as de, r as fe, t as pe } from "./tooltip-BO1REbqs.js";
|
|
8
8
|
import { $ as me, $t as he, B as ge, Bt as _e, C as ve, Ct as ye, D as be, Dt as xe, Et as Se, F as Ce, G as we, Gt as Te, Ht as Ee, I as De, J as Oe, Jt as ke, Kt as Ae, L as je, O as Me, Ot as Ne, P as Pe, Pt as Fe, Q as Ie, Qt as Le, Rt as Re, S as ze, St as Be, T as Ve, Tt as He, U as Ue, W as We, Wt as Ge, bt as Ke, c as qe, cn as Je, ct as Ye, d as Xe, dn as Ze, dt as Qe, en as $e, f as et, fn as tt, ft as nt, g as rt, gt as it, h as at, hn as ot, k as st, l as ct, ln as lt, m as ut, mn as dt, n as ft, o as pt, on as mt, ot as ht, p as gt, q as _t, qt as vt, r as yt, rn as bt, rt as xt, sn as St, st as Ct, tn as wt, u as Tt, v as Et, w as Dt, wt as Ot, zt as kt } from "./cells-C8iCYvLU.js";
|
|
9
9
|
import { D as At, E as jt, O as Mt, S as Nt, T as Pt, _ as Ft, a as It, c as Lt, d as Rt, f as zt, g as Bt, h as Vt, i as Ht, l as Ut, m as Wt, n as Gt, o as Kt, p as qt, r as Jt, s as Yt, t as Xt, u as Zt, v as Qt, w as $t, x as en, y as tn } from "./spec-BwarBpUJ.js";
|
|
10
|
-
import { $t as nn, A as rn, At as an, B as on, Bt as sn, C as cn, Ct as ln, D as un, Dt as dn, E as fn, Et as pn, F as mn, Ft as hn, G as gn, Gt as _n, H as vn, Ht as yn, I as bn, It as xn, J as Sn, Jt as Cn, K as wn, Kt as Tn, L as En, Lt as Dn, M as On, Mt as kn, N as An, Nt as jn, O as Mn, Ot as Nn, P as Pn, Pt as Fn, Qt as In, R as Ln, Rt as Rn, S as zn, St as Bn, T as Vn, Tt as Hn, U as Un, Ut as Wn, V as Gn, Vt as Kn, W as qn, Wt as Jn, X as Yn, Xt as Xn, Y as Zn, Yt as Qn, Zt as $n, _ as er, _t as tr, an as nr, at as rr, b as ir, c as ar, cn as or, ct as sr, d as cr, dn as lr, dt as ur, en as dr, et as fr, f as pr, fn as mr, ft as hr, g as gr, gt as _r, h as vr, hn as yr, ht as br, in as xr, it as Sr, j as Cr, jt as wr, k as Tr, kt as Er, ln as Dr, lt as Or, m as kr, mn as Ar, mt as jr, nn as Mr, nt as Nr, ot as Pr, p as Fr, pn as Ir, pt as Lr, q as Rr, qt as zr, r as Br, rn as Vr, rt as Hr, sn as Ur, st as Wr, t as Gr, tn as Kr, tt as qr, u as Jr, un as Yr, ut as Xr, v as Zr, vt as Qr, w as $r, wt as ei, x as ti, y as ni, yt as ri, z as ii, zt as ai } from "./common-
|
|
10
|
+
import { $t as nn, A as rn, At as an, B as on, Bt as sn, C as cn, Ct as ln, D as un, Dt as dn, E as fn, Et as pn, F as mn, Ft as hn, G as gn, Gt as _n, H as vn, Ht as yn, I as bn, It as xn, J as Sn, Jt as Cn, K as wn, Kt as Tn, L as En, Lt as Dn, M as On, Mt as kn, N as An, Nt as jn, O as Mn, Ot as Nn, P as Pn, Pt as Fn, Qt as In, R as Ln, Rt as Rn, S as zn, St as Bn, T as Vn, Tt as Hn, U as Un, Ut as Wn, V as Gn, Vt as Kn, W as qn, Wt as Jn, X as Yn, Xt as Xn, Y as Zn, Yt as Qn, Zt as $n, _ as er, _t as tr, an as nr, at as rr, b as ir, c as ar, cn as or, ct as sr, d as cr, dn as lr, dt as ur, en as dr, et as fr, f as pr, fn as mr, ft as hr, g as gr, gt as _r, h as vr, hn as yr, ht as br, in as xr, it as Sr, j as Cr, jt as wr, k as Tr, kt as Er, ln as Dr, lt as Or, m as kr, mn as Ar, mt as jr, nn as Mr, nt as Nr, ot as Pr, p as Fr, pn as Ir, pt as Lr, q as Rr, qt as zr, r as Br, rn as Vr, rt as Hr, sn as Ur, st as Wr, t as Gr, tn as Kr, tt as qr, u as Jr, un as Yr, ut as Xr, v as Zr, vt as Qr, w as $r, wt as ei, x as ti, y as ni, yt as ri, z as ii, zt as ai } from "./common-DiWGWDIM.js";
|
|
11
11
|
import { c as oi, i as si, n as ci, o as li, t as ui } from "./useLifecycle-j7D9uD1o.js";
|
|
12
12
|
import { t as di } from "./check-EDwSwP89.js";
|
|
13
13
|
import { A as fi, C as pi, D as mi, E as hi, M as gi, O as _i, S as vi, T as yi, a as bi, b as xi, c as Si, i as Ci, k as wi, l as Ti, n as Ei, o as Di, r as Oi, s as ki, t as Ai, w as ji, x as Mi } from "./select-BButFi-d.js";
|
|
@@ -15,8 +15,8 @@ import { $ as Ni, C as Pi, Et as Fi, J as Ii, K as Li, R as Ri, S as zi, U as Bi
|
|
|
15
15
|
import { S as ua, a as da, b as fa, c as pa, d as ma, f as ha, g as ga, l as _a, m as va, p as ya, s as ba, v as xa, y as Sa } from "./toDate-BNJf01u1.js";
|
|
16
16
|
import { a as Ca, c as wa, d as Ta, f as Ea, i as Da, l as Oa, n as ka, o as Aa, r as ja, s as Ma, t as Na } from "./process-output-e4jj9Ijt.js";
|
|
17
17
|
import { a as Pa, c as Fa, d as Ia, i as La, l as Ra, r as za, u as Ba } from "./glide-utils-DJyqh0EM.js";
|
|
18
|
-
import { n as Va, t as Ha } from "./ErrorBoundary-
|
|
19
|
-
import { n as Ua, t as Wa } from "./globals-
|
|
18
|
+
import { n as Va, t as Ha } from "./ErrorBoundary-CuU5CuVr.js";
|
|
19
|
+
import { n as Ua, t as Wa } from "./globals-EDQGFIvi.js";
|
|
20
20
|
import { t as Ga } from "./react-dom-DUmH0GXv.js";
|
|
21
21
|
import { t as Ka } from "./jsx-runtime-3BD1kT0s.js";
|
|
22
22
|
import { B as qa, C as H, Ct as Ja, D as Ya, E as Xa, F as U, G as Za, H as Qa, I as $a, L as eo, M as W, N as to, O as no, P as ro, Q as io, R as ao, S as G, St as oo, Tt as so, U as co, W as lo, Y as uo, Z as fo, _ as po, _t as mo, a as ho, b as go, bt as _o, c as vo, d as yo, f as bo, g as xo, h as So, ht as Co, i as wo, j as K, k as To, l as Eo, m as Do, n as Oo, o as ko, p as Ao, r as jo, s as Mo, u as No, ut as Po, v as Fo, vt as Io, w as Lo, wt as Ro, x as zo, xt as Bo, y as Vo, yt as Ho } from "./use-restore-focus-BEM7sFWl.js";
|
|
@@ -39,7 +39,7 @@ import { t as Ms } from "./useAsyncData-Deh5kszT.js";
|
|
|
39
39
|
import { i as Ns, n as Ps, r as Fs } from "./formats-BGh2thRc.js";
|
|
40
40
|
import { M as Is, N as Ls } from "./vega-loader.browser-DNST6uVf.js";
|
|
41
41
|
import { a as Rs, n as zs } from "./loader-BQOfjTIJ.js";
|
|
42
|
-
import { c as Bs, d as Vs, i as Hs, l as Us, o as Ws, s as Gs, t as Ks, u as qs } from "./code-visibility-
|
|
42
|
+
import { c as Bs, d as Vs, i as Hs, l as Us, o as Ws, s as Gs, t as Ks, u as qs } from "./code-visibility-Lu0dkrFO.js";
|
|
43
43
|
import { t as Js } from "./useDeepCompareMemoize-DXwzRINI.js";
|
|
44
44
|
import { t as Ys } from "./esm-D2gTsXEV.js";
|
|
45
45
|
//#region ../node_modules/.pnpm/iconify-icon@2.3.0/node_modules/iconify-icon/dist/iconify-icon.mjs
|
|
@@ -10533,7 +10533,7 @@ function VC(e, t) {
|
|
|
10533
10533
|
}
|
|
10534
10534
|
//#endregion
|
|
10535
10535
|
//#region src/plugins/impl/DataEditorPlugin.tsx
|
|
10536
|
-
var HC = J.lazy(() => import("./glide-data-editor-
|
|
10536
|
+
var HC = J.lazy(() => import("./glide-data-editor-Bv75MH6g.js").then((e) => e.t)), UC = wS("marimo-data-editor", { cssStyles: [qS] }).withData(W({
|
|
10537
10537
|
initialValue: W({ edits: G(W({
|
|
10538
10538
|
rowIdx: K(),
|
|
10539
10539
|
columnId: U(),
|
|
@@ -30107,7 +30107,7 @@ function OR(e) {
|
|
|
30107
30107
|
}
|
|
30108
30108
|
//#endregion
|
|
30109
30109
|
//#region src/components/editor/renderers/slides-layout/slides-layout.tsx
|
|
30110
|
-
var kR = J.lazy(() => import("./reveal-component-
|
|
30110
|
+
var kR = J.lazy(() => import("./reveal-component-DO5BJB9l.js")), AR = (e) => {
|
|
30111
30111
|
let t = (0, X.c)(33), { layout: n, setLayout: r, cells: i, mode: a } = e, o = ns(ga), s = a === "read" || o, c = ns(gt) > 1, [l, u] = (0, J.useState)(null), d;
|
|
30112
30112
|
t[0] !== i || t[1] !== n ? (d = EP(i, n), t[0] = i, t[1] = n, t[2] = d) : d = t[2];
|
|
30113
30113
|
let { slideCells: f, skippedIds: p, noOutputIds: m, slideTypes: h, startCellIndex: g } = d, _;
|
|
@@ -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-
|
|
3
|
+
import { a as n } from "./glide-data-editor-Bv75MH6g.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_aca11a353ce3b112922852200b9c160f/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",
|
|
@@ -4,14 +4,14 @@ import { t as o } from "./react-sHtZdjeP.js";
|
|
|
4
4
|
import { t as s } from "./compiler-runtime-CldRdSsv.js";
|
|
5
5
|
import { h as c, t as l } from "./tooltip-BO1REbqs.js";
|
|
6
6
|
import { w as u } from "./cells-C8iCYvLU.js";
|
|
7
|
-
import { $ as d, Q as f, Z as p, an as m, i as h, l as g, ln as _, n as v, on as y, r as b, t as x, u as S } from "./common-
|
|
7
|
+
import { $ as d, Q as f, Z as p, an as m, i as h, l as g, ln as _, n as v, on as y, r as b, t as x, u as S } from "./common-DiWGWDIM.js";
|
|
8
8
|
import { M as C } from "./select-BButFi-d.js";
|
|
9
9
|
import { q as ee } from "./dropdown-menu-CP_qCYHC.js";
|
|
10
10
|
import { g as w } from "./toDate-BNJf01u1.js";
|
|
11
11
|
import { t as T } from "./jsx-runtime-3BD1kT0s.js";
|
|
12
12
|
import { q as E } from "./use-restore-focus-BEM7sFWl.js";
|
|
13
13
|
import { A as D, C as O, b as k } from "./useTheme-CRnjkku0.js";
|
|
14
|
-
import { a as A, n as te, t as ne } from "./code-visibility-
|
|
14
|
+
import { a as A, n as te, t as ne } from "./code-visibility-Lu0dkrFO.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",
|
package/package.json
CHANGED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
4
|
+
import { buildCellFilter } from "../context-menu";
|
|
5
|
+
|
|
6
|
+
describe("buildCellFilter", () => {
|
|
7
|
+
// Use a non-UTC, non-DST-ambiguous offset so date-only parsing bugs
|
|
8
|
+
// (UTC vs. local midnight) can't hide behind a host machine that
|
|
9
|
+
// happens to run in UTC.
|
|
10
|
+
let originalTimezone: string | undefined;
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
originalTimezone = process.env.TZ;
|
|
13
|
+
process.env.TZ = "America/New_York";
|
|
14
|
+
});
|
|
15
|
+
afterAll(() => {
|
|
16
|
+
process.env.TZ = originalTimezone;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("builds an equality filter for a normal date value", () => {
|
|
20
|
+
const filter = buildCellFilter({
|
|
21
|
+
type: "date",
|
|
22
|
+
value: "2024-01-15",
|
|
23
|
+
comparison: "eq",
|
|
24
|
+
dataType: "date",
|
|
25
|
+
dtype: "date",
|
|
26
|
+
});
|
|
27
|
+
expect(filter).toEqual({
|
|
28
|
+
type: "date",
|
|
29
|
+
operator: "==",
|
|
30
|
+
// Must be local midnight, not `new Date("2024-01-15")` (UTC
|
|
31
|
+
// midnight), or the filter shifts by a day in UTC- timezones.
|
|
32
|
+
value: new Date(2024, 0, 15),
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("returns undefined for an empty-string date value in a non-temporal-looking context", () => {
|
|
37
|
+
const filter = buildCellFilter({
|
|
38
|
+
type: "date",
|
|
39
|
+
value: "",
|
|
40
|
+
comparison: "eq",
|
|
41
|
+
dataType: "date",
|
|
42
|
+
dtype: "date",
|
|
43
|
+
});
|
|
44
|
+
expect(filter).toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("returns undefined for an unparsable date value instead of an Invalid Date filter", () => {
|
|
48
|
+
const filter = buildCellFilter({
|
|
49
|
+
type: "date",
|
|
50
|
+
value: "not-a-date",
|
|
51
|
+
comparison: "eq",
|
|
52
|
+
dataType: "date",
|
|
53
|
+
dtype: "date",
|
|
54
|
+
});
|
|
55
|
+
expect(filter).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("builds an equality filter for a normal datetime value", () => {
|
|
59
|
+
const filter = buildCellFilter({
|
|
60
|
+
type: "datetime",
|
|
61
|
+
value: "2024-01-15 08:30:00",
|
|
62
|
+
comparison: "eq",
|
|
63
|
+
dataType: "datetime",
|
|
64
|
+
dtype: "datetime64[ns]",
|
|
65
|
+
});
|
|
66
|
+
expect(filter).toEqual({
|
|
67
|
+
type: "datetime",
|
|
68
|
+
operator: "==",
|
|
69
|
+
value: new Date(2024, 0, 15, 8, 30, 0),
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("returns undefined for an unparsable datetime value", () => {
|
|
74
|
+
const filter = buildCellFilter({
|
|
75
|
+
type: "datetime",
|
|
76
|
+
value: "not-a-datetime",
|
|
77
|
+
comparison: "eq",
|
|
78
|
+
dataType: "datetime",
|
|
79
|
+
dtype: "datetime64[ns]",
|
|
80
|
+
});
|
|
81
|
+
expect(filter).toBeUndefined();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("builds an equality filter for a normal time value", () => {
|
|
85
|
+
const filter = buildCellFilter({
|
|
86
|
+
type: "time",
|
|
87
|
+
value: "08:30:00",
|
|
88
|
+
comparison: "eq",
|
|
89
|
+
dataType: "time",
|
|
90
|
+
});
|
|
91
|
+
expect(filter).toEqual({
|
|
92
|
+
type: "time",
|
|
93
|
+
operator: "==",
|
|
94
|
+
value: new Date(1970, 0, 1, 8, 30, 0),
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("returns undefined instead of an unparsable time value", () => {
|
|
99
|
+
const filter = buildCellFilter({
|
|
100
|
+
type: "time",
|
|
101
|
+
value: "25:00:00",
|
|
102
|
+
comparison: "eq",
|
|
103
|
+
dataType: "time",
|
|
104
|
+
});
|
|
105
|
+
expect(filter).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("maps a NaT date value to is_null instead of an invalid Date filter", () => {
|
|
109
|
+
const filter = buildCellFilter({
|
|
110
|
+
type: "date",
|
|
111
|
+
value: "NaT",
|
|
112
|
+
comparison: "eq",
|
|
113
|
+
dataType: "date",
|
|
114
|
+
dtype: "date",
|
|
115
|
+
});
|
|
116
|
+
expect(filter).toEqual({ type: "date", operator: "is_null" });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("maps a NaT datetime value to is_not_null for the neq comparison", () => {
|
|
120
|
+
const filter = buildCellFilter({
|
|
121
|
+
type: "datetime",
|
|
122
|
+
value: "NaT",
|
|
123
|
+
comparison: "neq",
|
|
124
|
+
dataType: "datetime",
|
|
125
|
+
dtype: "datetime64[ns]",
|
|
126
|
+
});
|
|
127
|
+
expect(filter).toEqual({ type: "datetime", operator: "is_not_null" });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("does not treat the string 'NaT' in a non-temporal column as a sentinel", () => {
|
|
131
|
+
const filter = buildCellFilter({
|
|
132
|
+
type: "text",
|
|
133
|
+
value: "NaT",
|
|
134
|
+
comparison: "eq",
|
|
135
|
+
dataType: "string",
|
|
136
|
+
});
|
|
137
|
+
expect(filter).toEqual({ type: "text", operator: "equals", text: "NaT" });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("maps an actual null value to is_null", () => {
|
|
141
|
+
const filter = buildCellFilter({
|
|
142
|
+
type: "number",
|
|
143
|
+
value: null,
|
|
144
|
+
comparison: "eq",
|
|
145
|
+
dataType: "number",
|
|
146
|
+
});
|
|
147
|
+
expect(filter).toEqual({ type: "number", operator: "is_null" });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("builds a literal (always-false) equality filter for a NaN number value", () => {
|
|
151
|
+
// NaN is a real float value, not a missing-value marker like null/NaT,
|
|
152
|
+
// and the backend's is_null() (narwhals/polars semantics) doesn't match
|
|
153
|
+
// it, so it falls through to a normal (if unsatisfiable) == filter.
|
|
154
|
+
const filter = buildCellFilter({
|
|
155
|
+
type: "number",
|
|
156
|
+
value: Number.NaN,
|
|
157
|
+
comparison: "eq",
|
|
158
|
+
dataType: "number",
|
|
159
|
+
});
|
|
160
|
+
expect(filter).toEqual({
|
|
161
|
+
type: "number",
|
|
162
|
+
operator: "==",
|
|
163
|
+
value: Number.NaN,
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("builds a literal equality filter for a normal number value", () => {
|
|
168
|
+
const filter = buildCellFilter({
|
|
169
|
+
type: "number",
|
|
170
|
+
value: 42,
|
|
171
|
+
comparison: "eq",
|
|
172
|
+
dataType: "number",
|
|
173
|
+
});
|
|
174
|
+
expect(filter).toEqual({ type: "number", operator: "==", value: 42 });
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
import { dateToLocalISODate } from "@/utils/dates";
|
|
4
|
-
import { parsePastedRange } from "../date-filter-inputs";
|
|
4
|
+
import { parsePastedDate, parsePastedRange } from "../date-filter-inputs";
|
|
5
|
+
|
|
6
|
+
describe("parsePastedDate", () => {
|
|
7
|
+
it("parses a time-only string for the time filter type", () => {
|
|
8
|
+
const result = parsePastedDate("time", "2:30 PM");
|
|
9
|
+
expect(result).toEqual(new Date(1970, 0, 1, 14, 30));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("parses a local ISO datetime for the datetime filter type", () => {
|
|
13
|
+
const result = parsePastedDate("datetime", "2026-02-01T08:30:00");
|
|
14
|
+
expect(result).toEqual(new Date(2026, 1, 1, 8, 30, 0));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("falls back to the Date constructor for other formats", () => {
|
|
18
|
+
const result = parsePastedDate("datetime", "Feb 1, 2026 08:30:00");
|
|
19
|
+
expect(result).toBeDefined();
|
|
20
|
+
expect(result?.getFullYear()).toBe(2026);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("returns undefined for an empty or unparsable string", () => {
|
|
24
|
+
expect(parsePastedDate("date", "")).toBeUndefined();
|
|
25
|
+
expect(parsePastedDate("time", "not-a-time")).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
5
28
|
|
|
6
29
|
describe("parsePastedRange", () => {
|
|
7
30
|
it.each([
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
import type { DataType } from "@/core/kernel/messages";
|
|
4
|
+
import { assertNever } from "@/utils/assertNever";
|
|
5
|
+
import { copyToClipboard } from "@/utils/copy";
|
|
6
|
+
import {
|
|
7
|
+
parseLocalISODate,
|
|
8
|
+
parseLocalISODateTime,
|
|
9
|
+
parseTimeToDate,
|
|
10
|
+
} from "@/utils/dates";
|
|
11
|
+
import { Logger } from "@/utils/Logger";
|
|
3
12
|
import type { Cell } from "@tanstack/react-table";
|
|
4
13
|
import { useAtomValue } from "jotai";
|
|
5
14
|
import { CopyIcon, FilterIcon, SquareStack } from "lucide-react";
|
|
6
15
|
import type { RefObject } from "react";
|
|
7
16
|
import useEvent from "react-use-event-hook";
|
|
8
|
-
import { copyToClipboard } from "@/utils/copy";
|
|
9
|
-
import { Logger } from "@/utils/Logger";
|
|
10
17
|
import {
|
|
11
18
|
ContextMenu,
|
|
12
19
|
ContextMenuContent,
|
|
@@ -16,13 +23,94 @@ import {
|
|
|
16
23
|
ContextMenuTrigger,
|
|
17
24
|
} from "../ui/context-menu";
|
|
18
25
|
import { DATA_CELL_ID } from "./cell-utils";
|
|
19
|
-
import {
|
|
20
|
-
Filter,
|
|
21
|
-
isMembershipFilterType,
|
|
22
|
-
type MembershipFilterType,
|
|
23
|
-
} from "./filters";
|
|
26
|
+
import { type FilterType, Filter } from "./filters";
|
|
24
27
|
import { selectedCellsAtom } from "./range-focus/atoms";
|
|
25
|
-
import { getClipboardContent, getRawValue } from "./utils";
|
|
28
|
+
import { getClipboardContent, getRawValue, isNullishSentinel } from "./utils";
|
|
29
|
+
|
|
30
|
+
type Comparison = "eq" | "neq";
|
|
31
|
+
|
|
32
|
+
const symbolOp = {
|
|
33
|
+
eq: "==",
|
|
34
|
+
neq: "!=",
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
const textOp = {
|
|
38
|
+
eq: "equals",
|
|
39
|
+
neq: "does_not_equal",
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
export function buildCellFilter({
|
|
43
|
+
type,
|
|
44
|
+
value,
|
|
45
|
+
comparison = "eq",
|
|
46
|
+
dataType,
|
|
47
|
+
dtype,
|
|
48
|
+
}: {
|
|
49
|
+
type: FilterType;
|
|
50
|
+
value: unknown;
|
|
51
|
+
comparison?: Comparison;
|
|
52
|
+
dataType?: DataType;
|
|
53
|
+
dtype?: string;
|
|
54
|
+
}) {
|
|
55
|
+
if (isNullishSentinel(value, dataType, dtype)) {
|
|
56
|
+
return { type, operator: comparison === "eq" ? "is_null" : "is_not_null" };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
switch (type) {
|
|
60
|
+
case "number":
|
|
61
|
+
if (typeof value !== "number") {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return Filter.number({
|
|
66
|
+
operator: symbolOp[comparison],
|
|
67
|
+
value,
|
|
68
|
+
});
|
|
69
|
+
case "text":
|
|
70
|
+
return Filter.text({
|
|
71
|
+
operator: textOp[comparison],
|
|
72
|
+
text: value as string,
|
|
73
|
+
});
|
|
74
|
+
case "date": {
|
|
75
|
+
const date = parseLocalISODate(value as string);
|
|
76
|
+
if (!date) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
return Filter.date({
|
|
80
|
+
operator: symbolOp[comparison],
|
|
81
|
+
value: date,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
case "datetime": {
|
|
85
|
+
const date = parseLocalISODateTime(value as string);
|
|
86
|
+
if (!date) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
return Filter.datetime({
|
|
90
|
+
operator: symbolOp[comparison],
|
|
91
|
+
value: date,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
case "time": {
|
|
95
|
+
const time = parseTimeToDate(value as string);
|
|
96
|
+
if (!time) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
return Filter.time({
|
|
100
|
+
operator: symbolOp[comparison],
|
|
101
|
+
value: time,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
case "boolean": {
|
|
105
|
+
const target = comparison === "eq" ? value : !(value as boolean);
|
|
106
|
+
return Filter.boolean({
|
|
107
|
+
operator: target ? "is_true" : "is_false",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
default:
|
|
111
|
+
assertNever(type);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
26
114
|
|
|
27
115
|
export const DataTableContextMenu = <TData,>({
|
|
28
116
|
contextMenuRef,
|
|
@@ -102,19 +190,34 @@ export const CellContextMenu = <TData,>({
|
|
|
102
190
|
|
|
103
191
|
const column = cell.column;
|
|
104
192
|
const filterType = column.columnDef.meta?.filterType;
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
193
|
+
const dataType = column.columnDef.meta?.dataType;
|
|
194
|
+
const dtype = column.columnDef.meta?.dtype;
|
|
195
|
+
|
|
196
|
+
const canFilter = Boolean(filterType && column.getCanFilter());
|
|
197
|
+
const buildFilter = (comparison: Comparison) =>
|
|
198
|
+
canFilter
|
|
199
|
+
? buildCellFilter({
|
|
200
|
+
type: filterType as FilterType,
|
|
201
|
+
value: rawValue,
|
|
202
|
+
comparison,
|
|
203
|
+
dataType,
|
|
204
|
+
dtype,
|
|
205
|
+
})
|
|
108
206
|
: undefined;
|
|
207
|
+
const eqFilter = buildFilter("eq");
|
|
208
|
+
const canFilterByValue = eqFilter !== undefined;
|
|
209
|
+
|
|
210
|
+
const handleFilterCellByValue = (comparison: Comparison) => {
|
|
211
|
+
if (!filterType) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const filter = comparison === "eq" ? eqFilter : buildFilter("neq");
|
|
216
|
+
|
|
217
|
+
if (!filter) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
109
220
|
|
|
110
|
-
const handleFilterCell = (
|
|
111
|
-
type: MembershipFilterType,
|
|
112
|
-
operator: "in" | "not_in",
|
|
113
|
-
) => {
|
|
114
|
-
const filter =
|
|
115
|
-
type === "number"
|
|
116
|
-
? Filter.number({ operator, values: [rawValue] })
|
|
117
|
-
: Filter.text({ operator, values: [rawValue] });
|
|
118
221
|
column.setFilterValue(filter);
|
|
119
222
|
};
|
|
120
223
|
|
|
@@ -130,18 +233,14 @@ export const CellContextMenu = <TData,>({
|
|
|
130
233
|
Copy selected cells
|
|
131
234
|
</ContextMenuItem>
|
|
132
235
|
)}
|
|
133
|
-
{
|
|
236
|
+
{canFilterByValue && (
|
|
134
237
|
<>
|
|
135
238
|
<ContextMenuSeparator />
|
|
136
|
-
<ContextMenuItem
|
|
137
|
-
onClick={() => handleFilterCell(membershipFilterType, "in")}
|
|
138
|
-
>
|
|
239
|
+
<ContextMenuItem onClick={() => handleFilterCellByValue("eq")}>
|
|
139
240
|
<FilterIcon className="mo-dropdown-icon h-3 w-3" />
|
|
140
241
|
Filter by this value
|
|
141
242
|
</ContextMenuItem>
|
|
142
|
-
<ContextMenuItem
|
|
143
|
-
onClick={() => handleFilterCell(membershipFilterType, "not_in")}
|
|
144
|
-
>
|
|
243
|
+
<ContextMenuItem onClick={() => handleFilterCellByValue("neq")}>
|
|
145
244
|
<FilterIcon className="mo-dropdown-icon h-3 w-3" />
|
|
146
245
|
Remove rows with this value
|
|
147
246
|
</ContextMenuItem>
|
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
dateToLocalISODate,
|
|
15
15
|
dateToLocalISODateTime,
|
|
16
16
|
dateToLocalISOTime,
|
|
17
|
+
parseLocalISODate,
|
|
18
|
+
parseLocalISODateTime,
|
|
19
|
+
parseTimeToDate,
|
|
17
20
|
} from "@/utils/dates";
|
|
18
21
|
import type { FilterType } from "./filters";
|
|
19
22
|
|
|
@@ -72,53 +75,23 @@ export function parsePastedDate(
|
|
|
72
75
|
return undefined;
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (timeMatch) {
|
|
80
|
-
const [, hStr, mStr, sStr, ampm] = timeMatch;
|
|
81
|
-
let hour = Number.parseInt(hStr, 10);
|
|
82
|
-
const minute = Number.parseInt(mStr, 10);
|
|
83
|
-
const second = sStr ? Number.parseInt(sStr, 10) : 0;
|
|
84
|
-
if (ampm) {
|
|
85
|
-
const isPm = ampm.toLowerCase() === "pm";
|
|
86
|
-
if (hour === 12) {
|
|
87
|
-
hour = isPm ? 12 : 0;
|
|
88
|
-
} else if (isPm) {
|
|
89
|
-
hour += 12;
|
|
90
|
-
}
|
|
78
|
+
if (filterType === "time") {
|
|
79
|
+
const date = parseTimeToDate(trimmed);
|
|
80
|
+
if (date) {
|
|
81
|
+
return date;
|
|
91
82
|
}
|
|
92
|
-
return new Date(1970, 0, 1, hour, minute, second);
|
|
93
83
|
}
|
|
94
84
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
Number.parseInt(m, 10) - 1,
|
|
101
|
-
Number.parseInt(d, 10),
|
|
102
|
-
);
|
|
85
|
+
if (filterType === "date") {
|
|
86
|
+
const date = parseLocalISODate(trimmed);
|
|
87
|
+
if (date) {
|
|
88
|
+
return date;
|
|
89
|
+
}
|
|
103
90
|
}
|
|
104
91
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// viewer's timezone.
|
|
109
|
-
const isoMatch = trimmed.match(
|
|
110
|
-
/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?$/,
|
|
111
|
-
);
|
|
112
|
-
if (isoMatch) {
|
|
113
|
-
const [, y, mo, d, h, mi, s] = isoMatch;
|
|
114
|
-
return new Date(
|
|
115
|
-
Number.parseInt(y, 10),
|
|
116
|
-
Number.parseInt(mo, 10) - 1,
|
|
117
|
-
Number.parseInt(d, 10),
|
|
118
|
-
Number.parseInt(h, 10),
|
|
119
|
-
Number.parseInt(mi, 10),
|
|
120
|
-
s ? Number.parseInt(s, 10) : 0,
|
|
121
|
-
);
|
|
92
|
+
const isoDateTime = parseLocalISODateTime(trimmed);
|
|
93
|
+
if (isoDateTime) {
|
|
94
|
+
return isoDateTime;
|
|
122
95
|
}
|
|
123
96
|
|
|
124
97
|
const parsed = new Date(trimmed);
|
|
@@ -228,6 +228,24 @@ export function detectSentinel(
|
|
|
228
228
|
return null;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
type NullishSentinelType = Extract<CellValueSentinel["type"], "null" | "nat">;
|
|
232
|
+
|
|
233
|
+
const NULLISH_SENTINEL_TYPES: ReadonlySet<CellValueSentinel["type"]> =
|
|
234
|
+
new Set<NullishSentinelType>(["null", "nat"]);
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* True when the cell value represents a missing value (null, NaT)
|
|
238
|
+
* rather than a real value that happens to look special.
|
|
239
|
+
*/
|
|
240
|
+
export function isNullishSentinel(
|
|
241
|
+
value: unknown,
|
|
242
|
+
dataType: DataType | undefined,
|
|
243
|
+
dtype?: string,
|
|
244
|
+
): boolean {
|
|
245
|
+
const sentinel = detectSentinel(value, dataType, dtype);
|
|
246
|
+
return sentinel !== null && NULLISH_SENTINEL_TYPES.has(sentinel.type);
|
|
247
|
+
}
|
|
248
|
+
|
|
231
249
|
/**
|
|
232
250
|
* Stringify an unknown value. Converts objects to JSON strings.
|
|
233
251
|
* @param opts.value - The value to stringify.
|