@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
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
import type { Column, Table } from "@tanstack/react-table";
|
|
5
5
|
import { CheckIcon, MinusIcon, Trash2Icon, XIcon } from "lucide-react";
|
|
6
|
-
import { useId, useState } from "react";
|
|
6
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
7
7
|
import type { CalculateTopKRows } from "@/plugins/impl/DataTablePlugin";
|
|
8
|
+
import type { OperatorType } from "@/plugins/impl/data-frames/utils/operators";
|
|
8
9
|
import { Combobox, ComboboxItem } from "../ui/combobox";
|
|
9
10
|
import { Input } from "../ui/input";
|
|
10
11
|
import { NumberField } from "../ui/number-field";
|
|
@@ -16,68 +17,90 @@ import {
|
|
|
16
17
|
SelectValue,
|
|
17
18
|
} from "../ui/select";
|
|
18
19
|
import { Button } from "../ui/button";
|
|
20
|
+
import { DateLikeInput, DateLikeRangeInput } from "./date-filter-inputs";
|
|
19
21
|
import { FilterByValuesPicker } from "./filter-by-values-picker";
|
|
20
|
-
import {
|
|
22
|
+
import { RegexInput } from "./regex-input";
|
|
23
|
+
import {
|
|
24
|
+
type ColumnFilterValue,
|
|
25
|
+
DATETIME_OPS,
|
|
26
|
+
Filter,
|
|
27
|
+
isDatetimeComparisonOp,
|
|
28
|
+
isNumberComparisonOp,
|
|
29
|
+
isTextScalarOp,
|
|
30
|
+
MEMBERSHIP_OPS,
|
|
31
|
+
NUMBER_OPS,
|
|
32
|
+
TEXT_OPS,
|
|
33
|
+
} from "./filters";
|
|
34
|
+
import { OPERATOR_LABELS } from "./operator-labels";
|
|
21
35
|
import { Tooltip } from "../ui/tooltip";
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
type
|
|
33
|
-
|
|
34
|
-
| "
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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);
|
|
60
|
+
|
|
61
|
+
const BOOLEAN_OPS = ["is_true", "is_false", "is_null", "is_not_null"] as const;
|
|
62
|
+
const SELECT_OPS = MEMBERSHIP_OPS;
|
|
63
|
+
|
|
64
|
+
const OPERATORS_BY_TYPE: Record<
|
|
65
|
+
EditableFilterType,
|
|
66
|
+
ReadonlyArray<OperatorType>
|
|
67
|
+
> = {
|
|
68
|
+
number: NUMBER_OPS,
|
|
69
|
+
text: TEXT_OPS,
|
|
70
|
+
boolean: BOOLEAN_OPS,
|
|
71
|
+
select: SELECT_OPS,
|
|
72
|
+
date: DATETIME_OPS,
|
|
73
|
+
datetime: DATETIME_OPS,
|
|
74
|
+
time: DATETIME_OPS,
|
|
48
75
|
};
|
|
49
76
|
|
|
50
|
-
const DEFAULT_OPERATOR: Record<EditableFilterType,
|
|
77
|
+
const DEFAULT_OPERATOR: Record<EditableFilterType, OperatorType> = {
|
|
51
78
|
number: "between",
|
|
52
79
|
text: "contains",
|
|
53
80
|
boolean: "is_true",
|
|
54
81
|
select: "in",
|
|
82
|
+
date: "between",
|
|
83
|
+
datetime: "between",
|
|
84
|
+
time: "between",
|
|
55
85
|
};
|
|
56
86
|
|
|
57
|
-
const
|
|
58
|
-
between: "Between",
|
|
59
|
-
contains: "Contains",
|
|
60
|
-
is_true: "Is true",
|
|
61
|
-
is_false: "Is false",
|
|
62
|
-
is_null: "Is null",
|
|
63
|
-
is_not_null: "Is not null",
|
|
64
|
-
in: "Is in",
|
|
65
|
-
not_in: "Not in",
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const OPERATORS_WITHOUT_VALUE = new Set<UiOperator>([
|
|
87
|
+
const OPERATORS_WITHOUT_VALUE = new Set<OperatorType>([
|
|
69
88
|
"is_true",
|
|
70
89
|
"is_false",
|
|
71
90
|
"is_null",
|
|
72
91
|
"is_not_null",
|
|
92
|
+
"is_empty",
|
|
73
93
|
]);
|
|
74
94
|
|
|
75
|
-
|
|
76
|
-
min?: number;
|
|
77
|
-
|
|
78
|
-
text?: string
|
|
79
|
-
|
|
80
|
-
}
|
|
95
|
+
type DraftValue =
|
|
96
|
+
| { kind: "between"; min?: number; max?: number }
|
|
97
|
+
| { kind: "single-number"; value?: number }
|
|
98
|
+
| { kind: "single-text"; text?: string }
|
|
99
|
+
| { kind: "multi-text"; values?: string[] }
|
|
100
|
+
| { kind: "options"; options?: unknown[] }
|
|
101
|
+
| { kind: "date-between"; min?: Date; max?: Date }
|
|
102
|
+
| { kind: "date-single"; value?: Date }
|
|
103
|
+
| { kind: "none" };
|
|
81
104
|
|
|
82
105
|
interface Snapshot {
|
|
83
106
|
columnId: string;
|
|
@@ -92,7 +115,7 @@ interface FilterPillEditorProps<TData> {
|
|
|
92
115
|
}
|
|
93
116
|
|
|
94
117
|
export const FilterPillEditor = <TData,>({
|
|
95
|
-
snapshot,
|
|
118
|
+
snapshot,
|
|
96
119
|
table,
|
|
97
120
|
calculateTopKRows,
|
|
98
121
|
onClose,
|
|
@@ -102,34 +125,33 @@ export const FilterPillEditor = <TData,>({
|
|
|
102
125
|
const valueId = useId();
|
|
103
126
|
|
|
104
127
|
const snapshotType = getEditableType(snapshot.value);
|
|
105
|
-
const snapshotOperator =
|
|
128
|
+
const snapshotOperator = snapshot.value.operator as OperatorType;
|
|
106
129
|
const snapshotDraft = toDraftValue(snapshot.value);
|
|
107
130
|
|
|
108
131
|
const [draftColumnId, setDraftColumnId] = useState<string>(snapshot.columnId);
|
|
109
132
|
const [draftType, setDraftType] = useState<EditableFilterType>(snapshotType);
|
|
110
133
|
const [draftOperator, setDraftOperator] =
|
|
111
|
-
useState<
|
|
134
|
+
useState<OperatorType>(snapshotOperator);
|
|
112
135
|
const [draftValue, setDraftValue] = useState<DraftValue>(snapshotDraft);
|
|
113
136
|
|
|
114
137
|
const editableColumns = table.getAllColumns().filter((c) => {
|
|
115
138
|
const ft = c.columnDef.meta?.filterType;
|
|
116
139
|
return (
|
|
117
|
-
ft === "number" ||
|
|
140
|
+
ft === "number" ||
|
|
141
|
+
ft === "text" ||
|
|
142
|
+
ft === "boolean" ||
|
|
143
|
+
ft === "select" ||
|
|
144
|
+
ft === "date" ||
|
|
145
|
+
ft === "datetime" ||
|
|
146
|
+
ft === "time"
|
|
118
147
|
);
|
|
119
148
|
});
|
|
120
149
|
|
|
121
|
-
// if we switch back to pre-edit column+operator
|
|
122
|
-
// restore the original value as well
|
|
123
150
|
const rehydrateIfMatchesSnapshot = (args: {
|
|
124
151
|
id: string;
|
|
125
|
-
|
|
126
|
-
operator: UiOperator;
|
|
152
|
+
operator: OperatorType;
|
|
127
153
|
}) => {
|
|
128
|
-
if (
|
|
129
|
-
args.id === snapshot.columnId &&
|
|
130
|
-
args.type === snapshotType &&
|
|
131
|
-
args.operator === snapshotOperator
|
|
132
|
-
) {
|
|
154
|
+
if (args.id === snapshot.columnId && args.operator === snapshotOperator) {
|
|
133
155
|
setDraftValue(snapshotDraft);
|
|
134
156
|
}
|
|
135
157
|
};
|
|
@@ -147,34 +169,42 @@ export const FilterPillEditor = <TData,>({
|
|
|
147
169
|
nextOperator = DEFAULT_OPERATOR[nextColumnType];
|
|
148
170
|
setDraftType(nextColumnType);
|
|
149
171
|
setDraftOperator(nextOperator);
|
|
150
|
-
setDraftValue(
|
|
172
|
+
setDraftValue(emptyDraftFor(nextColumnType, nextOperator));
|
|
151
173
|
}
|
|
152
174
|
setDraftColumnId(nextColumnId);
|
|
153
175
|
rehydrateIfMatchesSnapshot({
|
|
154
176
|
id: nextColumnId,
|
|
155
|
-
type: nextColumnType,
|
|
156
177
|
operator: nextOperator,
|
|
157
178
|
});
|
|
158
179
|
};
|
|
159
180
|
|
|
160
|
-
const handleOperatorChange = (nextOp:
|
|
181
|
+
const handleOperatorChange = (nextOp: OperatorType) => {
|
|
161
182
|
setDraftOperator(nextOp);
|
|
183
|
+
const nextEmpty = emptyDraftFor(draftType, nextOp);
|
|
184
|
+
if (nextEmpty.kind !== draftValue.kind) {
|
|
185
|
+
setDraftValue(nextEmpty);
|
|
186
|
+
}
|
|
162
187
|
rehydrateIfMatchesSnapshot({
|
|
163
188
|
id: draftColumnId,
|
|
164
|
-
type: draftType,
|
|
165
189
|
operator: nextOp,
|
|
166
190
|
});
|
|
167
191
|
};
|
|
168
192
|
|
|
193
|
+
const pendingValue = buildFilterValue({
|
|
194
|
+
type: draftType,
|
|
195
|
+
operator: draftOperator,
|
|
196
|
+
draft: draftValue,
|
|
197
|
+
});
|
|
198
|
+
const applyDisabled = pendingValue === undefined;
|
|
199
|
+
const applyTooltip = applyDisabled
|
|
200
|
+
? getMissingValueMessage(draftType, draftOperator)
|
|
201
|
+
: "Apply filter";
|
|
202
|
+
|
|
169
203
|
const handleApply = () => {
|
|
170
|
-
|
|
171
|
-
type: draftType,
|
|
172
|
-
operator: draftOperator,
|
|
173
|
-
draft: draftValue,
|
|
174
|
-
});
|
|
175
|
-
if (!value) {
|
|
204
|
+
if (!pendingValue) {
|
|
176
205
|
return;
|
|
177
206
|
}
|
|
207
|
+
const value = pendingValue;
|
|
178
208
|
table.setColumnFilters((filters) => {
|
|
179
209
|
const dropIds = new Set([snapshot.columnId, draftColumnId]);
|
|
180
210
|
const filtered = filters.filter((f) => !dropIds.has(f.id));
|
|
@@ -191,9 +221,34 @@ export const FilterPillEditor = <TData,>({
|
|
|
191
221
|
};
|
|
192
222
|
|
|
193
223
|
const showValueSlot = !OPERATORS_WITHOUT_VALUE.has(draftOperator);
|
|
224
|
+
const operatorOptions = OPERATORS_BY_TYPE[draftType];
|
|
225
|
+
|
|
226
|
+
const valueSlotRef = useRef<HTMLDivElement>(null);
|
|
227
|
+
const operatorTriggerRef = useRef<HTMLButtonElement>(null);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
const firstInput = valueSlotRef.current?.querySelector<HTMLElement>(
|
|
230
|
+
'input, [role="spinbutton"], [role="combobox"], button',
|
|
231
|
+
);
|
|
232
|
+
if (firstInput) {
|
|
233
|
+
firstInput.focus();
|
|
234
|
+
} else {
|
|
235
|
+
operatorTriggerRef.current?.focus();
|
|
236
|
+
}
|
|
237
|
+
}, [draftType, draftOperator]);
|
|
194
238
|
|
|
195
239
|
return (
|
|
196
|
-
<
|
|
240
|
+
<form
|
|
241
|
+
className="flex flex-row gap-4 items-end p-3"
|
|
242
|
+
onSubmit={(e) => {
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
handleApply();
|
|
245
|
+
}}
|
|
246
|
+
onKeyDownCapture={(e) => {
|
|
247
|
+
if (e.key === "Tab") {
|
|
248
|
+
e.stopPropagation();
|
|
249
|
+
}
|
|
250
|
+
}}
|
|
251
|
+
>
|
|
197
252
|
<div className="flex flex-col gap-1">
|
|
198
253
|
<label className="text-xs text-muted-foreground" htmlFor={columnId}>
|
|
199
254
|
Column
|
|
@@ -218,14 +273,19 @@ export const FilterPillEditor = <TData,>({
|
|
|
218
273
|
Operator
|
|
219
274
|
</label>
|
|
220
275
|
<Select
|
|
276
|
+
key={draftType}
|
|
221
277
|
value={draftOperator}
|
|
222
|
-
onValueChange={(v) => handleOperatorChange(v as
|
|
278
|
+
onValueChange={(v) => handleOperatorChange(v as OperatorType)}
|
|
223
279
|
>
|
|
224
|
-
<SelectTrigger
|
|
280
|
+
<SelectTrigger
|
|
281
|
+
ref={operatorTriggerRef}
|
|
282
|
+
id={operatorId}
|
|
283
|
+
className="h-6 mb-1 bg-transparent"
|
|
284
|
+
>
|
|
225
285
|
<SelectValue />
|
|
226
286
|
</SelectTrigger>
|
|
227
287
|
<SelectContent>
|
|
228
|
-
{
|
|
288
|
+
{operatorOptions.map((op) => (
|
|
229
289
|
<SelectItem key={op} value={op}>
|
|
230
290
|
{OPERATOR_LABELS[op]}
|
|
231
291
|
</SelectItem>
|
|
@@ -234,13 +294,14 @@ export const FilterPillEditor = <TData,>({
|
|
|
234
294
|
</Select>
|
|
235
295
|
</div>
|
|
236
296
|
{showValueSlot && (
|
|
237
|
-
<div className="flex flex-col gap-1">
|
|
297
|
+
<div ref={valueSlotRef} className="flex flex-col gap-1">
|
|
238
298
|
<label htmlFor={valueId} className="text-xs text-muted-foreground">
|
|
239
299
|
Value
|
|
240
300
|
</label>
|
|
241
301
|
<ValueSlot
|
|
242
302
|
id={valueId}
|
|
243
303
|
type={draftType}
|
|
304
|
+
operator={draftOperator}
|
|
244
305
|
value={draftValue}
|
|
245
306
|
onChange={setDraftValue}
|
|
246
307
|
column={table.getColumn(draftColumnId) ?? null}
|
|
@@ -249,17 +310,19 @@ export const FilterPillEditor = <TData,>({
|
|
|
249
310
|
</div>
|
|
250
311
|
)}
|
|
251
312
|
<div className="flex gap-1 mb-1">
|
|
252
|
-
<Tooltip content=
|
|
253
|
-
<
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
313
|
+
<Tooltip content={applyTooltip}>
|
|
314
|
+
<span className="inline-flex">
|
|
315
|
+
<Button
|
|
316
|
+
type="submit"
|
|
317
|
+
size="icon"
|
|
318
|
+
variant="ghost"
|
|
319
|
+
disabled={applyDisabled}
|
|
320
|
+
className="rounded-full text-primary hover:text-primary hover:bg-primary/10"
|
|
321
|
+
aria-label="Apply filter"
|
|
322
|
+
>
|
|
323
|
+
<CheckIcon className="h-3.5 w-3.5" aria-hidden={true} />
|
|
324
|
+
</Button>
|
|
325
|
+
</span>
|
|
263
326
|
</Tooltip>
|
|
264
327
|
<Tooltip content="Close without saving">
|
|
265
328
|
<Button
|
|
@@ -286,13 +349,14 @@ export const FilterPillEditor = <TData,>({
|
|
|
286
349
|
</Button>
|
|
287
350
|
</Tooltip>
|
|
288
351
|
</div>
|
|
289
|
-
</
|
|
352
|
+
</form>
|
|
290
353
|
);
|
|
291
354
|
};
|
|
292
355
|
|
|
293
356
|
interface ValueSlotProps<TData, TValue> {
|
|
294
357
|
id?: string;
|
|
295
358
|
type: EditableFilterType;
|
|
359
|
+
operator: OperatorType;
|
|
296
360
|
value: DraftValue;
|
|
297
361
|
onChange: (next: DraftValue) => void;
|
|
298
362
|
column: Column<TData, TValue> | null;
|
|
@@ -302,26 +366,28 @@ interface ValueSlotProps<TData, TValue> {
|
|
|
302
366
|
const ValueSlot = <TData, TValue>({
|
|
303
367
|
id,
|
|
304
368
|
type,
|
|
369
|
+
operator,
|
|
305
370
|
value,
|
|
306
371
|
onChange,
|
|
307
372
|
column,
|
|
308
373
|
calculateTopKRows,
|
|
309
374
|
}: ValueSlotProps<TData, TValue>) => {
|
|
310
|
-
if (type === "number") {
|
|
375
|
+
if (type === "number" && operator === "between") {
|
|
376
|
+
const v = value.kind === "between" ? value : { kind: "between" as const };
|
|
311
377
|
return (
|
|
312
|
-
<div className="flex gap-1 items-center w-
|
|
378
|
+
<div className="flex gap-1 items-center w-44">
|
|
313
379
|
<NumberField
|
|
314
380
|
id={id}
|
|
315
|
-
value={
|
|
316
|
-
onChange={(
|
|
381
|
+
value={v.min}
|
|
382
|
+
onChange={(n) => onChange({ kind: "between", min: n, max: v.max })}
|
|
317
383
|
aria-label="min"
|
|
318
384
|
placeholder="min"
|
|
319
385
|
className="border-input flex-1 min-w-0"
|
|
320
386
|
/>
|
|
321
387
|
<MinusIcon className="h-5 w-5 text-muted-foreground shrink-0" />
|
|
322
388
|
<NumberField
|
|
323
|
-
value={
|
|
324
|
-
onChange={(
|
|
389
|
+
value={v.max}
|
|
390
|
+
onChange={(n) => onChange({ kind: "between", min: v.min, max: n })}
|
|
325
391
|
aria-label="max"
|
|
326
392
|
placeholder="max"
|
|
327
393
|
className="border-input flex-1 min-w-0"
|
|
@@ -329,26 +395,108 @@ const ValueSlot = <TData, TValue>({
|
|
|
329
395
|
</div>
|
|
330
396
|
);
|
|
331
397
|
}
|
|
332
|
-
if (type === "
|
|
398
|
+
if (type === "number" && isNumberComparisonOp(operator)) {
|
|
399
|
+
const v =
|
|
400
|
+
value.kind === "single-number"
|
|
401
|
+
? value
|
|
402
|
+
: { kind: "single-number" as const };
|
|
403
|
+
return (
|
|
404
|
+
<NumberField
|
|
405
|
+
id={id}
|
|
406
|
+
value={v.value}
|
|
407
|
+
onChange={(n) => onChange({ kind: "single-number", value: n })}
|
|
408
|
+
aria-label="value"
|
|
409
|
+
placeholder="value"
|
|
410
|
+
className="border-input w-24 min-w-0"
|
|
411
|
+
/>
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
if (
|
|
415
|
+
type === "text" &&
|
|
416
|
+
(operator === "in" || operator === "not_in") &&
|
|
417
|
+
column
|
|
418
|
+
) {
|
|
419
|
+
const v =
|
|
420
|
+
value.kind === "multi-text" ? value : { kind: "multi-text" as const };
|
|
421
|
+
return (
|
|
422
|
+
<div className="w-48">
|
|
423
|
+
<FilterByValuesPicker
|
|
424
|
+
column={column}
|
|
425
|
+
calculateTopKRows={calculateTopKRows}
|
|
426
|
+
chosenValues={v.values ?? []}
|
|
427
|
+
onChange={(next) =>
|
|
428
|
+
onChange({ kind: "multi-text", values: next.map(String) })
|
|
429
|
+
}
|
|
430
|
+
creatable={true}
|
|
431
|
+
/>
|
|
432
|
+
</div>
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
if (type === "text" && isTextScalarOp(operator)) {
|
|
436
|
+
const v =
|
|
437
|
+
value.kind === "single-text" ? value : { kind: "single-text" as const };
|
|
438
|
+
if (operator === "regex") {
|
|
439
|
+
return (
|
|
440
|
+
<RegexInput
|
|
441
|
+
id={id}
|
|
442
|
+
value={v.text ?? ""}
|
|
443
|
+
onChange={(text) => onChange({ kind: "single-text", text })}
|
|
444
|
+
className="w-40"
|
|
445
|
+
/>
|
|
446
|
+
);
|
|
447
|
+
}
|
|
333
448
|
return (
|
|
334
449
|
<Input
|
|
335
450
|
id={id}
|
|
336
451
|
type="text"
|
|
337
|
-
value={
|
|
338
|
-
onChange={(e) =>
|
|
452
|
+
value={v.text ?? ""}
|
|
453
|
+
onChange={(e) =>
|
|
454
|
+
onChange({ kind: "single-text", text: e.target.value })
|
|
455
|
+
}
|
|
339
456
|
placeholder="Text…"
|
|
340
|
-
className="border-input min-w-0"
|
|
457
|
+
className="border-input w-40 min-w-0"
|
|
458
|
+
/>
|
|
459
|
+
);
|
|
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"
|
|
341
488
|
/>
|
|
342
489
|
);
|
|
343
490
|
}
|
|
344
491
|
if (type === "select" && column) {
|
|
492
|
+
const v = value.kind === "options" ? value : { kind: "options" as const };
|
|
345
493
|
return (
|
|
346
494
|
<div className="flex w-48">
|
|
347
495
|
<FilterByValuesPicker
|
|
348
496
|
column={column}
|
|
349
497
|
calculateTopKRows={calculateTopKRows}
|
|
350
|
-
chosenValues={
|
|
351
|
-
onChange={(values) => onChange({
|
|
498
|
+
chosenValues={v.options ?? []}
|
|
499
|
+
onChange={(values) => onChange({ kind: "options", options: values })}
|
|
352
500
|
/>
|
|
353
501
|
</div>
|
|
354
502
|
);
|
|
@@ -357,55 +505,105 @@ const ValueSlot = <TData, TValue>({
|
|
|
357
505
|
};
|
|
358
506
|
|
|
359
507
|
function getEditableType(value: ColumnFilterValue): EditableFilterType {
|
|
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";
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function toDraftValue(value: ColumnFilterValue): DraftValue {
|
|
360
523
|
if (value.type === "number") {
|
|
361
|
-
|
|
524
|
+
switch (value.operator) {
|
|
525
|
+
case "between":
|
|
526
|
+
return { kind: "between", min: value.min, max: value.max };
|
|
527
|
+
case "is_null":
|
|
528
|
+
case "is_not_null":
|
|
529
|
+
return { kind: "none" };
|
|
530
|
+
default:
|
|
531
|
+
return { kind: "single-number", value: value.value };
|
|
532
|
+
}
|
|
362
533
|
}
|
|
363
534
|
if (value.type === "text") {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
535
|
+
switch (value.operator) {
|
|
536
|
+
case "in":
|
|
537
|
+
case "not_in":
|
|
538
|
+
return { kind: "multi-text", values: [...value.values] };
|
|
539
|
+
case "is_null":
|
|
540
|
+
case "is_not_null":
|
|
541
|
+
case "is_empty":
|
|
542
|
+
return { kind: "none" };
|
|
543
|
+
default:
|
|
544
|
+
return { kind: "single-text", text: value.text };
|
|
545
|
+
}
|
|
368
546
|
}
|
|
369
547
|
if (value.type === "select") {
|
|
370
|
-
return "
|
|
548
|
+
return { kind: "options", options: [...value.options] };
|
|
371
549
|
}
|
|
372
|
-
|
|
373
|
-
|
|
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
|
+
}
|
|
565
|
+
return { kind: "none" };
|
|
374
566
|
}
|
|
375
567
|
|
|
376
|
-
function
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (
|
|
381
|
-
return "
|
|
568
|
+
function emptyDraftFor(
|
|
569
|
+
type: EditableFilterType,
|
|
570
|
+
operator: OperatorType,
|
|
571
|
+
): DraftValue {
|
|
572
|
+
if (OPERATORS_WITHOUT_VALUE.has(operator)) {
|
|
573
|
+
return { kind: "none" };
|
|
382
574
|
}
|
|
383
|
-
if (
|
|
384
|
-
return "between"
|
|
575
|
+
if (type === "number") {
|
|
576
|
+
return operator === "between"
|
|
577
|
+
? { kind: "between" }
|
|
578
|
+
: { kind: "single-number" };
|
|
385
579
|
}
|
|
386
|
-
if (
|
|
387
|
-
return "
|
|
580
|
+
if (type === "text") {
|
|
581
|
+
return operator === "in" || operator === "not_in"
|
|
582
|
+
? { kind: "multi-text", values: [] }
|
|
583
|
+
: { kind: "single-text" };
|
|
388
584
|
}
|
|
389
|
-
if (
|
|
390
|
-
return
|
|
585
|
+
if (type === "select") {
|
|
586
|
+
return { kind: "options", options: [] };
|
|
391
587
|
}
|
|
392
|
-
if (
|
|
393
|
-
return
|
|
588
|
+
if (isDateLikeType(type)) {
|
|
589
|
+
return operator === "between"
|
|
590
|
+
? { kind: "date-between" }
|
|
591
|
+
: { kind: "date-single" };
|
|
394
592
|
}
|
|
395
|
-
return "
|
|
593
|
+
return { kind: "none" };
|
|
396
594
|
}
|
|
397
595
|
|
|
398
|
-
function
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (
|
|
403
|
-
return
|
|
596
|
+
function getMissingValueMessage(
|
|
597
|
+
type: EditableFilterType,
|
|
598
|
+
operator: OperatorType,
|
|
599
|
+
): string {
|
|
600
|
+
if (operator === "between") {
|
|
601
|
+
return "Min and max are required";
|
|
404
602
|
}
|
|
405
|
-
if (
|
|
406
|
-
return
|
|
603
|
+
if (type === "text" && (operator === "in" || operator === "not_in")) {
|
|
604
|
+
return "Pick at least one value";
|
|
407
605
|
}
|
|
408
|
-
return
|
|
606
|
+
return "Value is required";
|
|
409
607
|
}
|
|
410
608
|
|
|
411
609
|
function buildFilterValue({
|
|
@@ -414,51 +612,79 @@ function buildFilterValue({
|
|
|
414
612
|
draft,
|
|
415
613
|
}: {
|
|
416
614
|
type: EditableFilterType;
|
|
417
|
-
operator:
|
|
615
|
+
operator: OperatorType;
|
|
418
616
|
draft: DraftValue;
|
|
419
617
|
}): ColumnFilterValue | undefined {
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
return Filter.number({ operator: op });
|
|
618
|
+
if (type === "number") {
|
|
619
|
+
if (operator === "is_null" || operator === "is_not_null") {
|
|
620
|
+
return Filter.number({ operator });
|
|
424
621
|
}
|
|
425
|
-
if (
|
|
426
|
-
|
|
622
|
+
if (operator === "between") {
|
|
623
|
+
if (
|
|
624
|
+
draft.kind !== "between" ||
|
|
625
|
+
draft.min === undefined ||
|
|
626
|
+
draft.max === undefined
|
|
627
|
+
) {
|
|
628
|
+
return undefined;
|
|
629
|
+
}
|
|
630
|
+
return Filter.number({
|
|
631
|
+
operator: "between",
|
|
632
|
+
min: draft.min,
|
|
633
|
+
max: draft.max,
|
|
634
|
+
});
|
|
427
635
|
}
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
if (type === "number") {
|
|
431
|
-
if (draft.min === undefined && draft.max === undefined) {
|
|
636
|
+
if (!isNumberComparisonOp(operator)) {
|
|
432
637
|
return undefined;
|
|
433
638
|
}
|
|
434
|
-
|
|
639
|
+
if (draft.kind !== "single-number" || draft.value === undefined) {
|
|
640
|
+
return undefined;
|
|
641
|
+
}
|
|
642
|
+
return Filter.number({ operator, value: draft.value });
|
|
435
643
|
}
|
|
436
644
|
if (type === "text") {
|
|
437
|
-
if (
|
|
645
|
+
if (
|
|
646
|
+
operator === "is_null" ||
|
|
647
|
+
operator === "is_not_null" ||
|
|
648
|
+
operator === "is_empty"
|
|
649
|
+
) {
|
|
650
|
+
return Filter.text({ operator });
|
|
651
|
+
}
|
|
652
|
+
if (operator === "in" || operator === "not_in") {
|
|
653
|
+
if (
|
|
654
|
+
draft.kind !== "multi-text" ||
|
|
655
|
+
!draft.values ||
|
|
656
|
+
draft.values.length === 0
|
|
657
|
+
) {
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
return Filter.text({ operator, values: draft.values });
|
|
661
|
+
}
|
|
662
|
+
if (!isTextScalarOp(operator)) {
|
|
438
663
|
return undefined;
|
|
439
664
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
});
|
|
665
|
+
if (draft.kind !== "single-text" || !draft.text) {
|
|
666
|
+
return undefined;
|
|
667
|
+
}
|
|
668
|
+
return Filter.text({ operator, text: draft.text });
|
|
444
669
|
}
|
|
445
670
|
if (type === "boolean") {
|
|
446
671
|
if (operator === "is_true") {
|
|
447
|
-
return Filter.boolean({
|
|
448
|
-
value: true,
|
|
449
|
-
operator: "is_true",
|
|
450
|
-
});
|
|
672
|
+
return Filter.boolean({ value: true, operator: "is_true" });
|
|
451
673
|
}
|
|
452
674
|
if (operator === "is_false") {
|
|
453
|
-
return Filter.boolean({
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
});
|
|
675
|
+
return Filter.boolean({ value: false, operator: "is_false" });
|
|
676
|
+
}
|
|
677
|
+
if (operator === "is_null" || operator === "is_not_null") {
|
|
678
|
+
return Filter.boolean({ operator });
|
|
457
679
|
}
|
|
458
680
|
return undefined;
|
|
459
681
|
}
|
|
460
682
|
if (type === "select") {
|
|
461
|
-
if (
|
|
683
|
+
if (
|
|
684
|
+
draft.kind !== "options" ||
|
|
685
|
+
!draft.options ||
|
|
686
|
+
draft.options.length === 0
|
|
687
|
+
) {
|
|
462
688
|
return undefined;
|
|
463
689
|
}
|
|
464
690
|
return Filter.select({
|
|
@@ -466,5 +692,33 @@ function buildFilterValue({
|
|
|
466
692
|
operator: operator === "not_in" ? "not_in" : "in",
|
|
467
693
|
});
|
|
468
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
|
+
}
|
|
469
723
|
return undefined;
|
|
470
724
|
}
|