@marimo-team/frontend 0.23.7-dev55 → 0.23.7-dev57
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/assets/{CellStatus-DXNmZJpi.js → CellStatus-DGBvmSvq.js} +1 -1
- package/dist/assets/JsonOutput-BEbyS3oG.js +53 -0
- package/dist/assets/{LazyAnyLanguageCodeMirror-B2pl_WA3.js → LazyAnyLanguageCodeMirror-GdhQ07zA.js} +2 -2
- package/dist/assets/{MarimoErrorOutput-YGhIA85d.js → MarimoErrorOutput-XWqnhvJ6.js} +1 -1
- package/dist/assets/{RenderHTML-Dz1OIbOh.js → RenderHTML-B5r25cP5.js} +1 -1
- package/dist/assets/{RunButton-D1IZ1Yr0.js → RunButton-Dbak5hfa.js} +1 -1
- package/dist/assets/{add-cell-with-ai-hIPYl46r.js → add-cell-with-ai-BbZkMqv2.js} +1 -1
- package/dist/assets/{add-connection-dialog-CMR-c9XE.js → add-connection-dialog-Cw6_iYno.js} +1 -1
- package/dist/assets/{agent-panel-DnBAoLsM.js → agent-panel-StLA6gDR.js} +1 -1
- package/dist/assets/{ai-model-dropdown-zvokTxf_.js → ai-model-dropdown-CjhUqXgj.js} +1 -1
- package/dist/assets/{any-language-editor-BgxVFHQ8.js → any-language-editor-Bdhmwznp.js} +1 -1
- package/dist/assets/{app-config-button-BjFAqaTN.js → app-config-button-CCs8Jepz.js} +1 -1
- package/dist/assets/{cell-editor-Dak_jwhB.js → cell-editor-Cgyoqdi5.js} +1 -1
- package/dist/assets/{cell-link-pRI-YfIp.js → cell-link-PQYiMZw1.js} +1 -1
- package/dist/assets/{cells-DbE28H1u.js → cells-Dnu4nDoy.js} +1 -1
- package/dist/assets/{chat-display-D_nDPZek.js → chat-display-DetTBnqK.js} +1 -1
- package/dist/assets/{chat-panel-BktSpl2P.js → chat-panel-CEgw_vg0.js} +1 -1
- package/dist/assets/{chat-ui-C0_KcXrv.js → chat-ui-D-Y7p_cT.js} +1 -1
- package/dist/assets/{chunk-5FQGJX7Z-DILIU9Rm.js → chunk-5FQGJX7Z-BSzccEgu.js} +3 -3
- package/dist/assets/{code-block-37QAKDTI-Bgm-HPiB.js → code-block-37QAKDTI-U2R1jyOo.js} +1 -1
- package/dist/assets/{column-preview-CNeXQtKn.js → column-preview-DA6nf5_Q.js} +1 -1
- package/dist/assets/{command-palette-CcjZs_TG.js → command-palette-n_e11WBA.js} +1 -1
- package/dist/assets/{common-CWRr25jC.js → common-BaBE_ygg.js} +1 -1
- package/dist/assets/{components-DUd0ki0p.js → components-CvGaLA5d.js} +1 -1
- package/dist/assets/{components-Cj3Al1Y6.js → components-zB5yT_R8.js} +1 -1
- package/dist/assets/config-C2lTvbuU.js +1 -0
- package/dist/assets/{datasource-Prn_GWOB.js → datasource-I-LOgxeP.js} +1 -1
- package/dist/assets/{dependency-graph-panel-DUUCij85.js → dependency-graph-panel-BjOeXp74.js} +1 -1
- package/dist/assets/{dist-DxnNQmQo.js → dist-CW3rweKM.js} +1 -1
- package/dist/assets/{documentation-panel-CB8xalFX.js → documentation-panel-DMdFXuBf.js} +1 -1
- package/dist/assets/{download-DEJbA1IY.js → download-BO6T2USS.js} +1 -1
- package/dist/assets/{edit-page-BkHYt2if.js → edit-page-Dos8zz_9.js} +6 -6
- package/dist/assets/{error-panel-DG6AtqLR.js → error-panel-iXznkJZ1.js} +1 -1
- package/dist/assets/{file-explorer-panel-Co6MlwYD.js → file-explorer-panel-_77UepGi.js} +3 -3
- package/dist/assets/{file-icons-BjTIuMQg.js → file-icons-B6DaZdP0.js} +1 -1
- package/dist/assets/{file-name-input-CQVbWhL8.js → file-name-input-g2H2sY2h.js} +1 -1
- package/dist/assets/{floating-outline-uy6dAsIe.js → floating-outline-DbOtUfo-.js} +1 -1
- package/dist/assets/{focus-0RBjdtZw.js → focus-BaOnnMs-.js} +1 -1
- package/dist/assets/{form-DNa2VnwU.js → form-CM8vYbSt.js} +1 -1
- package/dist/assets/{globals-DI5QlXvl.js → globals-CpVAcN9Z.js} +1 -1
- package/dist/assets/{home-page-BSuXANlw.js → home-page-De1W6q6f.js} +1 -1
- package/dist/assets/{hooks-B7pYZHjF.js → hooks-7OHHugrQ.js} +1 -1
- package/dist/assets/{html-to-image-CIu-0LbU.js → html-to-image-D6SgvARi.js} +1 -1
- package/dist/assets/index-1EIgCVR_.js +38 -0
- package/dist/assets/{kiosk-mode-Ch75k65P.js → kiosk-mode-Czvj3vmL.js} +1 -1
- package/dist/assets/{layout-DFhJt7oJ.js → layout-DQGNHEpb.js} +5 -5
- package/dist/assets/{logs-panel-DR-1BC0S.js → logs-panel-BMAfoMJg.js} +1 -1
- package/dist/assets/{markdown-renderer-DGqYztXR.js → markdown-renderer-BQ-BQLiJ.js} +3 -3
- package/dist/assets/mermaid-4DMBBIKO-C0OyyVdo.js +1 -0
- package/dist/assets/{name-cell-input-DwfyLq31.js → name-cell-input-bwfAyC0i.js} +1 -1
- package/dist/assets/{outline-panel-CWunrooQ.js → outline-panel-CkZUQcZ1.js} +1 -1
- package/dist/assets/{packages-panel-BdcXUFQJ.js → packages-panel-B3dRYuRM.js} +1 -1
- package/dist/assets/panels-DvIOAb34.js +1 -0
- package/dist/assets/{process-output-CS4QGJvL.js → process-output-9W-JyYdE.js} +1 -1
- package/dist/assets/{radio-group-BS2PIEzV.js → radio-group-rsi1ibXY.js} +1 -1
- package/dist/assets/{readonly-python-code-C5JNX2fu.js → readonly-python-code-BKYj8PNf.js} +1 -1
- package/dist/assets/{reveal-component-PiSHIrbA.js → reveal-component-ClM8W-TD.js} +1 -1
- package/dist/assets/{run-page-BovrPK0f.js → run-page-DJOwAe2z.js} +1 -1
- package/dist/assets/{scratchpad-panel-CAWFveBD.js → scratchpad-panel-CpM3jVv7.js} +1 -1
- package/dist/assets/{secrets-panel-CEh4Wjfn.js → secrets-panel-DqHGq3V8.js} +1 -1
- package/dist/assets/{session-panel-BR9h5w96.js → session-panel-jVcSUURP.js} +1 -1
- package/dist/assets/{snippets-panel-Y2etH9Qg.js → snippets-panel-DFJd1ui5.js} +1 -1
- package/dist/assets/{state-Fa6RzVTL.js → state-BXNNuw9g.js} +1 -1
- package/dist/assets/{state-1SbOXCLX.js → state-D9EoHCkz.js} +1 -1
- package/dist/assets/{switch-CTn-kJzM.js → switch-BtkQp293.js} +1 -1
- package/dist/assets/{terminal-DI2XRUUH.js → terminal-BSE1Vg5d.js} +1 -1
- package/dist/assets/{textarea-wgoQLrBS.js → textarea-BBTcSr-i.js} +1 -1
- package/dist/assets/{tracing-C9PZ0Pr1.js → tracing-BQU8fBDM.js} +1 -1
- package/dist/assets/{tracing-panel-C20Rk6hU.js → tracing-panel-DEVpyGX3.js} +2 -2
- package/dist/assets/useBoolean-B8LMGUHl.js +1 -0
- package/dist/assets/{useCellActionButton-D_-iAhme.js → useCellActionButton-CpNJthj4.js} +1 -1
- package/dist/assets/{useDeleteCell-41mvwiyA.js → useDeleteCell-DFahVcdW.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-ELdrL73c.js → useDependencyPanelTab-BNbEyT1o.js} +1 -1
- package/dist/assets/{useNotebookActions-CgN-58GN.js → useNotebookActions-DqlAe4Ea.js} +1 -1
- package/dist/assets/{useRunCells-CfHlqXY6.js → useRunCells-C0BPo9m1.js} +1 -1
- package/dist/assets/{useSplitCell-gHtyz873.js → useSplitCell-BN53wD86.js} +1 -1
- package/dist/assets/{vega-component-BPU1T-x7.js → vega-component-C9fDGx86.js} +1 -1
- package/dist/assets/{write-secret-modal-DjVzKit_.js → write-secret-modal-Liv_9MXS.js} +1 -1
- package/dist/index.html +40 -40
- package/package.json +1 -1
- package/src/components/data-table/__tests__/column-header.test.tsx +106 -1
- package/src/components/data-table/__tests__/filter-pill-editor.test.tsx +88 -2
- package/src/components/data-table/__tests__/filters.test.ts +84 -13
- package/src/components/data-table/column-header.tsx +152 -26
- package/src/components/data-table/date-filter-inputs.tsx +325 -0
- package/src/components/data-table/filter-pill-editor.tsx +139 -30
- package/src/components/data-table/filter-pills.tsx +31 -57
- package/src/components/data-table/filters.ts +88 -66
- package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
- package/src/core/runtime/__tests__/runtime.test.ts +38 -17
- package/src/core/runtime/runtime.ts +57 -34
- package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +5 -4
- package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +18 -54
- package/src/core/websocket/transports/__tests__/ws.test.ts +125 -0
- package/src/core/websocket/transports/basic.ts +1 -3
- package/src/core/websocket/transports/transport.ts +0 -1
- package/src/core/websocket/transports/ws.ts +96 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +30 -26
- package/src/core/websocket/useWebSocket.tsx +3 -18
- package/dist/assets/JsonOutput-Dxol3ZtH.js +0 -49
- package/dist/assets/config-DGudsRYK.js +0 -1
- package/dist/assets/index-DySiGerD.js +0 -42
- package/dist/assets/mermaid-4DMBBIKO-B-uFGNnk.js +0 -1
- package/dist/assets/panels-CJ1t18_z.js +0 -1
- package/dist/assets/useBoolean-DP3412N2.js +0 -1
- /package/dist/assets/{bundle.esm-DjhGJy4I.js → bundle.esm-BXIlAZ6T.js} +0 -0
- /package/dist/assets/{esm-DLYpPRvw.js → esm-Cb2bnV6o.js} +0 -0
- /package/dist/assets/{field-CQGpbXj3.js → field-DNlzfMKW.js} +0 -0
- /package/dist/assets/{formats-CJQ67TPE.js → formats-BRq458WH.js} +0 -0
- /package/dist/assets/{icons-Ol38nIbL.js → icons-8tfAri2V.js} +0 -0
- /package/dist/assets/{micromark-factory-space-P--XWZhg.js → micromark-factory-space-BUQpMdx2.js} +0 -0
- /package/dist/assets/{react-resizable-panels.browser.esm-CgWOEYeG.js → react-resizable-panels.browser.esm-Ce2ksurd.js} +0 -0
- /package/dist/assets/{renderShortcut-D7FYCtYQ.js → renderShortcut-DK-VjfaX.js} +0 -0
- /package/dist/assets/{table-DUSsaCYD.js → table-DQE9hQzM.js} +0 -0
- /package/dist/assets/{tree-actions-D9i3o3Zk.js → tree-actions-DY8FUp3V.js} +0 -0
- /package/dist/assets/{useDeepCompareMemoize-zUHU--0D.js → useDeepCompareMemoize-CWcgQCbT.js} +0 -0
|
@@ -17,23 +17,46 @@ import {
|
|
|
17
17
|
SelectValue,
|
|
18
18
|
} from "../ui/select";
|
|
19
19
|
import { Button } from "../ui/button";
|
|
20
|
+
import { DateLikeInput, DateLikeRangeInput } from "./date-filter-inputs";
|
|
20
21
|
import { FilterByValuesPicker } from "./filter-by-values-picker";
|
|
21
22
|
import { RegexInput } from "./regex-input";
|
|
22
23
|
import {
|
|
23
24
|
type ColumnFilterValue,
|
|
25
|
+
DATETIME_OPS,
|
|
24
26
|
Filter,
|
|
27
|
+
isDatetimeComparisonOp,
|
|
28
|
+
isNumberComparisonOp,
|
|
29
|
+
isTextScalarOp,
|
|
25
30
|
MEMBERSHIP_OPS,
|
|
26
|
-
NUMBER_COMPARISON_OPS,
|
|
27
|
-
type NumberComparisonOp,
|
|
28
31
|
NUMBER_OPS,
|
|
29
32
|
TEXT_OPS,
|
|
30
|
-
TEXT_SCALAR_OPS,
|
|
31
|
-
type TextScalarOp,
|
|
32
33
|
} from "./filters";
|
|
33
34
|
import { OPERATOR_LABELS } from "./operator-labels";
|
|
34
35
|
import { Tooltip } from "../ui/tooltip";
|
|
35
36
|
|
|
36
|
-
type EditableFilterType =
|
|
37
|
+
type EditableFilterType =
|
|
38
|
+
| "number"
|
|
39
|
+
| "text"
|
|
40
|
+
| "boolean"
|
|
41
|
+
| "select"
|
|
42
|
+
| "date"
|
|
43
|
+
| "datetime"
|
|
44
|
+
| "time";
|
|
45
|
+
|
|
46
|
+
type DateLikeEditableFilterType = Extract<
|
|
47
|
+
EditableFilterType,
|
|
48
|
+
"date" | "datetime" | "time"
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
const DATE_LIKE_TYPES: ReadonlySet<EditableFilterType> = new Set([
|
|
52
|
+
"date",
|
|
53
|
+
"datetime",
|
|
54
|
+
"time",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const isDateLikeType = (
|
|
58
|
+
type: EditableFilterType,
|
|
59
|
+
): type is DateLikeEditableFilterType => DATE_LIKE_TYPES.has(type);
|
|
37
60
|
|
|
38
61
|
const BOOLEAN_OPS = ["is_true", "is_false", "is_null", "is_not_null"] as const;
|
|
39
62
|
const SELECT_OPS = MEMBERSHIP_OPS;
|
|
@@ -46,6 +69,9 @@ const OPERATORS_BY_TYPE: Record<
|
|
|
46
69
|
text: TEXT_OPS,
|
|
47
70
|
boolean: BOOLEAN_OPS,
|
|
48
71
|
select: SELECT_OPS,
|
|
72
|
+
date: DATETIME_OPS,
|
|
73
|
+
datetime: DATETIME_OPS,
|
|
74
|
+
time: DATETIME_OPS,
|
|
49
75
|
};
|
|
50
76
|
|
|
51
77
|
const DEFAULT_OPERATOR: Record<EditableFilterType, OperatorType> = {
|
|
@@ -53,6 +79,9 @@ const DEFAULT_OPERATOR: Record<EditableFilterType, OperatorType> = {
|
|
|
53
79
|
text: "contains",
|
|
54
80
|
boolean: "is_true",
|
|
55
81
|
select: "in",
|
|
82
|
+
date: "between",
|
|
83
|
+
datetime: "between",
|
|
84
|
+
time: "between",
|
|
56
85
|
};
|
|
57
86
|
|
|
58
87
|
const OPERATORS_WITHOUT_VALUE = new Set<OperatorType>([
|
|
@@ -63,22 +92,14 @@ const OPERATORS_WITHOUT_VALUE = new Set<OperatorType>([
|
|
|
63
92
|
"is_empty",
|
|
64
93
|
]);
|
|
65
94
|
|
|
66
|
-
const NUMBER_COMPARISON_SET: ReadonlySet<OperatorType> = new Set(
|
|
67
|
-
NUMBER_COMPARISON_OPS,
|
|
68
|
-
);
|
|
69
|
-
const TEXT_SCALAR_SET: ReadonlySet<OperatorType> = new Set(TEXT_SCALAR_OPS);
|
|
70
|
-
|
|
71
|
-
const isNumberComparisonOp = (op: OperatorType): op is NumberComparisonOp =>
|
|
72
|
-
NUMBER_COMPARISON_SET.has(op);
|
|
73
|
-
const isTextScalarOp = (op: OperatorType): op is TextScalarOp =>
|
|
74
|
-
TEXT_SCALAR_SET.has(op);
|
|
75
|
-
|
|
76
95
|
type DraftValue =
|
|
77
96
|
| { kind: "between"; min?: number; max?: number }
|
|
78
97
|
| { kind: "single-number"; value?: number }
|
|
79
98
|
| { kind: "single-text"; text?: string }
|
|
80
99
|
| { kind: "multi-text"; values?: string[] }
|
|
81
100
|
| { kind: "options"; options?: unknown[] }
|
|
101
|
+
| { kind: "date-between"; min?: Date; max?: Date }
|
|
102
|
+
| { kind: "date-single"; value?: Date }
|
|
82
103
|
| { kind: "none" };
|
|
83
104
|
|
|
84
105
|
interface Snapshot {
|
|
@@ -116,7 +137,13 @@ export const FilterPillEditor = <TData,>({
|
|
|
116
137
|
const editableColumns = table.getAllColumns().filter((c) => {
|
|
117
138
|
const ft = c.columnDef.meta?.filterType;
|
|
118
139
|
return (
|
|
119
|
-
ft === "number" ||
|
|
140
|
+
ft === "number" ||
|
|
141
|
+
ft === "text" ||
|
|
142
|
+
ft === "boolean" ||
|
|
143
|
+
ft === "select" ||
|
|
144
|
+
ft === "date" ||
|
|
145
|
+
ft === "datetime" ||
|
|
146
|
+
ft === "time"
|
|
120
147
|
);
|
|
121
148
|
});
|
|
122
149
|
|
|
@@ -200,7 +227,7 @@ export const FilterPillEditor = <TData,>({
|
|
|
200
227
|
const operatorTriggerRef = useRef<HTMLButtonElement>(null);
|
|
201
228
|
useEffect(() => {
|
|
202
229
|
const firstInput = valueSlotRef.current?.querySelector<HTMLElement>(
|
|
203
|
-
'input, [role="combobox"], button',
|
|
230
|
+
'input, [role="spinbutton"], [role="combobox"], button',
|
|
204
231
|
);
|
|
205
232
|
if (firstInput) {
|
|
206
233
|
firstInput.focus();
|
|
@@ -216,6 +243,11 @@ export const FilterPillEditor = <TData,>({
|
|
|
216
243
|
e.preventDefault();
|
|
217
244
|
handleApply();
|
|
218
245
|
}}
|
|
246
|
+
onKeyDownCapture={(e) => {
|
|
247
|
+
if (e.key === "Tab") {
|
|
248
|
+
e.stopPropagation();
|
|
249
|
+
}
|
|
250
|
+
}}
|
|
219
251
|
>
|
|
220
252
|
<div className="flex flex-col gap-1">
|
|
221
253
|
<label className="text-xs text-muted-foreground" htmlFor={columnId}>
|
|
@@ -426,6 +458,36 @@ const ValueSlot = <TData, TValue>({
|
|
|
426
458
|
/>
|
|
427
459
|
);
|
|
428
460
|
}
|
|
461
|
+
if (isDateLikeType(type) && operator === "between") {
|
|
462
|
+
const v =
|
|
463
|
+
value.kind === "date-between" ? value : { kind: "date-between" as const };
|
|
464
|
+
return (
|
|
465
|
+
<DateLikeRangeInput
|
|
466
|
+
key={`${column?.id ?? "_"}-${operator}`}
|
|
467
|
+
filterType={type}
|
|
468
|
+
min={v.min}
|
|
469
|
+
max={v.max}
|
|
470
|
+
onRangeChange={(min, max) =>
|
|
471
|
+
onChange({ kind: "date-between", min, max })
|
|
472
|
+
}
|
|
473
|
+
className="border-input"
|
|
474
|
+
/>
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
if (isDateLikeType(type) && isDatetimeComparisonOp(operator)) {
|
|
478
|
+
const v =
|
|
479
|
+
value.kind === "date-single" ? value : { kind: "date-single" as const };
|
|
480
|
+
return (
|
|
481
|
+
<DateLikeInput
|
|
482
|
+
key={`${column?.id ?? "_"}-${operator}`}
|
|
483
|
+
filterType={type}
|
|
484
|
+
value={v.value}
|
|
485
|
+
onChange={(next) => onChange({ kind: "date-single", value: next })}
|
|
486
|
+
aria-label="value"
|
|
487
|
+
className="border-input"
|
|
488
|
+
/>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
429
491
|
if (type === "select" && column) {
|
|
430
492
|
const v = value.kind === "options" ? value : { kind: "options" as const };
|
|
431
493
|
return (
|
|
@@ -443,19 +505,18 @@ const ValueSlot = <TData, TValue>({
|
|
|
443
505
|
};
|
|
444
506
|
|
|
445
507
|
function getEditableType(value: ColumnFilterValue): EditableFilterType {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
508
|
+
switch (value.type) {
|
|
509
|
+
case "number":
|
|
510
|
+
case "text":
|
|
511
|
+
case "boolean":
|
|
512
|
+
case "select":
|
|
513
|
+
case "date":
|
|
514
|
+
case "datetime":
|
|
515
|
+
case "time":
|
|
516
|
+
return value.type;
|
|
517
|
+
default:
|
|
518
|
+
return "text";
|
|
457
519
|
}
|
|
458
|
-
return "text";
|
|
459
520
|
}
|
|
460
521
|
|
|
461
522
|
function toDraftValue(value: ColumnFilterValue): DraftValue {
|
|
@@ -486,6 +547,21 @@ function toDraftValue(value: ColumnFilterValue): DraftValue {
|
|
|
486
547
|
if (value.type === "select") {
|
|
487
548
|
return { kind: "options", options: [...value.options] };
|
|
488
549
|
}
|
|
550
|
+
if (
|
|
551
|
+
value.type === "date" ||
|
|
552
|
+
value.type === "datetime" ||
|
|
553
|
+
value.type === "time"
|
|
554
|
+
) {
|
|
555
|
+
switch (value.operator) {
|
|
556
|
+
case "between":
|
|
557
|
+
return { kind: "date-between", min: value.min, max: value.max };
|
|
558
|
+
case "is_null":
|
|
559
|
+
case "is_not_null":
|
|
560
|
+
return { kind: "none" };
|
|
561
|
+
default:
|
|
562
|
+
return { kind: "date-single", value: value.value };
|
|
563
|
+
}
|
|
564
|
+
}
|
|
489
565
|
return { kind: "none" };
|
|
490
566
|
}
|
|
491
567
|
|
|
@@ -509,6 +585,11 @@ function emptyDraftFor(
|
|
|
509
585
|
if (type === "select") {
|
|
510
586
|
return { kind: "options", options: [] };
|
|
511
587
|
}
|
|
588
|
+
if (isDateLikeType(type)) {
|
|
589
|
+
return operator === "between"
|
|
590
|
+
? { kind: "date-between" }
|
|
591
|
+
: { kind: "date-single" };
|
|
592
|
+
}
|
|
512
593
|
return { kind: "none" };
|
|
513
594
|
}
|
|
514
595
|
|
|
@@ -516,7 +597,7 @@ function getMissingValueMessage(
|
|
|
516
597
|
type: EditableFilterType,
|
|
517
598
|
operator: OperatorType,
|
|
518
599
|
): string {
|
|
519
|
-
if (
|
|
600
|
+
if (operator === "between") {
|
|
520
601
|
return "Min and max are required";
|
|
521
602
|
}
|
|
522
603
|
if (type === "text" && (operator === "in" || operator === "not_in")) {
|
|
@@ -611,5 +692,33 @@ function buildFilterValue({
|
|
|
611
692
|
operator: operator === "not_in" ? "not_in" : "in",
|
|
612
693
|
});
|
|
613
694
|
}
|
|
695
|
+
if (isDateLikeType(type)) {
|
|
696
|
+
const factory =
|
|
697
|
+
type === "date"
|
|
698
|
+
? Filter.date
|
|
699
|
+
: type === "datetime"
|
|
700
|
+
? Filter.datetime
|
|
701
|
+
: Filter.time;
|
|
702
|
+
if (operator === "is_null" || operator === "is_not_null") {
|
|
703
|
+
return factory({ operator });
|
|
704
|
+
}
|
|
705
|
+
if (operator === "between") {
|
|
706
|
+
if (
|
|
707
|
+
draft.kind !== "date-between" ||
|
|
708
|
+
draft.min === undefined ||
|
|
709
|
+
draft.max === undefined
|
|
710
|
+
) {
|
|
711
|
+
return undefined;
|
|
712
|
+
}
|
|
713
|
+
return factory({ operator: "between", min: draft.min, max: draft.max });
|
|
714
|
+
}
|
|
715
|
+
if (!isDatetimeComparisonOp(operator)) {
|
|
716
|
+
return undefined;
|
|
717
|
+
}
|
|
718
|
+
if (draft.kind !== "date-single" || draft.value === undefined) {
|
|
719
|
+
return undefined;
|
|
720
|
+
}
|
|
721
|
+
return factory({ operator, value: draft.value });
|
|
722
|
+
}
|
|
614
723
|
return undefined;
|
|
615
724
|
}
|
|
@@ -12,7 +12,11 @@ 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";
|
|
16
20
|
import { OPERATOR_LABELS } from "./operator-labels";
|
|
17
21
|
import { stringifyUnknownValue } from "./utils";
|
|
18
22
|
|
|
@@ -82,13 +86,6 @@ const FilterPill = <TData,>({
|
|
|
82
86
|
return null;
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
// this is temporary, with more operator & datatype support this goes away
|
|
86
|
-
const isReadOnly =
|
|
87
|
-
"type" in value &&
|
|
88
|
-
(value.type === "date" ||
|
|
89
|
-
value.type === "datetime" ||
|
|
90
|
-
value.type === "time");
|
|
91
|
-
|
|
92
89
|
const twoSegment = formatted.value === undefined;
|
|
93
90
|
|
|
94
91
|
const handleRemove = (e: React.MouseEvent) => {
|
|
@@ -130,20 +127,8 @@ const FilterPill = <TData,>({
|
|
|
130
127
|
</Button>
|
|
131
128
|
);
|
|
132
129
|
|
|
133
|
-
if (isReadOnly) {
|
|
134
|
-
return (
|
|
135
|
-
<Badge
|
|
136
|
-
variant="outline"
|
|
137
|
-
className="bg-background border-border text-foreground"
|
|
138
|
-
>
|
|
139
|
-
{segments}
|
|
140
|
-
{removeButton}
|
|
141
|
-
</Badge>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
130
|
return (
|
|
146
|
-
<Popover open={open} onOpenChange={setOpen} modal={
|
|
131
|
+
<Popover open={open} onOpenChange={setOpen} modal={false}>
|
|
147
132
|
<Badge
|
|
148
133
|
variant="outline"
|
|
149
134
|
className={cn(
|
|
@@ -249,23 +234,31 @@ function formatValue(
|
|
|
249
234
|
};
|
|
250
235
|
}
|
|
251
236
|
}
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
+
}
|
|
269
262
|
}
|
|
270
263
|
if (value.type === "boolean") {
|
|
271
264
|
return { operator: `is ${value.value ? "True" : "False"}` };
|
|
@@ -282,22 +275,3 @@ function formatValue(
|
|
|
282
275
|
logNever(value);
|
|
283
276
|
return undefined;
|
|
284
277
|
}
|
|
285
|
-
|
|
286
|
-
function formatMinMaxLegacy(
|
|
287
|
-
min: string | number | undefined,
|
|
288
|
-
max: string | number | undefined,
|
|
289
|
-
): FormattedFilter | undefined {
|
|
290
|
-
if (min === undefined && max === undefined) {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
if (min === max) {
|
|
294
|
-
return { operator: "==", value: String(min) };
|
|
295
|
-
}
|
|
296
|
-
if (min === undefined) {
|
|
297
|
-
return { operator: "<=", value: String(max) };
|
|
298
|
-
}
|
|
299
|
-
if (max === undefined) {
|
|
300
|
-
return { operator: ">=", value: String(min) };
|
|
301
|
-
}
|
|
302
|
-
return { operator: "between", value: `${min} - ${max}` };
|
|
303
|
-
}
|
|
@@ -51,6 +51,15 @@ export const TEXT_SCALAR_OPS = [
|
|
|
51
51
|
"ends_with",
|
|
52
52
|
] as const;
|
|
53
53
|
|
|
54
|
+
export const DATETIME_COMPARISON_OPS = [
|
|
55
|
+
"==",
|
|
56
|
+
"!=",
|
|
57
|
+
">",
|
|
58
|
+
">=",
|
|
59
|
+
"<",
|
|
60
|
+
"<=",
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
54
63
|
export const NUMBER_OPS = [
|
|
55
64
|
"between",
|
|
56
65
|
...NUMBER_COMPARISON_OPS,
|
|
@@ -62,11 +71,26 @@ export const TEXT_OPS = [
|
|
|
62
71
|
"is_empty",
|
|
63
72
|
...NULLISH_OPS,
|
|
64
73
|
] as const;
|
|
74
|
+
export const DATETIME_OPS = [
|
|
75
|
+
"between",
|
|
76
|
+
...DATETIME_COMPARISON_OPS,
|
|
77
|
+
...NULLISH_OPS,
|
|
78
|
+
] as const;
|
|
65
79
|
|
|
66
80
|
export type NullishOp = (typeof NULLISH_OPS)[number];
|
|
67
81
|
export type MembershipOp = (typeof MEMBERSHIP_OPS)[number];
|
|
68
82
|
export type NumberComparisonOp = (typeof NUMBER_COMPARISON_OPS)[number];
|
|
69
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);
|
|
70
94
|
|
|
71
95
|
interface NullishOpts {
|
|
72
96
|
operator: NullishOp;
|
|
@@ -83,6 +107,11 @@ type TextFilterOpts =
|
|
|
83
107
|
| { operator: "is_empty" }
|
|
84
108
|
| NullishOpts;
|
|
85
109
|
|
|
110
|
+
type DateLikeFilterOpts =
|
|
111
|
+
| { operator: "between"; min: Date; max: Date }
|
|
112
|
+
| { operator: DatetimeComparisonOp; value: Date }
|
|
113
|
+
| NullishOpts;
|
|
114
|
+
|
|
86
115
|
// Filter is a factory function that creates a filter object
|
|
87
116
|
export const Filter = {
|
|
88
117
|
number(opts: NumberFilterOpts) {
|
|
@@ -97,19 +126,19 @@ export const Filter = {
|
|
|
97
126
|
...opts,
|
|
98
127
|
} as const;
|
|
99
128
|
},
|
|
100
|
-
date(opts:
|
|
129
|
+
date(opts: DateLikeFilterOpts) {
|
|
101
130
|
return {
|
|
102
131
|
type: "date",
|
|
103
132
|
...opts,
|
|
104
133
|
} as const;
|
|
105
134
|
},
|
|
106
|
-
datetime(opts:
|
|
135
|
+
datetime(opts: DateLikeFilterOpts) {
|
|
107
136
|
return {
|
|
108
137
|
type: "datetime",
|
|
109
138
|
...opts,
|
|
110
139
|
} as const;
|
|
111
140
|
},
|
|
112
|
-
time(opts:
|
|
141
|
+
time(opts: DateLikeFilterOpts) {
|
|
113
142
|
return {
|
|
114
143
|
type: "time",
|
|
115
144
|
...opts,
|
|
@@ -135,6 +164,26 @@ export type ColumnFilterForType<T extends FilterType> = T extends FilterType
|
|
|
135
164
|
? Extract<ColumnFilterValue, { type: T }>
|
|
136
165
|
: never;
|
|
137
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
|
+
|
|
138
187
|
function isNullishFilter(
|
|
139
188
|
filter: ColumnFilterValue,
|
|
140
189
|
): filter is Extract<
|
|
@@ -235,71 +284,44 @@ export function filterToFilterCondition(
|
|
|
235
284
|
default:
|
|
236
285
|
assertNever(filter);
|
|
237
286
|
}
|
|
238
|
-
case "
|
|
239
|
-
|
|
240
|
-
if (filter.min !== undefined) {
|
|
241
|
-
conditions.push({
|
|
242
|
-
column_id: columnId,
|
|
243
|
-
operator: ">=",
|
|
244
|
-
value: filter.min.toISOString(),
|
|
245
|
-
type: "condition",
|
|
246
|
-
negate: false,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
if (filter.max !== undefined) {
|
|
250
|
-
conditions.push({
|
|
251
|
-
column_id: columnId,
|
|
252
|
-
operator: "<=",
|
|
253
|
-
value: filter.max.toISOString(),
|
|
254
|
-
type: "condition",
|
|
255
|
-
negate: false,
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
return conditions;
|
|
259
|
-
}
|
|
260
|
-
case "date": {
|
|
261
|
-
const conditions: FilterConditionType[] = [];
|
|
262
|
-
if (filter.min !== undefined) {
|
|
263
|
-
conditions.push({
|
|
264
|
-
column_id: columnId,
|
|
265
|
-
operator: ">=",
|
|
266
|
-
value: filter.min.toISOString(),
|
|
267
|
-
type: "condition",
|
|
268
|
-
negate: false,
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
if (filter.max !== undefined) {
|
|
272
|
-
conditions.push({
|
|
273
|
-
column_id: columnId,
|
|
274
|
-
operator: "<=",
|
|
275
|
-
value: filter.max.toISOString(),
|
|
276
|
-
type: "condition",
|
|
277
|
-
negate: false,
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
return conditions;
|
|
281
|
-
}
|
|
287
|
+
case "date":
|
|
288
|
+
case "datetime":
|
|
282
289
|
case "time": {
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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);
|
|
301
324
|
}
|
|
302
|
-
return conditions;
|
|
303
325
|
}
|
|
304
326
|
case "boolean":
|
|
305
327
|
if (filter.value) {
|