@marimo-team/islands 0.23.7-dev9 → 0.23.7-dev90
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/{ConnectedDataExplorerComponent-DnRhpPMJ.js → ConnectedDataExplorerComponent-2lBNiUv6.js} +13 -13
- package/dist/{ErrorBoundary-Da4UeYxT.js → ErrorBoundary-D3wrPNma.js} +1 -1
- package/dist/{any-language-editor-DDubl8YH.js → any-language-editor-VWs_7v27.js} +5 -5
- package/dist/assets/__vite-browser-external-CAdMKBac.js +1 -0
- package/dist/assets/worker-CpBbwbQo.js +73 -0
- package/dist/{button-CA5pI2YF.js → button-Dj4BTre0.js} +5 -0
- package/dist/{capabilities-6laDasij.js → capabilities-C9rrYCzf.js} +1 -1
- package/dist/{chat-ui-BmWZZ3mE.js → chat-ui-D3XBept8.js} +625 -233
- package/dist/{check-CFM2mVDr.js → check-BcUIXnUT.js} +1 -1
- package/dist/{code-visibility-CRHzv49w.js → code-visibility-C5NrPsUC.js} +11480 -1992
- package/dist/{copy-TGGAUEWp.js → copy-DLf4aN7I.js} +2 -2
- package/dist/{dist-ESg7xyoD.js → dist-D3ZI9nhS.js} +2 -2
- package/dist/{error-banner-DnBPzEWg.js → error-banner-CVkfBUT3.js} +2 -2
- package/dist/{esm-Dd1z1auZ.js → esm-CWp0KQeK.js} +1 -1
- package/dist/{extends-CzJgxo2J.js → extends-vAi97cpa.js} +4 -4
- package/dist/{formats-CgaK7Gmx.js → formats-Dsy9kkZu.js} +3 -3
- package/dist/{glide-data-editor-B-3A3G02.js → glide-data-editor-DucgdjRo.js} +9 -9
- package/dist/{html-to-image-BwZL1Pkk.js → html-to-image-CpggM7u1.js} +2667 -2408
- package/dist/{input-BAOe64zx.js → input-D4kjoQUB.js} +8 -6
- package/dist/{label-BCWi-Oqu.js → label-BLqV33b1.js} +2 -2
- package/dist/{loader-BvW0-YWZ.js → loader-Dr8Qem8p.js} +1 -1
- package/dist/main.js +1697 -10282
- package/dist/{mermaid-cXSZ1pfD.js → mermaid-DO-Daq7u.js} +5 -5
- package/dist/{process-output-lpVrk7d5.js → process-output-X8TR20AK.js} +3 -3
- package/dist/reveal-component-kMIwe09M.js +7447 -0
- package/dist/{spec-DSIuqd3f.js → spec-hVaaZsY5.js} +4 -4
- package/dist/{strings-B_FOH6eV.js → strings-BiIhGaI8.js} +4 -4
- package/dist/style.css +1 -1
- package/dist/{swiper-component-BHs0PWwp.js → swiper-component-DlD2GU2g.js} +2 -2
- package/dist/{toDate-CHtl9vts.js → toDate-CIpC_34u.js} +33 -20
- package/dist/{tooltip-B0mtKTXm.js → tooltip-DRaMBu06.js} +3 -3
- package/dist/{types-DBtDeUKD.js → types-Dzuoc3LN.js} +1 -1
- package/dist/{useAsyncData-B6hCGywC.js → useAsyncData-C56Khv_R.js} +1 -1
- package/dist/{useDateFormatter-B3mCQMP3.js → useDateFormatter-B_9k85Ex.js} +2 -2
- package/dist/{useDeepCompareMemoize-CmwDuYUH.js → useDeepCompareMemoize-Dt98v2ua.js} +1 -1
- package/dist/{useIframeCapabilities-DbdLoEDm.js → useIframeCapabilities-BkYHTrss.js} +1 -1
- package/dist/{useLifecycle-CjMjllqy.js → useLifecycle-BF6-z62y.js} +3 -3
- package/dist/{useTheme-CByZUW0p.js → useTheme-DykuNHR2.js} +2 -2
- package/dist/{vega-component-C2BYPkfd.js → vega-component-cSdqoAxe.js} +10 -10
- package/dist/{zod-BxdsqRPd.js → zod-BWkcDORu.js} +1 -1
- package/package.json +3 -3
- package/src/components/chat/chat-components.tsx +47 -0
- package/src/components/chat/chat-display.tsx +41 -7
- package/src/components/chat/chat-panel.tsx +37 -10
- package/src/components/chat/chat-utils.ts +42 -20
- package/src/components/chat/reasoning-accordion.tsx +14 -3
- package/src/components/chat/tool-call/shared.ts +13 -0
- package/src/components/chat/tool-call/tool-approval-card.tsx +62 -0
- package/src/components/chat/tool-call/tool-args.tsx +26 -0
- package/src/components/chat/tool-call/tool-call-view.tsx +99 -0
- package/src/components/chat/tool-call/tool-error-card.tsx +81 -0
- package/src/components/chat/tool-call/tool-history-row.tsx +153 -0
- package/src/components/chat/tool-call/tool-result.tsx +101 -0
- package/src/components/data-table/__tests__/column-header.test.ts +3 -1
- package/src/components/data-table/__tests__/column-header.test.tsx +308 -0
- package/src/components/data-table/__tests__/filter-by-values-picker.test.tsx +112 -0
- package/src/components/data-table/__tests__/filter-pill-editor.test.tsx +261 -0
- package/src/components/data-table/__tests__/filters.test.ts +196 -49
- package/src/components/data-table/charts/components/form-fields.tsx +1 -0
- package/src/components/data-table/column-header.tsx +349 -170
- package/src/components/data-table/date-filter-inputs.tsx +325 -0
- package/src/components/data-table/filter-by-values-picker.tsx +70 -9
- package/src/components/data-table/filter-pill-editor.tsx +410 -156
- package/src/components/data-table/filter-pills.tsx +69 -54
- package/src/components/data-table/filters.ts +218 -101
- package/src/components/data-table/header-items.tsx +8 -1
- package/src/components/data-table/operator-labels.ts +25 -0
- package/src/components/data-table/regex-input.tsx +61 -0
- package/src/components/dependency-graph/minimap-content.tsx +14 -3
- package/src/components/editor/actions/pair-with-agent-modal.tsx +140 -49
- package/src/components/editor/actions/useNotebookActions.tsx +3 -1
- package/src/components/editor/app-container.tsx +7 -1
- package/src/components/editor/chrome/panels/context-aware-panel/context-aware-panel.tsx +10 -2
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +1 -0
- package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
- package/src/components/editor/chrome/wrapper/footer.tsx +4 -1
- package/src/components/editor/chrome/wrapper/panels.tsx +4 -1
- package/src/components/editor/chrome/wrapper/sidebar.tsx +4 -1
- package/src/components/editor/controls/Controls.tsx +11 -3
- package/src/components/editor/file-tree/file-explorer.tsx +12 -2
- package/src/components/editor/header/__tests__/status.test.tsx +108 -0
- package/src/components/editor/header/status.tsx +44 -10
- package/src/components/editor/navigation/__tests__/clipboard.test.ts +106 -0
- package/src/components/editor/navigation/__tests__/navigation.test.ts +70 -0
- package/src/components/editor/navigation/clipboard.ts +99 -25
- package/src/components/editor/navigation/navigation.ts +15 -1
- package/src/components/editor/notebook-cell.tsx +5 -0
- package/src/components/editor/output/console/ConsoleOutput.tsx +23 -5
- package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +114 -0
- package/src/components/editor/renderers/slides-layout/__tests__/compute-slide-cells.test.ts +5 -4
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +55 -15
- package/src/components/editor/renderers/slides-layout/plugin.tsx +8 -25
- package/src/components/editor/renderers/slides-layout/slides-layout.tsx +19 -6
- package/src/components/editor/renderers/slides-layout/types.ts +40 -31
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -0
- package/src/components/home/components.tsx +6 -0
- package/src/components/pages/run-page.tsx +4 -1
- package/src/components/scratchpad/scratchpad.tsx +1 -0
- package/src/components/slides/__tests__/slide-notes.test.ts +131 -0
- package/src/components/slides/reveal-component.tsx +252 -147
- package/src/components/slides/slide-notes-editor.tsx +127 -0
- package/src/components/slides/slide-notes.ts +64 -0
- package/src/components/slides/slides.css +14 -0
- package/src/components/ui/combobox.tsx +24 -5
- package/src/components/ui/number-field.tsx +2 -0
- package/src/core/ai/tools/__tests__/registry.test.ts +10 -12
- package/src/core/ai/tools/registry.ts +9 -5
- package/src/core/cells/__tests__/cells.test.ts +187 -0
- package/src/core/cells/__tests__/pending-cut-service.test.tsx +123 -0
- package/src/core/cells/cells.ts +102 -17
- package/src/core/cells/document-changes.ts +6 -1
- package/src/core/cells/pending-cut-service.ts +55 -0
- package/src/core/cells/utils.ts +11 -0
- package/src/core/codemirror/cells/extensions.ts +10 -0
- package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +152 -0
- package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +99 -0
- package/src/core/codemirror/go-to-definition/commands.ts +382 -22
- package/src/core/codemirror/go-to-definition/utils.ts +23 -5
- package/src/core/edit-app.tsx +3 -2
- package/src/core/hotkeys/hotkeys.ts +5 -0
- package/src/core/islands/worker/worker.tsx +3 -2
- package/src/core/run-app.tsx +2 -1
- package/src/core/runtime/__tests__/runtime.test.ts +38 -17
- package/src/core/runtime/runtime.ts +57 -34
- package/src/core/wasm/__tests__/utils.test.ts +34 -0
- package/src/core/wasm/utils.ts +14 -0
- package/src/core/wasm/worker/bootstrap.ts +3 -2
- package/src/core/wasm/worker/worker.ts +3 -2
- package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +156 -0
- package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +101 -0
- package/src/core/websocket/transports/__tests__/ws.test.ts +125 -0
- package/src/core/websocket/transports/basic.ts +1 -1
- package/src/core/websocket/transports/ws.ts +96 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +133 -54
- package/src/core/websocket/useWebSocket.tsx +3 -15
- package/src/css/app/Cell.css +10 -0
- package/src/plugins/core/__test__/sanitize.test.ts +30 -0
- package/src/plugins/impl/DropdownPlugin.tsx +12 -1
- package/src/plugins/impl/MultiselectPlugin.tsx +4 -0
- package/src/plugins/impl/SearchableSelect.tsx +11 -1
- package/src/plugins/impl/TabsPlugin.tsx +35 -7
- package/src/plugins/impl/__tests__/DropdownPlugin.test.tsx +56 -0
- package/src/plugins/impl/__tests__/TabsPlugin.test.tsx +154 -0
- package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +48 -36
- package/src/plugins/impl/data-frames/schema.ts +4 -1
- package/src/plugins/layout/DownloadPlugin.tsx +9 -7
- package/src/utils/__tests__/id-tree.test.ts +71 -0
- package/src/utils/download.ts +4 -2
- package/src/utils/id-tree.tsx +89 -0
- package/dist/assets/__vite-browser-external-rrUYDKRl.js +0 -1
- package/dist/assets/worker-Bfy15ViQ.js +0 -73
- package/dist/reveal-component-C97Ceb7e.js +0 -4863
- package/src/components/chat/tool-call-accordion.tsx +0 -247
|
@@ -12,7 +12,12 @@ import { Badge } from "../ui/badge";
|
|
|
12
12
|
import { Button } from "../ui/button";
|
|
13
13
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
|
14
14
|
import { FilterPillEditor } from "./filter-pill-editor";
|
|
15
|
-
import
|
|
15
|
+
import {
|
|
16
|
+
type ColumnFilterValue,
|
|
17
|
+
dateToISODate,
|
|
18
|
+
dateToISODateTime,
|
|
19
|
+
} from "./filters";
|
|
20
|
+
import { OPERATOR_LABELS } from "./operator-labels";
|
|
16
21
|
import { stringifyUnknownValue } from "./utils";
|
|
17
22
|
|
|
18
23
|
interface Props<TData> {
|
|
@@ -81,13 +86,6 @@ const FilterPill = <TData,>({
|
|
|
81
86
|
return null;
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
// this is temporary, with more operator & datatype support this goes away
|
|
85
|
-
const isReadOnly =
|
|
86
|
-
"type" in value &&
|
|
87
|
-
(value.type === "date" ||
|
|
88
|
-
value.type === "datetime" ||
|
|
89
|
-
value.type === "time");
|
|
90
|
-
|
|
91
89
|
const twoSegment = formatted.value === undefined;
|
|
92
90
|
|
|
93
91
|
const handleRemove = (e: React.MouseEvent) => {
|
|
@@ -129,20 +127,8 @@ const FilterPill = <TData,>({
|
|
|
129
127
|
</Button>
|
|
130
128
|
);
|
|
131
129
|
|
|
132
|
-
if (isReadOnly) {
|
|
133
|
-
return (
|
|
134
|
-
<Badge
|
|
135
|
-
variant="outline"
|
|
136
|
-
className="bg-background border-border text-foreground"
|
|
137
|
-
>
|
|
138
|
-
{segments}
|
|
139
|
-
{removeButton}
|
|
140
|
-
</Badge>
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
130
|
return (
|
|
145
|
-
<Popover open={open} onOpenChange={setOpen} modal={
|
|
131
|
+
<Popover open={open} onOpenChange={setOpen} modal={false}>
|
|
146
132
|
<Badge
|
|
147
133
|
variant="outline"
|
|
148
134
|
className={cn(
|
|
@@ -209,19 +195,70 @@ function formatValue(
|
|
|
209
195
|
}
|
|
210
196
|
|
|
211
197
|
if (value.type === "number") {
|
|
212
|
-
|
|
198
|
+
switch (value.operator) {
|
|
199
|
+
case "between":
|
|
200
|
+
return {
|
|
201
|
+
operator: OPERATOR_LABELS.between.toLowerCase(),
|
|
202
|
+
value: `${value.min} - ${value.max}`,
|
|
203
|
+
};
|
|
204
|
+
case "==":
|
|
205
|
+
case "!=":
|
|
206
|
+
case ">":
|
|
207
|
+
case ">=":
|
|
208
|
+
case "<":
|
|
209
|
+
case "<=":
|
|
210
|
+
return { operator: value.operator, value: String(value.value) };
|
|
211
|
+
}
|
|
213
212
|
}
|
|
214
|
-
if (value.type === "
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
213
|
+
if (value.type === "text") {
|
|
214
|
+
switch (value.operator) {
|
|
215
|
+
case "in":
|
|
216
|
+
case "not_in": {
|
|
217
|
+
const items = value.values.map((v) => `"${v}"`);
|
|
218
|
+
return {
|
|
219
|
+
operator: value.operator === "in" ? "is in" : "not in",
|
|
220
|
+
value: `[${items.join(", ")}]`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
case "is_empty":
|
|
224
|
+
return { operator: "is empty" };
|
|
225
|
+
case "contains":
|
|
226
|
+
case "equals":
|
|
227
|
+
case "does_not_equal":
|
|
228
|
+
case "regex":
|
|
229
|
+
case "starts_with":
|
|
230
|
+
case "ends_with":
|
|
231
|
+
return {
|
|
232
|
+
operator: OPERATOR_LABELS[value.operator].toLowerCase(),
|
|
233
|
+
value: `"${value.text}"`,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
222
236
|
}
|
|
223
|
-
if (
|
|
224
|
-
|
|
237
|
+
if (
|
|
238
|
+
value.type === "date" ||
|
|
239
|
+
value.type === "datetime" ||
|
|
240
|
+
value.type === "time"
|
|
241
|
+
) {
|
|
242
|
+
const format =
|
|
243
|
+
value.type === "time"
|
|
244
|
+
? (d: Date) => timeFormatter.format(d)
|
|
245
|
+
: value.type === "date"
|
|
246
|
+
? dateToISODate
|
|
247
|
+
: dateToISODateTime;
|
|
248
|
+
switch (value.operator) {
|
|
249
|
+
case "between":
|
|
250
|
+
return {
|
|
251
|
+
operator: OPERATOR_LABELS.between.toLowerCase(),
|
|
252
|
+
value: `${format(value.min)} - ${format(value.max)}`,
|
|
253
|
+
};
|
|
254
|
+
case "==":
|
|
255
|
+
case "!=":
|
|
256
|
+
case ">":
|
|
257
|
+
case ">=":
|
|
258
|
+
case "<":
|
|
259
|
+
case "<=":
|
|
260
|
+
return { operator: value.operator, value: format(value.value) };
|
|
261
|
+
}
|
|
225
262
|
}
|
|
226
263
|
if (value.type === "boolean") {
|
|
227
264
|
return { operator: `is ${value.value ? "True" : "False"}` };
|
|
@@ -235,28 +272,6 @@ function formatValue(
|
|
|
235
272
|
value: `[${stringifiedOptions.join(", ")}]`,
|
|
236
273
|
};
|
|
237
274
|
}
|
|
238
|
-
if (value.type === "text") {
|
|
239
|
-
return { operator: "contains", value: `"${value.text}"` };
|
|
240
|
-
}
|
|
241
275
|
logNever(value);
|
|
242
276
|
return undefined;
|
|
243
277
|
}
|
|
244
|
-
|
|
245
|
-
function formatMinMax(
|
|
246
|
-
min: string | number | undefined,
|
|
247
|
-
max: string | number | undefined,
|
|
248
|
-
): FormattedFilter | undefined {
|
|
249
|
-
if (min === undefined && max === undefined) {
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if (min === max) {
|
|
253
|
-
return { operator: "==", value: String(min) };
|
|
254
|
-
}
|
|
255
|
-
if (min === undefined) {
|
|
256
|
-
return { operator: "<=", value: String(max) };
|
|
257
|
-
}
|
|
258
|
-
if (max === undefined) {
|
|
259
|
-
return { operator: ">=", value: String(min) };
|
|
260
|
-
}
|
|
261
|
-
return { operator: "between", value: `${min} - ${max}` };
|
|
262
|
-
}
|
|
@@ -32,33 +32,113 @@ export type FilterType =
|
|
|
32
32
|
| "select"
|
|
33
33
|
| "boolean";
|
|
34
34
|
|
|
35
|
+
export const NULLISH_OPS = ["is_null", "is_not_null"] as const;
|
|
36
|
+
export const MEMBERSHIP_OPS = ["in", "not_in"] as const;
|
|
37
|
+
export const NUMBER_COMPARISON_OPS = [
|
|
38
|
+
"==",
|
|
39
|
+
"!=",
|
|
40
|
+
">",
|
|
41
|
+
">=",
|
|
42
|
+
"<",
|
|
43
|
+
"<=",
|
|
44
|
+
] as const;
|
|
45
|
+
export const TEXT_SCALAR_OPS = [
|
|
46
|
+
"contains",
|
|
47
|
+
"equals",
|
|
48
|
+
"does_not_equal",
|
|
49
|
+
"regex",
|
|
50
|
+
"starts_with",
|
|
51
|
+
"ends_with",
|
|
52
|
+
] as const;
|
|
53
|
+
|
|
54
|
+
export const DATETIME_COMPARISON_OPS = [
|
|
55
|
+
"==",
|
|
56
|
+
"!=",
|
|
57
|
+
">",
|
|
58
|
+
">=",
|
|
59
|
+
"<",
|
|
60
|
+
"<=",
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
63
|
+
export const NUMBER_OPS = [
|
|
64
|
+
"between",
|
|
65
|
+
...NUMBER_COMPARISON_OPS,
|
|
66
|
+
...NULLISH_OPS,
|
|
67
|
+
] as const;
|
|
68
|
+
export const TEXT_OPS = [
|
|
69
|
+
...TEXT_SCALAR_OPS,
|
|
70
|
+
...MEMBERSHIP_OPS,
|
|
71
|
+
"is_empty",
|
|
72
|
+
...NULLISH_OPS,
|
|
73
|
+
] as const;
|
|
74
|
+
export const DATETIME_OPS = [
|
|
75
|
+
"between",
|
|
76
|
+
...DATETIME_COMPARISON_OPS,
|
|
77
|
+
...NULLISH_OPS,
|
|
78
|
+
] as const;
|
|
79
|
+
|
|
80
|
+
export type NullishOp = (typeof NULLISH_OPS)[number];
|
|
81
|
+
export type MembershipOp = (typeof MEMBERSHIP_OPS)[number];
|
|
82
|
+
export type NumberComparisonOp = (typeof NUMBER_COMPARISON_OPS)[number];
|
|
83
|
+
export type TextScalarOp = (typeof TEXT_SCALAR_OPS)[number];
|
|
84
|
+
export type DatetimeComparisonOp = (typeof DATETIME_COMPARISON_OPS)[number];
|
|
85
|
+
|
|
86
|
+
const makeOpGuard = <T extends OperatorType>(ops: readonly T[]) => {
|
|
87
|
+
const set = new Set<OperatorType>(ops);
|
|
88
|
+
return (op: OperatorType): op is T => set.has(op);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const isNumberComparisonOp = makeOpGuard(NUMBER_COMPARISON_OPS);
|
|
92
|
+
export const isTextScalarOp = makeOpGuard(TEXT_SCALAR_OPS);
|
|
93
|
+
export const isDatetimeComparisonOp = makeOpGuard(DATETIME_COMPARISON_OPS);
|
|
94
|
+
|
|
95
|
+
interface NullishOpts {
|
|
96
|
+
operator: NullishOp;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type NumberFilterOpts =
|
|
100
|
+
| { operator: "between"; min: number; max: number }
|
|
101
|
+
| { operator: NumberComparisonOp; value: number }
|
|
102
|
+
| NullishOpts;
|
|
103
|
+
|
|
104
|
+
type TextFilterOpts =
|
|
105
|
+
| { operator: TextScalarOp; text: string }
|
|
106
|
+
| { operator: MembershipOp; values: string[] }
|
|
107
|
+
| { operator: "is_empty" }
|
|
108
|
+
| NullishOpts;
|
|
109
|
+
|
|
110
|
+
type DateLikeFilterOpts =
|
|
111
|
+
| { operator: "between"; min: Date; max: Date }
|
|
112
|
+
| { operator: DatetimeComparisonOp; value: Date }
|
|
113
|
+
| NullishOpts;
|
|
114
|
+
|
|
35
115
|
// Filter is a factory function that creates a filter object
|
|
36
116
|
export const Filter = {
|
|
37
|
-
number(opts:
|
|
117
|
+
number(opts: NumberFilterOpts) {
|
|
38
118
|
return {
|
|
39
119
|
type: "number",
|
|
40
120
|
...opts,
|
|
41
121
|
} as const;
|
|
42
122
|
},
|
|
43
|
-
text(opts:
|
|
123
|
+
text(opts: TextFilterOpts) {
|
|
44
124
|
return {
|
|
45
125
|
type: "text",
|
|
46
126
|
...opts,
|
|
47
127
|
} as const;
|
|
48
128
|
},
|
|
49
|
-
date(opts:
|
|
129
|
+
date(opts: DateLikeFilterOpts) {
|
|
50
130
|
return {
|
|
51
131
|
type: "date",
|
|
52
132
|
...opts,
|
|
53
133
|
} as const;
|
|
54
134
|
},
|
|
55
|
-
datetime(opts:
|
|
135
|
+
datetime(opts: DateLikeFilterOpts) {
|
|
56
136
|
return {
|
|
57
137
|
type: "datetime",
|
|
58
138
|
...opts,
|
|
59
139
|
} as const;
|
|
60
140
|
},
|
|
61
|
-
time(opts:
|
|
141
|
+
time(opts: DateLikeFilterOpts) {
|
|
62
142
|
return {
|
|
63
143
|
type: "time",
|
|
64
144
|
...opts,
|
|
@@ -84,6 +164,35 @@ export type ColumnFilterForType<T extends FilterType> = T extends FilterType
|
|
|
84
164
|
? Extract<ColumnFilterValue, { type: T }>
|
|
85
165
|
: never;
|
|
86
166
|
|
|
167
|
+
function pad2(n: number): string {
|
|
168
|
+
return n.toString().padStart(2, "0");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function pad4(n: number): string {
|
|
172
|
+
return n.toString().padStart(4, "0");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function dateToISODate(d: Date): string {
|
|
176
|
+
return `${pad4(d.getFullYear())}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function dateToISOTime(d: Date): string {
|
|
180
|
+
return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function dateToISODateTime(d: Date): string {
|
|
184
|
+
return `${dateToISODate(d)}T${dateToISOTime(d)}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isNullishFilter(
|
|
188
|
+
filter: ColumnFilterValue,
|
|
189
|
+
): filter is Extract<
|
|
190
|
+
ColumnFilterValue,
|
|
191
|
+
{ operator: "is_null" | "is_not_null" }
|
|
192
|
+
> {
|
|
193
|
+
return filter.operator === "is_null" || filter.operator === "is_not_null";
|
|
194
|
+
}
|
|
195
|
+
|
|
87
196
|
export function filterToFilterCondition(
|
|
88
197
|
columnIdString: string,
|
|
89
198
|
filter: ColumnFilterValue | undefined,
|
|
@@ -93,12 +202,11 @@ export function filterToFilterCondition(
|
|
|
93
202
|
}
|
|
94
203
|
const columnId = columnIdString as ColumnId;
|
|
95
204
|
|
|
96
|
-
if (filter
|
|
205
|
+
if (isNullishFilter(filter)) {
|
|
97
206
|
return [
|
|
98
207
|
{
|
|
99
208
|
column_id: columnId,
|
|
100
209
|
operator: filter.operator,
|
|
101
|
-
value: undefined,
|
|
102
210
|
type: "condition",
|
|
103
211
|
negate: false,
|
|
104
212
|
},
|
|
@@ -106,103 +214,114 @@ export function filterToFilterCondition(
|
|
|
106
214
|
}
|
|
107
215
|
|
|
108
216
|
switch (filter.type) {
|
|
109
|
-
case "number":
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
217
|
+
case "number":
|
|
218
|
+
switch (filter.operator) {
|
|
219
|
+
case "between":
|
|
220
|
+
return [
|
|
221
|
+
{
|
|
222
|
+
column_id: columnId,
|
|
223
|
+
operator: "between",
|
|
224
|
+
value: { min: filter.min, max: filter.max },
|
|
225
|
+
type: "condition",
|
|
226
|
+
negate: false,
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
case "==":
|
|
230
|
+
case "!=":
|
|
231
|
+
case ">":
|
|
232
|
+
case ">=":
|
|
233
|
+
case "<":
|
|
234
|
+
case "<=":
|
|
235
|
+
return [
|
|
236
|
+
{
|
|
237
|
+
column_id: columnId,
|
|
238
|
+
operator: filter.operator,
|
|
239
|
+
value: filter.value,
|
|
240
|
+
type: "condition",
|
|
241
|
+
negate: false,
|
|
242
|
+
},
|
|
243
|
+
];
|
|
244
|
+
default:
|
|
245
|
+
assertNever(filter);
|
|
119
246
|
}
|
|
120
|
-
if (filter.max !== undefined) {
|
|
121
|
-
conditions.push({
|
|
122
|
-
column_id: columnId,
|
|
123
|
-
operator: "<=",
|
|
124
|
-
value: filter.max,
|
|
125
|
-
type: "condition",
|
|
126
|
-
negate: false,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
return conditions;
|
|
130
|
-
}
|
|
131
247
|
case "text":
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
type: "condition",
|
|
171
|
-
negate: false,
|
|
172
|
-
});
|
|
248
|
+
switch (filter.operator) {
|
|
249
|
+
case "contains":
|
|
250
|
+
case "equals":
|
|
251
|
+
case "does_not_equal":
|
|
252
|
+
case "regex":
|
|
253
|
+
case "starts_with":
|
|
254
|
+
case "ends_with":
|
|
255
|
+
return [
|
|
256
|
+
{
|
|
257
|
+
column_id: columnId,
|
|
258
|
+
operator: filter.operator,
|
|
259
|
+
value: filter.text,
|
|
260
|
+
type: "condition",
|
|
261
|
+
negate: false,
|
|
262
|
+
},
|
|
263
|
+
];
|
|
264
|
+
case "in":
|
|
265
|
+
case "not_in":
|
|
266
|
+
return [
|
|
267
|
+
{
|
|
268
|
+
column_id: columnId,
|
|
269
|
+
operator: filter.operator,
|
|
270
|
+
value: filter.values,
|
|
271
|
+
type: "condition",
|
|
272
|
+
negate: false,
|
|
273
|
+
},
|
|
274
|
+
];
|
|
275
|
+
case "is_empty":
|
|
276
|
+
return [
|
|
277
|
+
{
|
|
278
|
+
column_id: columnId,
|
|
279
|
+
operator: "is_empty",
|
|
280
|
+
type: "condition",
|
|
281
|
+
negate: false,
|
|
282
|
+
},
|
|
283
|
+
];
|
|
284
|
+
default:
|
|
285
|
+
assertNever(filter);
|
|
173
286
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
column_id: columnId,
|
|
177
|
-
operator: "<=",
|
|
178
|
-
value: filter.max.toISOString(),
|
|
179
|
-
type: "condition",
|
|
180
|
-
negate: false,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
return conditions;
|
|
184
|
-
}
|
|
287
|
+
case "date":
|
|
288
|
+
case "datetime":
|
|
185
289
|
case "time": {
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
290
|
+
const encode =
|
|
291
|
+
filter.type === "date"
|
|
292
|
+
? dateToISODate
|
|
293
|
+
: filter.type === "time"
|
|
294
|
+
? dateToISOTime
|
|
295
|
+
: dateToISODateTime;
|
|
296
|
+
switch (filter.operator) {
|
|
297
|
+
case "between":
|
|
298
|
+
return [
|
|
299
|
+
{
|
|
300
|
+
column_id: columnId,
|
|
301
|
+
operator: "between",
|
|
302
|
+
value: { min: encode(filter.min), max: encode(filter.max) },
|
|
303
|
+
type: "condition",
|
|
304
|
+
negate: false,
|
|
305
|
+
},
|
|
306
|
+
];
|
|
307
|
+
case "==":
|
|
308
|
+
case "!=":
|
|
309
|
+
case ">":
|
|
310
|
+
case ">=":
|
|
311
|
+
case "<":
|
|
312
|
+
case "<=":
|
|
313
|
+
return [
|
|
314
|
+
{
|
|
315
|
+
column_id: columnId,
|
|
316
|
+
operator: filter.operator,
|
|
317
|
+
value: encode(filter.value),
|
|
318
|
+
type: "condition",
|
|
319
|
+
negate: false,
|
|
320
|
+
},
|
|
321
|
+
];
|
|
322
|
+
default:
|
|
323
|
+
assertNever(filter);
|
|
204
324
|
}
|
|
205
|
-
return conditions;
|
|
206
325
|
}
|
|
207
326
|
case "boolean":
|
|
208
327
|
if (filter.value) {
|
|
@@ -210,7 +329,6 @@ export function filterToFilterCondition(
|
|
|
210
329
|
{
|
|
211
330
|
column_id: columnId,
|
|
212
331
|
operator: "is_true",
|
|
213
|
-
value: undefined,
|
|
214
332
|
type: "condition",
|
|
215
333
|
negate: false,
|
|
216
334
|
},
|
|
@@ -221,7 +339,6 @@ export function filterToFilterCondition(
|
|
|
221
339
|
{
|
|
222
340
|
column_id: columnId,
|
|
223
341
|
operator: "is_false",
|
|
224
|
-
value: undefined,
|
|
225
342
|
type: "condition",
|
|
226
343
|
negate: false,
|
|
227
344
|
},
|
|
@@ -328,14 +328,21 @@ export const FilterButtons = ({
|
|
|
328
328
|
onApply,
|
|
329
329
|
onClear,
|
|
330
330
|
clearButtonDisabled,
|
|
331
|
+
applyButtonDisabled,
|
|
331
332
|
}: {
|
|
332
333
|
onApply: () => void;
|
|
333
334
|
onClear: () => void;
|
|
334
335
|
clearButtonDisabled?: boolean;
|
|
336
|
+
applyButtonDisabled?: boolean;
|
|
335
337
|
}) => {
|
|
336
338
|
return (
|
|
337
339
|
<div className="flex gap-2 px-2 justify-between">
|
|
338
|
-
<Button
|
|
340
|
+
<Button
|
|
341
|
+
variant="link"
|
|
342
|
+
size="sm"
|
|
343
|
+
onClick={onApply}
|
|
344
|
+
disabled={applyButtonDisabled}
|
|
345
|
+
>
|
|
339
346
|
Apply
|
|
340
347
|
</Button>
|
|
341
348
|
<Button
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import type { OperatorType } from "@/plugins/impl/data-frames/utils/operators";
|
|
3
|
+
|
|
4
|
+
export const OPERATOR_LABELS: Record<OperatorType | "between", string> = {
|
|
5
|
+
"==": "Equals",
|
|
6
|
+
"!=": "Doesn't equal",
|
|
7
|
+
">": "Greater than",
|
|
8
|
+
">=": "Greater than or equal",
|
|
9
|
+
"<": "Less than",
|
|
10
|
+
"<=": "Less than or equal",
|
|
11
|
+
between: "Between",
|
|
12
|
+
contains: "Contains",
|
|
13
|
+
equals: "Equals",
|
|
14
|
+
does_not_equal: "Doesn't equal",
|
|
15
|
+
starts_with: "Starts with",
|
|
16
|
+
ends_with: "Ends with",
|
|
17
|
+
regex: "Matches regex",
|
|
18
|
+
in: "Is in",
|
|
19
|
+
not_in: "Not in",
|
|
20
|
+
is_empty: "Is empty",
|
|
21
|
+
is_true: "Is true",
|
|
22
|
+
is_false: "Is false",
|
|
23
|
+
is_null: "Is null",
|
|
24
|
+
is_not_null: "Is not null",
|
|
25
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { cn } from "@/utils/cn";
|
|
4
|
+
import { Input } from "../ui/input";
|
|
5
|
+
|
|
6
|
+
export interface RegexInputProps {
|
|
7
|
+
id?: string;
|
|
8
|
+
value: string;
|
|
9
|
+
onChange: (next: string) => void;
|
|
10
|
+
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
className?: string;
|
|
13
|
+
autoFocus?: boolean;
|
|
14
|
+
"aria-label"?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const RegexInput = React.forwardRef<HTMLInputElement, RegexInputProps>(
|
|
18
|
+
(
|
|
19
|
+
{
|
|
20
|
+
id,
|
|
21
|
+
value,
|
|
22
|
+
onChange,
|
|
23
|
+
onKeyDown,
|
|
24
|
+
placeholder = "pattern",
|
|
25
|
+
className,
|
|
26
|
+
autoFocus,
|
|
27
|
+
"aria-label": ariaLabel,
|
|
28
|
+
},
|
|
29
|
+
ref,
|
|
30
|
+
) => (
|
|
31
|
+
<div
|
|
32
|
+
className={cn(
|
|
33
|
+
"flex items-stretch h-6 mb-1 rounded-sm border border-input bg-background shadow-xs-solid focus-within:shadow-md-solid focus-within:ring-1 focus-within:ring-ring focus-within:border-primary",
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
>
|
|
37
|
+
<Slash />
|
|
38
|
+
<Input
|
|
39
|
+
ref={ref}
|
|
40
|
+
id={id}
|
|
41
|
+
type="text"
|
|
42
|
+
value={value}
|
|
43
|
+
onChange={(e) => onChange(e.target.value)}
|
|
44
|
+
onKeyDown={onKeyDown}
|
|
45
|
+
placeholder={placeholder}
|
|
46
|
+
autoFocus={autoFocus}
|
|
47
|
+
aria-label={ariaLabel}
|
|
48
|
+
rootClassName="flex-1 min-w-0"
|
|
49
|
+
className="border-0 mb-0 h-full shadow-none! hover:shadow-none! focus-visible:shadow-none! focus-visible:ring-0 focus-visible:border-0 rounded-none bg-transparent"
|
|
50
|
+
/>
|
|
51
|
+
<Slash />
|
|
52
|
+
</div>
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
RegexInput.displayName = "RegexInput";
|
|
56
|
+
|
|
57
|
+
const Slash = () => (
|
|
58
|
+
<span className="px-1.5 flex items-center text-muted-foreground font-code text-sm select-none">
|
|
59
|
+
/
|
|
60
|
+
</span>
|
|
61
|
+
);
|