@marimo-team/islands 0.23.7-dev56 → 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/{code-visibility-nPfbiA_L.js → code-visibility-PjV7HUDZ.js} +10621 -1448
- package/dist/main.js +1347 -9983
- package/dist/{reveal-component-DuRlSS4j.js → reveal-component-Phd-LTXq.js} +1 -1
- 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/core/websocket/transports/ws.ts +3 -1
|
@@ -8,7 +8,7 @@ import { t as require_react } from "./react-DA-nE2FX.js";
|
|
|
8
8
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
9
9
|
import "./html-to-image-CpggM7u1.js";
|
|
10
10
|
import "./chunk-5FQGJX7Z-BOg95xG5.js";
|
|
11
|
-
import { Ft as
|
|
11
|
+
import { Ft as EyeOff, It as Expand, a as DEFAULT_SLIDE_TYPE, c as Slide, i as DEFAULT_DECK_TRANSITION, s as SlideSidebar, t as useNotebookCodeAvailable, zt as Code } from "./code-visibility-PjV7HUDZ.js";
|
|
12
12
|
import "./input-D4kjoQUB.js";
|
|
13
13
|
import "./toDate-CIpC_34u.js";
|
|
14
14
|
import "./react-dom-BWRJ_g_k.js";
|
package/package.json
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
import type { Column } from "@tanstack/react-table";
|
|
3
3
|
import { fireEvent, render, screen, within } from "@testing-library/react";
|
|
4
4
|
import { beforeAll, describe, expect, it, vi } from "vitest";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
DateFilterMenu,
|
|
7
|
+
NumberFilterMenu,
|
|
8
|
+
TextFilterMenu,
|
|
9
|
+
} from "../column-header";
|
|
6
10
|
import { Filter } from "../filters";
|
|
7
11
|
|
|
8
12
|
beforeAll(() => {
|
|
@@ -201,3 +205,104 @@ describe("TextFilterMenu", () => {
|
|
|
201
205
|
expect(screen.getByRole("button", { name: /apply/i })).not.toBeDisabled();
|
|
202
206
|
});
|
|
203
207
|
});
|
|
208
|
+
|
|
209
|
+
type DateFilterValue = ReturnType<typeof Filter.date>;
|
|
210
|
+
|
|
211
|
+
function mockDateColumn(
|
|
212
|
+
filterType: "date" | "datetime" | "time" = "date",
|
|
213
|
+
initial?: DateFilterValue,
|
|
214
|
+
): Column<unknown, unknown> & {
|
|
215
|
+
setFilterValue: ReturnType<typeof vi.fn>;
|
|
216
|
+
} {
|
|
217
|
+
let filterValue = initial;
|
|
218
|
+
const setFilterValue = vi.fn((next) => {
|
|
219
|
+
filterValue = next;
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
id: "when",
|
|
223
|
+
columnDef: { meta: { dataType: filterType, filterType } },
|
|
224
|
+
getFilterValue: () => filterValue,
|
|
225
|
+
setFilterValue,
|
|
226
|
+
} as unknown as Column<unknown, unknown> & {
|
|
227
|
+
setFilterValue: ReturnType<typeof vi.fn>;
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
describe("DateFilterMenu", () => {
|
|
232
|
+
it("shows all expected operators in the dropdown", () => {
|
|
233
|
+
const column = mockDateColumn("date");
|
|
234
|
+
render(<DateFilterMenu column={column} filterType="date" />);
|
|
235
|
+
fireEvent.click(screen.getByRole("combobox"));
|
|
236
|
+
const listbox = screen.getByRole("listbox");
|
|
237
|
+
const labels = within(listbox)
|
|
238
|
+
.getAllByRole("option")
|
|
239
|
+
.map((o) => o.textContent);
|
|
240
|
+
expect(labels).toEqual([
|
|
241
|
+
"Between",
|
|
242
|
+
"Equals",
|
|
243
|
+
"Doesn't equal",
|
|
244
|
+
"Greater than",
|
|
245
|
+
"Greater than or equal",
|
|
246
|
+
"Less than",
|
|
247
|
+
"Less than or equal",
|
|
248
|
+
"Is null",
|
|
249
|
+
"Is not null",
|
|
250
|
+
]);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("defaults to between mode and disables Apply until both bounds set", () => {
|
|
254
|
+
const column = mockDateColumn("date");
|
|
255
|
+
render(<DateFilterMenu column={column} filterType="date" />);
|
|
256
|
+
expect(screen.getByLabelText("range")).toBeInTheDocument();
|
|
257
|
+
expect(screen.getByRole("button", { name: /apply/i })).toBeDisabled();
|
|
258
|
+
expect(screen.queryByLabelText("value")).not.toBeInTheDocument();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("seeds between min/max from current filter", () => {
|
|
262
|
+
const column = mockDateColumn(
|
|
263
|
+
"date",
|
|
264
|
+
Filter.date({
|
|
265
|
+
operator: "between",
|
|
266
|
+
min: new Date("2024-01-01T00:00:00Z"),
|
|
267
|
+
max: new Date("2024-06-01T00:00:00Z"),
|
|
268
|
+
}),
|
|
269
|
+
);
|
|
270
|
+
render(<DateFilterMenu column={column} filterType="date" />);
|
|
271
|
+
expect(screen.getByLabelText("range")).toBeInTheDocument();
|
|
272
|
+
expect(screen.getByRole("button", { name: /apply/i })).not.toBeDisabled();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("comparison operator swaps range for a single value picker", () => {
|
|
276
|
+
const column = mockDateColumn(
|
|
277
|
+
"date",
|
|
278
|
+
Filter.date({
|
|
279
|
+
operator: ">",
|
|
280
|
+
value: new Date("2024-01-01T00:00:00Z"),
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
283
|
+
render(<DateFilterMenu column={column} filterType="date" />);
|
|
284
|
+
expect(screen.getByLabelText("value")).toBeInTheDocument();
|
|
285
|
+
expect(screen.queryByLabelText("range")).not.toBeInTheDocument();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("time filter type renders two TimeFields for between", () => {
|
|
289
|
+
const column = mockDateColumn("time");
|
|
290
|
+
render(<DateFilterMenu column={column} filterType="time" />);
|
|
291
|
+
expect(screen.getByLabelText("min")).toBeInTheDocument();
|
|
292
|
+
expect(screen.getByLabelText("max")).toBeInTheDocument();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("selecting a nullish operator hides value inputs and commits on Apply", () => {
|
|
296
|
+
const column = mockDateColumn("date");
|
|
297
|
+
render(<DateFilterMenu column={column} filterType="date" />);
|
|
298
|
+
fireEvent.click(screen.getByRole("combobox"));
|
|
299
|
+
const listbox = screen.getByRole("listbox");
|
|
300
|
+
fireEvent.click(within(listbox).getByText("Is null"));
|
|
301
|
+
expect(screen.queryByLabelText("range")).not.toBeInTheDocument();
|
|
302
|
+
expect(screen.queryByLabelText("value")).not.toBeInTheDocument();
|
|
303
|
+
fireEvent.click(screen.getByRole("button", { name: /apply/i }));
|
|
304
|
+
expect(column.setFilterValue).toHaveBeenCalledWith(
|
|
305
|
+
Filter.date({ operator: "is_null" }),
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
@@ -26,7 +26,14 @@ beforeAll(() => {
|
|
|
26
26
|
|
|
27
27
|
function makeColumn(
|
|
28
28
|
id: string,
|
|
29
|
-
filterType:
|
|
29
|
+
filterType:
|
|
30
|
+
| "text"
|
|
31
|
+
| "number"
|
|
32
|
+
| "boolean"
|
|
33
|
+
| "select"
|
|
34
|
+
| "date"
|
|
35
|
+
| "datetime"
|
|
36
|
+
| "time",
|
|
30
37
|
): Column<unknown, unknown> {
|
|
31
38
|
return {
|
|
32
39
|
id,
|
|
@@ -35,7 +42,13 @@ function makeColumn(
|
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
function mockTable(): Table<unknown> {
|
|
38
|
-
const columns = [
|
|
45
|
+
const columns = [
|
|
46
|
+
makeColumn("name", "text"),
|
|
47
|
+
makeColumn("age", "number"),
|
|
48
|
+
makeColumn("when", "date"),
|
|
49
|
+
makeColumn("at", "datetime"),
|
|
50
|
+
makeColumn("clock", "time"),
|
|
51
|
+
];
|
|
39
52
|
return {
|
|
40
53
|
getAllColumns: () => columns,
|
|
41
54
|
getColumn: (id: string) => columns.find((c) => c.id === id),
|
|
@@ -144,6 +157,79 @@ describe("FilterPillEditor — snapshot rehydration", () => {
|
|
|
144
157
|
});
|
|
145
158
|
});
|
|
146
159
|
|
|
160
|
+
describe("FilterPillEditor — date/datetime/time", () => {
|
|
161
|
+
it("rehydrates a date between snapshot with the range picker", () => {
|
|
162
|
+
renderWithProviders(
|
|
163
|
+
<FilterPillEditor
|
|
164
|
+
snapshot={{
|
|
165
|
+
columnId: "when",
|
|
166
|
+
value: Filter.date({
|
|
167
|
+
operator: "between",
|
|
168
|
+
min: new Date("2024-01-01T00:00:00Z"),
|
|
169
|
+
max: new Date("2024-06-01T00:00:00Z"),
|
|
170
|
+
}),
|
|
171
|
+
}}
|
|
172
|
+
table={mockTable()}
|
|
173
|
+
onClose={vi.fn()}
|
|
174
|
+
/>,
|
|
175
|
+
);
|
|
176
|
+
expect(screen.getByLabelText("range")).toBeInTheDocument();
|
|
177
|
+
expect(screen.queryByLabelText("value")).not.toBeInTheDocument();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("rehydrates a datetime <= snapshot with a single value picker", () => {
|
|
181
|
+
renderWithProviders(
|
|
182
|
+
<FilterPillEditor
|
|
183
|
+
snapshot={{
|
|
184
|
+
columnId: "at",
|
|
185
|
+
value: Filter.datetime({
|
|
186
|
+
operator: "<=",
|
|
187
|
+
value: new Date("2024-06-01T12:00:00Z"),
|
|
188
|
+
}),
|
|
189
|
+
}}
|
|
190
|
+
table={mockTable()}
|
|
191
|
+
onClose={vi.fn()}
|
|
192
|
+
/>,
|
|
193
|
+
);
|
|
194
|
+
expect(screen.getByLabelText("value")).toBeInTheDocument();
|
|
195
|
+
expect(screen.queryByLabelText("range")).not.toBeInTheDocument();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("renders min/max TimeFields for time between", () => {
|
|
199
|
+
renderWithProviders(
|
|
200
|
+
<FilterPillEditor
|
|
201
|
+
snapshot={{
|
|
202
|
+
columnId: "clock",
|
|
203
|
+
value: Filter.time({
|
|
204
|
+
operator: "between",
|
|
205
|
+
min: new Date("2024-01-01T08:00:00Z"),
|
|
206
|
+
max: new Date("2024-01-01T17:00:00Z"),
|
|
207
|
+
}),
|
|
208
|
+
}}
|
|
209
|
+
table={mockTable()}
|
|
210
|
+
onClose={vi.fn()}
|
|
211
|
+
/>,
|
|
212
|
+
);
|
|
213
|
+
expect(screen.getByLabelText("min")).toBeInTheDocument();
|
|
214
|
+
expect(screen.getByLabelText("max")).toBeInTheDocument();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("hides the value slot for date is_null", () => {
|
|
218
|
+
renderWithProviders(
|
|
219
|
+
<FilterPillEditor
|
|
220
|
+
snapshot={{
|
|
221
|
+
columnId: "when",
|
|
222
|
+
value: Filter.date({ operator: "is_null" }),
|
|
223
|
+
}}
|
|
224
|
+
table={mockTable()}
|
|
225
|
+
onClose={vi.fn()}
|
|
226
|
+
/>,
|
|
227
|
+
);
|
|
228
|
+
expect(screen.queryByText("Value")).not.toBeInTheDocument();
|
|
229
|
+
expect(screen.queryByLabelText("range")).not.toBeInTheDocument();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
147
233
|
describe("FilterPillEditor — apply", () => {
|
|
148
234
|
it("commits a number > filter via setColumnFilters", () => {
|
|
149
235
|
const table = mockTable();
|
|
@@ -215,22 +215,93 @@ describe("filterToFilterCondition", () => {
|
|
|
215
215
|
]);
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
-
it("handles date filter
|
|
219
|
-
const min = new Date(
|
|
220
|
-
const max = new Date(
|
|
218
|
+
it("handles date between filter", () => {
|
|
219
|
+
const min = new Date(2024, 0, 1);
|
|
220
|
+
const max = new Date(2024, 11, 31);
|
|
221
221
|
const result = filterToFilterCondition(
|
|
222
222
|
"created",
|
|
223
|
-
Filter.date({ min, max }),
|
|
223
|
+
Filter.date({ operator: "between", min, max }),
|
|
224
224
|
);
|
|
225
|
-
expect(result).
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
225
|
+
expect(result).toEqual([
|
|
226
|
+
{
|
|
227
|
+
column_id: "created",
|
|
228
|
+
operator: "between",
|
|
229
|
+
value: { min: "2024-01-01", max: "2024-12-31" },
|
|
230
|
+
type: "condition",
|
|
231
|
+
negate: false,
|
|
232
|
+
},
|
|
233
|
+
]);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("handles date comparison filter", () => {
|
|
237
|
+
const value = new Date(2024, 5, 15);
|
|
238
|
+
const result = filterToFilterCondition(
|
|
239
|
+
"created",
|
|
240
|
+
Filter.date({ operator: ">=", value }),
|
|
241
|
+
);
|
|
242
|
+
expect(result).toEqual([
|
|
243
|
+
{
|
|
244
|
+
column_id: "created",
|
|
245
|
+
operator: ">=",
|
|
246
|
+
value: "2024-06-15",
|
|
247
|
+
type: "condition",
|
|
248
|
+
negate: false,
|
|
249
|
+
},
|
|
250
|
+
]);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("handles datetime between filter as local ISO string without TZ", () => {
|
|
254
|
+
const min = new Date(2024, 0, 1, 0, 0, 0);
|
|
255
|
+
const max = new Date(2024, 11, 31, 23, 59, 59);
|
|
256
|
+
const result = filterToFilterCondition(
|
|
257
|
+
"created",
|
|
258
|
+
Filter.datetime({ operator: "between", min, max }),
|
|
259
|
+
);
|
|
260
|
+
expect(result).toEqual([
|
|
261
|
+
{
|
|
262
|
+
column_id: "created",
|
|
263
|
+
operator: "between",
|
|
264
|
+
value: {
|
|
265
|
+
min: "2024-01-01T00:00:00",
|
|
266
|
+
max: "2024-12-31T23:59:59",
|
|
267
|
+
},
|
|
268
|
+
type: "condition",
|
|
269
|
+
negate: false,
|
|
270
|
+
},
|
|
271
|
+
]);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("handles time between filter as HH:MM:SS", () => {
|
|
275
|
+
const min = new Date(2024, 0, 1, 9, 30, 0);
|
|
276
|
+
const max = new Date(2024, 0, 1, 17, 45, 15);
|
|
277
|
+
const result = filterToFilterCondition(
|
|
278
|
+
"start",
|
|
279
|
+
Filter.time({ operator: "between", min, max }),
|
|
280
|
+
);
|
|
281
|
+
expect(result).toEqual([
|
|
282
|
+
{
|
|
283
|
+
column_id: "start",
|
|
284
|
+
operator: "between",
|
|
285
|
+
value: { min: "09:30:00", max: "17:45:15" },
|
|
286
|
+
type: "condition",
|
|
287
|
+
negate: false,
|
|
288
|
+
},
|
|
289
|
+
]);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("handles date is_null filter", () => {
|
|
293
|
+
const result = filterToFilterCondition(
|
|
294
|
+
"created",
|
|
295
|
+
Filter.date({ operator: "is_null" }),
|
|
296
|
+
);
|
|
297
|
+
expect(result).toEqual([
|
|
298
|
+
{
|
|
299
|
+
column_id: "created",
|
|
300
|
+
operator: "is_null",
|
|
301
|
+
type: "condition",
|
|
302
|
+
negate: false,
|
|
303
|
+
},
|
|
304
|
+
]);
|
|
234
305
|
});
|
|
235
306
|
|
|
236
307
|
it("every condition has type and negate fields", () => {
|
|
@@ -44,14 +44,19 @@ import { OPERATOR_LABELS } from "./operator-labels";
|
|
|
44
44
|
import {
|
|
45
45
|
type ColumnFilterForType,
|
|
46
46
|
type ColumnFilterValue,
|
|
47
|
+
DATETIME_OPS,
|
|
47
48
|
Filter,
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
isDatetimeComparisonOp,
|
|
50
|
+
isNumberComparisonOp,
|
|
51
|
+
isTextScalarOp,
|
|
50
52
|
NUMBER_OPS,
|
|
51
53
|
TEXT_OPS,
|
|
52
|
-
TEXT_SCALAR_OPS,
|
|
53
|
-
type TextScalarOp,
|
|
54
54
|
} from "./filters";
|
|
55
|
+
import {
|
|
56
|
+
type DateLikeFilterType,
|
|
57
|
+
DateLikeInput,
|
|
58
|
+
DateLikeRangeInput,
|
|
59
|
+
} from "./date-filter-inputs";
|
|
55
60
|
import {
|
|
56
61
|
ClearFilterMenuItem,
|
|
57
62
|
FilterButtons,
|
|
@@ -283,19 +288,21 @@ export function renderMenuItemFilter<TData, TValue>(
|
|
|
283
288
|
return null;
|
|
284
289
|
}
|
|
285
290
|
|
|
286
|
-
if (
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
291
|
+
if (
|
|
292
|
+
filterType === "date" ||
|
|
293
|
+
filterType === "datetime" ||
|
|
294
|
+
filterType === "time"
|
|
295
|
+
) {
|
|
296
|
+
return (
|
|
297
|
+
<DropdownMenuSub>
|
|
298
|
+
{filterMenuItem}
|
|
299
|
+
<DropdownMenuPortal>
|
|
300
|
+
<DropdownMenuSubContent>
|
|
301
|
+
<DateFilterMenu column={column} filterType={filterType} />
|
|
302
|
+
</DropdownMenuSubContent>
|
|
303
|
+
</DropdownMenuPortal>
|
|
304
|
+
</DropdownMenuSub>
|
|
305
|
+
);
|
|
299
306
|
}
|
|
300
307
|
|
|
301
308
|
logNever(filterType);
|
|
@@ -369,12 +376,6 @@ const BooleanFilter = <TData, TValue>({
|
|
|
369
376
|
);
|
|
370
377
|
};
|
|
371
378
|
|
|
372
|
-
const NUMBER_COMPARISON_SET: ReadonlySet<OperatorType> = new Set(
|
|
373
|
-
NUMBER_COMPARISON_OPS,
|
|
374
|
-
);
|
|
375
|
-
const isNumberComparisonOp = (op: OperatorType): op is NumberComparisonOp =>
|
|
376
|
-
NUMBER_COMPARISON_SET.has(op);
|
|
377
|
-
|
|
378
379
|
type NumberComparisonFilter = Extract<
|
|
379
380
|
ColumnFilterForType<"number">,
|
|
380
381
|
{ value: number }
|
|
@@ -485,9 +486,134 @@ export const NumberFilterMenu = <TData, TValue>({
|
|
|
485
486
|
);
|
|
486
487
|
};
|
|
487
488
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
489
|
+
type DateComparisonFilter = Extract<
|
|
490
|
+
ColumnFilterForType<DateLikeFilterType>,
|
|
491
|
+
{ value: Date }
|
|
492
|
+
>;
|
|
493
|
+
const isDateComparisonFilter = (
|
|
494
|
+
filter: ColumnFilterForType<DateLikeFilterType>,
|
|
495
|
+
): filter is DateComparisonFilter => isDatetimeComparisonOp(filter.operator);
|
|
496
|
+
|
|
497
|
+
export const DateFilterMenu = <TData, TValue>({
|
|
498
|
+
column,
|
|
499
|
+
filterType,
|
|
500
|
+
}: {
|
|
501
|
+
column: Column<TData, TValue>;
|
|
502
|
+
filterType: DateLikeFilterType;
|
|
503
|
+
}) => {
|
|
504
|
+
const currentFilter = column.getFilterValue() as
|
|
505
|
+
| ColumnFilterForType<DateLikeFilterType>
|
|
506
|
+
| undefined;
|
|
507
|
+
const hasFilter = currentFilter !== undefined;
|
|
508
|
+
|
|
509
|
+
const [operator, setOperator] = useState<OperatorType>(
|
|
510
|
+
currentFilter?.operator ?? "between",
|
|
511
|
+
);
|
|
512
|
+
const [min, setMin] = useState<Date | undefined>(
|
|
513
|
+
currentFilter?.operator === "between" ? currentFilter.min : undefined,
|
|
514
|
+
);
|
|
515
|
+
const [max, setMax] = useState<Date | undefined>(
|
|
516
|
+
currentFilter?.operator === "between" ? currentFilter.max : undefined,
|
|
517
|
+
);
|
|
518
|
+
const [value, setValue] = useState<Date | undefined>(
|
|
519
|
+
currentFilter !== undefined && isDateComparisonFilter(currentFilter)
|
|
520
|
+
? currentFilter.value
|
|
521
|
+
: undefined,
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
const isComparison = isDatetimeComparisonOp(operator);
|
|
525
|
+
const isNullish = operator === "is_null" || operator === "is_not_null";
|
|
526
|
+
|
|
527
|
+
const applyDisabled =
|
|
528
|
+
(operator === "between" && (min === undefined || max === undefined)) ||
|
|
529
|
+
(isComparison && value === undefined);
|
|
530
|
+
|
|
531
|
+
const buildFilter = (
|
|
532
|
+
opts: Parameters<typeof Filter.date>[0],
|
|
533
|
+
): ColumnFilterForType<DateLikeFilterType> => {
|
|
534
|
+
switch (filterType) {
|
|
535
|
+
case "date":
|
|
536
|
+
return Filter.date(opts);
|
|
537
|
+
case "datetime":
|
|
538
|
+
return Filter.datetime(opts);
|
|
539
|
+
case "time":
|
|
540
|
+
return Filter.time(opts);
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
const handleApply = () => {
|
|
545
|
+
if (isNullish) {
|
|
546
|
+
column.setFilterValue(buildFilter({ operator }));
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (operator === "between" && min !== undefined && max !== undefined) {
|
|
550
|
+
column.setFilterValue(buildFilter({ operator: "between", min, max }));
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
if (isComparison && value !== undefined) {
|
|
554
|
+
column.setFilterValue(buildFilter({ operator, value }));
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
const [resetKey, setResetKey] = useState(0);
|
|
559
|
+
const handleClear = () => {
|
|
560
|
+
setMin(undefined);
|
|
561
|
+
setMax(undefined);
|
|
562
|
+
setValue(undefined);
|
|
563
|
+
setResetKey((k) => k + 1);
|
|
564
|
+
column.setFilterValue(undefined);
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
const handleOperatorChange = (next: OperatorType) => {
|
|
568
|
+
setOperator(next);
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
return (
|
|
572
|
+
<div
|
|
573
|
+
className="flex flex-col gap-1 pt-3 px-2"
|
|
574
|
+
onKeyDownCapture={(e) => {
|
|
575
|
+
if (e.key === "Tab") {
|
|
576
|
+
e.stopPropagation();
|
|
577
|
+
}
|
|
578
|
+
}}
|
|
579
|
+
>
|
|
580
|
+
<OperatorSelect
|
|
581
|
+
operator={operator}
|
|
582
|
+
options={DATETIME_OPS}
|
|
583
|
+
onChange={handleOperatorChange}
|
|
584
|
+
/>
|
|
585
|
+
{operator === "between" && (
|
|
586
|
+
<DateLikeRangeInput
|
|
587
|
+
key={`${filterType}-${resetKey}`}
|
|
588
|
+
filterType={filterType}
|
|
589
|
+
min={min}
|
|
590
|
+
max={max}
|
|
591
|
+
onRangeChange={(nextMin, nextMax) => {
|
|
592
|
+
setMin(nextMin);
|
|
593
|
+
setMax(nextMax);
|
|
594
|
+
}}
|
|
595
|
+
className="shadow-none! border-border hover:shadow-none!"
|
|
596
|
+
/>
|
|
597
|
+
)}
|
|
598
|
+
{isComparison && (
|
|
599
|
+
<DateLikeInput
|
|
600
|
+
key={`${filterType}-${resetKey}`}
|
|
601
|
+
filterType={filterType}
|
|
602
|
+
value={value}
|
|
603
|
+
onChange={setValue}
|
|
604
|
+
aria-label="value"
|
|
605
|
+
className="shadow-none! border-border hover:shadow-none!"
|
|
606
|
+
/>
|
|
607
|
+
)}
|
|
608
|
+
<FilterButtons
|
|
609
|
+
onApply={handleApply}
|
|
610
|
+
onClear={handleClear}
|
|
611
|
+
clearButtonDisabled={!hasFilter}
|
|
612
|
+
applyButtonDisabled={applyDisabled}
|
|
613
|
+
/>
|
|
614
|
+
</div>
|
|
615
|
+
);
|
|
616
|
+
};
|
|
491
617
|
|
|
492
618
|
export const TextFilterMenu = <TData, TValue>({
|
|
493
619
|
column,
|