@marimo-team/islands 0.23.7-dev56 → 0.23.7-dev58
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-DGyytgGn.js} +10621 -1448
- package/dist/main.js +1347 -9983
- package/dist/{reveal-component-DuRlSS4j.js → reveal-component-40GHzspd.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
|
@@ -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) {
|
|
@@ -55,7 +55,9 @@ export class WsTransport implements IConnectionTransport {
|
|
|
55
55
|
// Match native EventTarget dedupe: a second addEventListener with the
|
|
56
56
|
// same listener is a no-op. Without this, repeated adds leak wrappers
|
|
57
57
|
// on the inner socket and double-fire on close.
|
|
58
|
-
if (this.closeWrappers.has(userCb))
|
|
58
|
+
if (this.closeWrappers.has(userCb)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
59
61
|
const wrapper: ConnectionTransportCallback<"close"> = (e) => {
|
|
60
62
|
if (this.inner.retryCount >= MAX_RETRIES) {
|
|
61
63
|
userCb(
|