@flytedan/flytebot-design-system 0.29.0 → 0.31.0
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/index.cjs +248 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -38
- package/dist/index.d.ts +65 -38
- package/dist/index.js +248 -119
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/styles/components.css +2 -2
package/dist/index.js
CHANGED
|
@@ -1517,7 +1517,7 @@ var BANNER_TONE_STYLE = {
|
|
|
1517
1517
|
};
|
|
1518
1518
|
function Banner({ tone = "info", title, detail, actions, role }) {
|
|
1519
1519
|
const t = BANNER_TONE_STYLE[tone];
|
|
1520
|
-
const liveRole = role || (tone === "danger"
|
|
1520
|
+
const liveRole = role || (tone === "danger" ? "alert" : "status");
|
|
1521
1521
|
return /* @__PURE__ */ jsxs19(
|
|
1522
1522
|
"div",
|
|
1523
1523
|
{
|
|
@@ -2769,8 +2769,10 @@ function NumberInput({
|
|
|
2769
2769
|
max = Infinity,
|
|
2770
2770
|
step = 1,
|
|
2771
2771
|
bigStep,
|
|
2772
|
+
integer = false,
|
|
2772
2773
|
value,
|
|
2773
2774
|
onChange,
|
|
2775
|
+
onValidityChange,
|
|
2774
2776
|
format = true,
|
|
2775
2777
|
className = "",
|
|
2776
2778
|
style,
|
|
@@ -2786,17 +2788,34 @@ function NumberInput({
|
|
|
2786
2788
|
const n = Number(s);
|
|
2787
2789
|
return isNaN(n) ? null : n;
|
|
2788
2790
|
};
|
|
2791
|
+
const validationMessage = (n) => {
|
|
2792
|
+
if (integer && !Number.isInteger(n)) return "Enter a whole number";
|
|
2793
|
+
if (n < min) return "Must be \u2265 " + min;
|
|
2794
|
+
if (n > max) return "Must be \u2264 " + max;
|
|
2795
|
+
return null;
|
|
2796
|
+
};
|
|
2789
2797
|
const [text, setText] = React21.useState(value == null || value === "" ? "" : String(value));
|
|
2790
2798
|
const [editing, setEditing] = React21.useState(false);
|
|
2799
|
+
const [invalidMessage, setInvalidMessage] = React21.useState(null);
|
|
2791
2800
|
const timer = React21.useRef(null);
|
|
2792
2801
|
React21.useEffect(() => {
|
|
2793
|
-
if (!editing)
|
|
2802
|
+
if (!editing) {
|
|
2803
|
+
setText(value == null || value === "" ? "" : String(value));
|
|
2804
|
+
setInvalidMessage(null);
|
|
2805
|
+
}
|
|
2794
2806
|
}, [value, editing]);
|
|
2807
|
+
React21.useEffect(() => {
|
|
2808
|
+
onValidityChange && onValidityChange(invalidMessage == null);
|
|
2809
|
+
}, [invalidMessage]);
|
|
2795
2810
|
const clamp = (n) => Math.min(max, Math.max(min, n));
|
|
2796
2811
|
const fire = (n) => onChange && onChange({ target: { value: n } });
|
|
2797
2812
|
const bump = (dir, mult = 1) => {
|
|
2798
|
-
const cur = num(text) ?? num(value) ?? (min === -Infinity ? 0 : min);
|
|
2799
|
-
const
|
|
2813
|
+
const cur = (invalidMessage ? null : num(text)) ?? num(value) ?? (min === -Infinity ? 0 : min);
|
|
2814
|
+
const amount = (bigStep && mult > 1 ? bigStep : step) * (bigStep ? 1 : mult);
|
|
2815
|
+
let next = cur + dir * amount;
|
|
2816
|
+
if (integer) next = Math.round(next);
|
|
2817
|
+
next = clamp(next);
|
|
2818
|
+
setInvalidMessage(null);
|
|
2800
2819
|
setText(String(next));
|
|
2801
2820
|
fire(next);
|
|
2802
2821
|
};
|
|
@@ -2820,7 +2839,8 @@ function NumberInput({
|
|
|
2820
2839
|
const n = num(text);
|
|
2821
2840
|
return n == null ? "" : format ? n.toLocaleString() : String(n);
|
|
2822
2841
|
})();
|
|
2823
|
-
const
|
|
2842
|
+
const shownError = error || invalidMessage || void 0;
|
|
2843
|
+
const box = ["fd-input", "fd-input-num", shownError ? "fd-input-error" : "", disabled ? "fd-input-disabled" : ""].filter(Boolean).join(" ");
|
|
2824
2844
|
const fieldName = label || ariaLabel;
|
|
2825
2845
|
const stepperName = (verb) => fieldName ? verb + " " + fieldName : verb;
|
|
2826
2846
|
return /* @__PURE__ */ jsxs36("div", { className: ["fd-field", className].filter(Boolean).join(" "), style, children: [
|
|
@@ -2836,30 +2856,29 @@ function NumberInput({
|
|
|
2836
2856
|
disabled,
|
|
2837
2857
|
placeholder,
|
|
2838
2858
|
value: shown,
|
|
2839
|
-
"aria-invalid":
|
|
2840
|
-
"aria-describedby":
|
|
2859
|
+
"aria-invalid": shownError ? "true" : void 0,
|
|
2860
|
+
"aria-describedby": shownError ? errorId : void 0,
|
|
2841
2861
|
onFocus: () => setEditing(true),
|
|
2842
2862
|
onBlur: () => {
|
|
2843
2863
|
setEditing(false);
|
|
2844
|
-
if (text.trim() === "")
|
|
2845
|
-
fire(void 0);
|
|
2846
|
-
return;
|
|
2847
|
-
}
|
|
2848
|
-
const n = num(text);
|
|
2849
|
-
if (n != null) {
|
|
2850
|
-
const c = clamp(n);
|
|
2851
|
-
setText(String(c));
|
|
2852
|
-
fire(c);
|
|
2853
|
-
}
|
|
2864
|
+
if (text.trim() === "") fire(void 0);
|
|
2854
2865
|
},
|
|
2855
2866
|
onChange: (e) => {
|
|
2856
|
-
|
|
2857
|
-
|
|
2867
|
+
const raw = e.target.value;
|
|
2868
|
+
setText(raw);
|
|
2869
|
+
if (raw.trim() === "") {
|
|
2870
|
+
setInvalidMessage(null);
|
|
2858
2871
|
fire(void 0);
|
|
2859
2872
|
return;
|
|
2860
2873
|
}
|
|
2861
|
-
const n = num(
|
|
2862
|
-
if (n
|
|
2874
|
+
const n = num(raw);
|
|
2875
|
+
if (n == null) {
|
|
2876
|
+
setInvalidMessage("Enter a number");
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
const message = validationMessage(n);
|
|
2880
|
+
setInvalidMessage(message);
|
|
2881
|
+
if (!message) fire(n);
|
|
2863
2882
|
},
|
|
2864
2883
|
onKeyDown: (e) => {
|
|
2865
2884
|
if (e.key === "ArrowUp") {
|
|
@@ -2902,9 +2921,9 @@ function NumberInput({
|
|
|
2902
2921
|
)
|
|
2903
2922
|
] })
|
|
2904
2923
|
] }),
|
|
2905
|
-
|
|
2924
|
+
shownError ? /* @__PURE__ */ jsxs36("span", { id: errorId, className: "fd-field-error", children: [
|
|
2906
2925
|
/* @__PURE__ */ jsx42("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
|
|
2907
|
-
|
|
2926
|
+
shownError
|
|
2908
2927
|
] }) : help ? /* @__PURE__ */ jsx42("span", { className: "fd-field-help", children: help }) : null
|
|
2909
2928
|
] });
|
|
2910
2929
|
}
|
|
@@ -3182,6 +3201,91 @@ function isFieldSet(field, value) {
|
|
|
3182
3201
|
}
|
|
3183
3202
|
}
|
|
3184
3203
|
}
|
|
3204
|
+
function validateFilterValue(field, value) {
|
|
3205
|
+
switch (field.type) {
|
|
3206
|
+
case "text":
|
|
3207
|
+
case "select":
|
|
3208
|
+
return { ok: true };
|
|
3209
|
+
case "multiSelect": {
|
|
3210
|
+
const v = value;
|
|
3211
|
+
if (!v || !Array.isArray(v.values) || v.values.length === 0) {
|
|
3212
|
+
return { ok: false, reason: "Select at least one value" };
|
|
3213
|
+
}
|
|
3214
|
+
if (!operatorsFor(field).includes(v.operator)) {
|
|
3215
|
+
return { ok: false, reason: "That operator isn't allowed for " + field.label };
|
|
3216
|
+
}
|
|
3217
|
+
return { ok: true };
|
|
3218
|
+
}
|
|
3219
|
+
case "range": {
|
|
3220
|
+
const v = value;
|
|
3221
|
+
if (!v || v.min == null && v.max == null) {
|
|
3222
|
+
return { ok: false, reason: "Enter at least one bound" };
|
|
3223
|
+
}
|
|
3224
|
+
const lo = field.min ?? -Infinity;
|
|
3225
|
+
const hi = field.max ?? Infinity;
|
|
3226
|
+
for (const n of [v.min, v.max]) {
|
|
3227
|
+
if (n == null) continue;
|
|
3228
|
+
if (field.integer && !Number.isInteger(n)) return { ok: false, reason: "Enter a whole number" };
|
|
3229
|
+
if (n < lo) return { ok: false, reason: "Must be \u2265 " + lo };
|
|
3230
|
+
if (n > hi) return { ok: false, reason: "Must be \u2264 " + hi };
|
|
3231
|
+
}
|
|
3232
|
+
if (v.min != null && v.max != null && v.min > v.max) {
|
|
3233
|
+
return { ok: false, reason: "Must be \u2265 " + v.min };
|
|
3234
|
+
}
|
|
3235
|
+
return { ok: true };
|
|
3236
|
+
}
|
|
3237
|
+
case "date": {
|
|
3238
|
+
const v = value;
|
|
3239
|
+
if (!v) return { ok: false, reason: "Pick a date value" };
|
|
3240
|
+
if (v.kind === "withinDays") {
|
|
3241
|
+
const maxDays = field.maxDays ?? 3650;
|
|
3242
|
+
if (v.days == null) return { ok: false, reason: "Enter a number of days" };
|
|
3243
|
+
if (!Number.isInteger(v.days)) return { ok: false, reason: "Enter a whole number" };
|
|
3244
|
+
if (v.days < 1) return { ok: false, reason: "Must be \u2265 1" };
|
|
3245
|
+
if (v.days > maxDays) return { ok: false, reason: "Must be \u2264 " + maxDays };
|
|
3246
|
+
return { ok: true };
|
|
3247
|
+
}
|
|
3248
|
+
if (!v.from || !v.to) return { ok: false, reason: "Pick both dates" };
|
|
3249
|
+
if (v.from > v.to) return { ok: false, reason: "The start date must be on or before the end date" };
|
|
3250
|
+
return { ok: true };
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
function isEmptyCandidate(field, candidate) {
|
|
3255
|
+
switch (field.type) {
|
|
3256
|
+
case "text":
|
|
3257
|
+
case "select":
|
|
3258
|
+
return false;
|
|
3259
|
+
case "multiSelect": {
|
|
3260
|
+
const v = candidate;
|
|
3261
|
+
return !v || !Array.isArray(v.values) || v.values.length === 0;
|
|
3262
|
+
}
|
|
3263
|
+
case "range": {
|
|
3264
|
+
const v = candidate;
|
|
3265
|
+
return !v || v.min == null && v.max == null;
|
|
3266
|
+
}
|
|
3267
|
+
case "date": {
|
|
3268
|
+
const v = candidate;
|
|
3269
|
+
if (!v) return true;
|
|
3270
|
+
return v.kind === "withinDays" ? v.days == null : !(v.from && v.to);
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
function assertValidDateField(field) {
|
|
3275
|
+
if (process.env.NODE_ENV === "production") return;
|
|
3276
|
+
const defaultDays = field.defaultDays ?? 30;
|
|
3277
|
+
const maxDays = field.maxDays ?? 3650;
|
|
3278
|
+
if (!Number.isInteger(maxDays) || maxDays < 1 || maxDays > 3650) {
|
|
3279
|
+
throw new Error(
|
|
3280
|
+
'FilterBar: date field "' + field.key + '" has an invalid maxDays (' + field.maxDays + ") \u2014 must be a whole number from 1 to 3650."
|
|
3281
|
+
);
|
|
3282
|
+
}
|
|
3283
|
+
if (!Number.isInteger(defaultDays) || defaultDays < 1 || defaultDays > maxDays) {
|
|
3284
|
+
throw new Error(
|
|
3285
|
+
'FilterBar: date field "' + field.key + '" has an invalid defaultDays (' + field.defaultDays + ") \u2014 must be a whole number from 1 to maxDays (" + maxDays + ")."
|
|
3286
|
+
);
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3185
3289
|
function chipLabel(field, value) {
|
|
3186
3290
|
switch (field.type) {
|
|
3187
3291
|
case "text":
|
|
@@ -3234,18 +3338,30 @@ function MultiSelectPanel({
|
|
|
3234
3338
|
field,
|
|
3235
3339
|
value,
|
|
3236
3340
|
anchorRef,
|
|
3237
|
-
|
|
3238
|
-
onRemove,
|
|
3341
|
+
onCommit,
|
|
3239
3342
|
onClose
|
|
3240
3343
|
}) {
|
|
3241
3344
|
const allowedOps = operatorsFor(field);
|
|
3242
|
-
const
|
|
3345
|
+
const [draftOperator, setDraftOperator] = React24.useState(value?.operator ?? allowedOps[0]);
|
|
3346
|
+
const operator = allowedOps.length === 1 ? allowedOps[0] : value?.operator ?? draftOperator;
|
|
3243
3347
|
const selected = value?.values ?? [];
|
|
3244
3348
|
const selectedRef = React24.useRef(selected);
|
|
3245
3349
|
selectedRef.current = selected;
|
|
3350
|
+
const operatorRef = React24.useRef(operator);
|
|
3351
|
+
operatorRef.current = operator;
|
|
3352
|
+
const mountedRef = React24.useRef(true);
|
|
3353
|
+
React24.useEffect(() => {
|
|
3354
|
+
mountedRef.current = true;
|
|
3355
|
+
return () => {
|
|
3356
|
+
mountedRef.current = false;
|
|
3357
|
+
};
|
|
3358
|
+
}, []);
|
|
3246
3359
|
const commit = (nextValues, nextOperator = operator) => {
|
|
3247
|
-
|
|
3248
|
-
|
|
3360
|
+
onCommit({ operator: nextOperator, values: nextValues });
|
|
3361
|
+
};
|
|
3362
|
+
const setOperator = (op) => {
|
|
3363
|
+
setDraftOperator(op);
|
|
3364
|
+
if (selected.length > 0) commit(selected, op);
|
|
3249
3365
|
};
|
|
3250
3366
|
const [mode2, setMode] = React24.useState("search");
|
|
3251
3367
|
const [query, setQuery] = React24.useState("");
|
|
@@ -3283,18 +3399,20 @@ function MultiSelectPanel({
|
|
|
3283
3399
|
const options = field.loadOptions ? asyncState.options ?? [] : (field.options ?? []).filter((o) => o.label.toLowerCase().includes(query.toLowerCase()));
|
|
3284
3400
|
const showLoading = !!field.loadOptions && asyncState.loading;
|
|
3285
3401
|
const showError = !!field.loadOptions && asyncState.error;
|
|
3286
|
-
const
|
|
3402
|
+
const hasListbox = !showLoading && !showError && options.length > 0;
|
|
3287
3403
|
const isSelected = (o) => selected.some((s) => s.value === o.value);
|
|
3288
3404
|
const toggle = (o) => {
|
|
3289
3405
|
const next = isSelected(o) ? selected.filter((s) => s.value !== o.value) : [...selected, o];
|
|
3290
3406
|
commit(next);
|
|
3291
3407
|
};
|
|
3292
3408
|
const nav = useListboxNav({ count: options.length, onSelect: (i) => toggle(options[i]) });
|
|
3293
|
-
useListboxScrollIntoView(nav.active, listRef,
|
|
3409
|
+
useListboxScrollIntoView(nav.active, listRef, hasListbox);
|
|
3294
3410
|
React24.useEffect(() => {
|
|
3295
3411
|
nav.setActive(0);
|
|
3296
3412
|
}, [query, options.length]);
|
|
3297
|
-
const activeId =
|
|
3413
|
+
const activeId = hasListbox && options[nav.active] ? listId + "-opt-" + nav.active : void 0;
|
|
3414
|
+
const liveRole = showError ? "alert" : "status";
|
|
3415
|
+
const liveMessage = showLoading ? "Loading\u2026" : showError ? "Couldn't load options." : options.length === 0 ? "No matches" : "";
|
|
3298
3416
|
const [pasteText, setPasteText] = React24.useState("");
|
|
3299
3417
|
const [pasteLoading, setPasteLoading] = React24.useState(false);
|
|
3300
3418
|
const [unmatchedTokens, setUnmatchedTokens] = React24.useState([]);
|
|
@@ -3310,19 +3428,21 @@ function MultiSelectPanel({
|
|
|
3310
3428
|
setPasteLoading(true);
|
|
3311
3429
|
setPasteError(null);
|
|
3312
3430
|
field.resolve(tokens).then((result) => {
|
|
3431
|
+
if (!mountedRef.current) return;
|
|
3313
3432
|
setPasteLoading(false);
|
|
3314
3433
|
setUnmatchedTokens(result.unmatched);
|
|
3315
3434
|
const merged = new Map(selectedRef.current.map((s) => [s.value, s]));
|
|
3316
3435
|
for (const m of result.matched) merged.set(m.value, { value: m.value, label: m.label });
|
|
3317
|
-
commit(Array.from(merged.values()));
|
|
3436
|
+
commit(Array.from(merged.values()), operatorRef.current);
|
|
3318
3437
|
setPasteText("");
|
|
3319
3438
|
}).catch((err) => {
|
|
3439
|
+
if (!mountedRef.current) return;
|
|
3320
3440
|
setPasteLoading(false);
|
|
3321
3441
|
setPasteError(err instanceof Error ? err.message : "Could not resolve the pasted list.");
|
|
3322
3442
|
});
|
|
3323
3443
|
};
|
|
3324
3444
|
const header = /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
|
|
3325
|
-
allowedOps.length > 1 ? /* @__PURE__ */ jsx45(OperatorToggle, { operator, onChange:
|
|
3445
|
+
allowedOps.length > 1 ? /* @__PURE__ */ jsx45(OperatorToggle, { operator, onChange: setOperator, label: field.label }) : null,
|
|
3326
3446
|
field.resolve ? /* @__PURE__ */ jsx45(
|
|
3327
3447
|
SegmentedControl,
|
|
3328
3448
|
{
|
|
@@ -3339,8 +3459,8 @@ function MultiSelectPanel({
|
|
|
3339
3459
|
{
|
|
3340
3460
|
ref: inputRef,
|
|
3341
3461
|
role: "combobox",
|
|
3342
|
-
"aria-expanded":
|
|
3343
|
-
"aria-controls":
|
|
3462
|
+
"aria-expanded": hasListbox,
|
|
3463
|
+
"aria-controls": hasListbox ? listId : void 0,
|
|
3344
3464
|
"aria-autocomplete": "list",
|
|
3345
3465
|
"aria-activedescendant": activeId,
|
|
3346
3466
|
"aria-label": field.label,
|
|
@@ -3396,33 +3516,41 @@ function MultiSelectPanel({
|
|
|
3396
3516
|
] }),
|
|
3397
3517
|
/* @__PURE__ */ jsx45("span", { className: "fd-row fd-wrap fd-gap-xs", children: selected.map((v) => /* @__PURE__ */ jsx45(Tag, { onRemove: () => commit(selected.filter((s) => s.value !== v.value)), children: v.label }, v.value)) })
|
|
3398
3518
|
] }) : null
|
|
3399
|
-
] }) :
|
|
3400
|
-
/* @__PURE__ */
|
|
3401
|
-
|
|
3402
|
-
] }) : showError ? /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
|
|
3403
|
-
/* @__PURE__ */ jsx45("span", { className: "fd-body-sm", role: "alert", children: "Couldn't load options." }),
|
|
3404
|
-
/* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: () => setRetryTick((n) => n + 1), children: "Retry" })
|
|
3405
|
-
] }) : options.length === 0 ? /* @__PURE__ */ jsx45("span", { className: "fd-body-sm fd-muted", role: "status", children: "No matches" }) : /* @__PURE__ */ jsx45("span", { id: listId, role: "listbox", "aria-label": field.label, "aria-multiselectable": "true", ref: listRef, className: "fd-pop-list", children: options.map((o, i) => {
|
|
3406
|
-
const on = isSelected(o);
|
|
3407
|
-
return /* @__PURE__ */ jsxs39(
|
|
3408
|
-
"div",
|
|
3519
|
+
] }) : /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
|
|
3520
|
+
/* @__PURE__ */ jsxs39(
|
|
3521
|
+
"span",
|
|
3409
3522
|
{
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
role: "option",
|
|
3413
|
-
tabIndex: -1,
|
|
3414
|
-
"aria-selected": on,
|
|
3415
|
-
className: ["fd-opt", i === nav.active ? "is-active" : "", on ? "is-selected" : ""].filter(Boolean).join(" "),
|
|
3416
|
-
onMouseEnter: () => nav.setActive(i),
|
|
3417
|
-
onClick: () => toggle(o),
|
|
3523
|
+
className: "fd-row fd-gap-xs" + (liveMessage && !showError ? " fd-muted" : ""),
|
|
3524
|
+
role: liveRole,
|
|
3418
3525
|
children: [
|
|
3419
|
-
/* @__PURE__ */ jsx45("
|
|
3420
|
-
/* @__PURE__ */ jsx45("span", { className: "fd-
|
|
3526
|
+
showLoading ? /* @__PURE__ */ jsx45("span", { className: "fd-spinner", "aria-hidden": "true" }) : null,
|
|
3527
|
+
liveMessage ? /* @__PURE__ */ jsx45("span", { className: "fd-body-sm", children: liveMessage }) : null
|
|
3421
3528
|
]
|
|
3422
|
-
}
|
|
3423
|
-
|
|
3424
|
-
)
|
|
3425
|
-
|
|
3529
|
+
}
|
|
3530
|
+
),
|
|
3531
|
+
showError ? /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: () => setRetryTick((n) => n + 1), children: "Retry" }) : null,
|
|
3532
|
+
hasListbox ? /* @__PURE__ */ jsx45("span", { id: listId, role: "listbox", "aria-label": field.label, "aria-multiselectable": "true", ref: listRef, className: "fd-pop-list", children: options.map((o, i) => {
|
|
3533
|
+
const on = isSelected(o);
|
|
3534
|
+
return /* @__PURE__ */ jsxs39(
|
|
3535
|
+
"div",
|
|
3536
|
+
{
|
|
3537
|
+
id: listId + "-opt-" + i,
|
|
3538
|
+
"data-i": i,
|
|
3539
|
+
role: "option",
|
|
3540
|
+
tabIndex: -1,
|
|
3541
|
+
"aria-selected": on,
|
|
3542
|
+
className: ["fd-opt", i === nav.active ? "is-active" : "", on ? "is-selected" : ""].filter(Boolean).join(" "),
|
|
3543
|
+
onMouseEnter: () => nav.setActive(i),
|
|
3544
|
+
onClick: () => toggle(o),
|
|
3545
|
+
children: [
|
|
3546
|
+
/* @__PURE__ */ jsx45("i", { className: "fd-filterbar-opt-check ph " + (on ? "ph-check-square is-checked" : "ph-square"), "aria-hidden": "true" }),
|
|
3547
|
+
/* @__PURE__ */ jsx45("span", { className: "fd-opt-label", children: o.label })
|
|
3548
|
+
]
|
|
3549
|
+
},
|
|
3550
|
+
o.value
|
|
3551
|
+
);
|
|
3552
|
+
}) }) : null
|
|
3553
|
+
] });
|
|
3426
3554
|
const footer = mode2 === "paste" ? /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: doResolve, disabled: pasteLoading || !pasteText.trim(), children: pasteLoading ? "Resolving\u2026" : "Resolve" }) : void 0;
|
|
3427
3555
|
return /* @__PURE__ */ jsx45(
|
|
3428
3556
|
Popover,
|
|
@@ -3444,22 +3572,21 @@ function RangePanel({
|
|
|
3444
3572
|
field,
|
|
3445
3573
|
value,
|
|
3446
3574
|
anchorRef,
|
|
3447
|
-
|
|
3448
|
-
onRemove,
|
|
3575
|
+
onCommit,
|
|
3449
3576
|
onClose
|
|
3450
3577
|
}) {
|
|
3451
3578
|
const [draftMin, setDraftMin] = React24.useState(value?.min);
|
|
3452
3579
|
const [draftMax, setDraftMax] = React24.useState(value?.max);
|
|
3453
|
-
const
|
|
3580
|
+
const [minTextValid, setMinTextValid] = React24.useState(true);
|
|
3581
|
+
const [maxTextValid, setMaxTextValid] = React24.useState(true);
|
|
3582
|
+
const isEmpty = draftMin == null && draftMax == null;
|
|
3583
|
+
const validation = isEmpty ? { ok: true } : validateFilterValue(field, { min: draftMin, max: draftMax });
|
|
3584
|
+
const comboError = !isEmpty && !validation.ok ? validation.reason : void 0;
|
|
3585
|
+
const doneDisabled = !minTextValid || !maxTextValid || !!comboError;
|
|
3454
3586
|
const apply = (nextMin, nextMax) => {
|
|
3455
3587
|
setDraftMin(nextMin);
|
|
3456
3588
|
setDraftMax(nextMax);
|
|
3457
|
-
|
|
3458
|
-
onRemove();
|
|
3459
|
-
return;
|
|
3460
|
-
}
|
|
3461
|
-
if (nextMin != null && nextMax != null && nextMin > nextMax) return;
|
|
3462
|
-
onChange({ min: nextMin, max: nextMax });
|
|
3589
|
+
onCommit(nextMin == null && nextMax == null ? void 0 : { min: nextMin, max: nextMax });
|
|
3463
3590
|
};
|
|
3464
3591
|
return /* @__PURE__ */ jsx45(
|
|
3465
3592
|
Popover,
|
|
@@ -3471,7 +3598,7 @@ function RangePanel({
|
|
|
3471
3598
|
offset: 6,
|
|
3472
3599
|
width: PANEL_WIDTH.range,
|
|
3473
3600
|
label: field.label,
|
|
3474
|
-
footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: onClose, children: "Done" }),
|
|
3601
|
+
footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: onClose, disabled: doneDisabled, children: "Done" }),
|
|
3475
3602
|
children: /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
|
|
3476
3603
|
/* @__PURE__ */ jsx45(
|
|
3477
3604
|
NumberInput,
|
|
@@ -3481,8 +3608,10 @@ function RangePanel({
|
|
|
3481
3608
|
min: field.min,
|
|
3482
3609
|
max: field.max,
|
|
3483
3610
|
step: field.step,
|
|
3611
|
+
integer: field.integer,
|
|
3484
3612
|
placeholder: field.minPlaceholder,
|
|
3485
|
-
onChange: (e) => apply(e.target.value, draftMax)
|
|
3613
|
+
onChange: (e) => apply(e.target.value, draftMax),
|
|
3614
|
+
onValidityChange: setMinTextValid
|
|
3486
3615
|
}
|
|
3487
3616
|
),
|
|
3488
3617
|
/* @__PURE__ */ jsx45(
|
|
@@ -3493,9 +3622,11 @@ function RangePanel({
|
|
|
3493
3622
|
min: field.min,
|
|
3494
3623
|
max: field.max,
|
|
3495
3624
|
step: field.step,
|
|
3496
|
-
|
|
3625
|
+
integer: field.integer,
|
|
3626
|
+
error: comboError,
|
|
3497
3627
|
placeholder: field.maxPlaceholder,
|
|
3498
|
-
onChange: (e) => apply(draftMin, e.target.value)
|
|
3628
|
+
onChange: (e) => apply(draftMin, e.target.value),
|
|
3629
|
+
onValidityChange: setMaxTextValid
|
|
3499
3630
|
}
|
|
3500
3631
|
)
|
|
3501
3632
|
] })
|
|
@@ -3506,49 +3637,35 @@ function DatePanel({
|
|
|
3506
3637
|
field,
|
|
3507
3638
|
value,
|
|
3508
3639
|
anchorRef,
|
|
3509
|
-
|
|
3510
|
-
onRemove,
|
|
3640
|
+
onCommit,
|
|
3511
3641
|
onClose
|
|
3512
3642
|
}) {
|
|
3513
3643
|
const defaultDays = field.defaultDays ?? 30;
|
|
3514
|
-
const
|
|
3515
|
-
const
|
|
3644
|
+
const maxDays = field.maxDays ?? 3650;
|
|
3645
|
+
const [kind, setKind] = React24.useState(value?.kind ?? "withinDays");
|
|
3646
|
+
const [daysDraft, setDaysDraft] = React24.useState(
|
|
3647
|
+
value?.kind === "withinDays" ? value.days : defaultDays
|
|
3648
|
+
);
|
|
3649
|
+
const [between, setBetween] = React24.useState(
|
|
3650
|
+
value?.kind === "between" ? { from: value.from, to: value.to } : { from: "", to: "" }
|
|
3651
|
+
);
|
|
3516
3652
|
const applyWithinDays = (days) => {
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
if (days != null) {
|
|
3520
|
-
committedRef.current = true;
|
|
3521
|
-
onChange(next);
|
|
3522
|
-
}
|
|
3653
|
+
setDaysDraft(days);
|
|
3654
|
+
onCommit(days == null ? void 0 : { kind: "withinDays", days });
|
|
3523
3655
|
};
|
|
3524
3656
|
const applyBetween = (from, to) => {
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
if (from && to) {
|
|
3528
|
-
committedRef.current = true;
|
|
3529
|
-
onChange(next);
|
|
3530
|
-
} else if (committedRef.current) {
|
|
3531
|
-
committedRef.current = false;
|
|
3532
|
-
onRemove();
|
|
3533
|
-
}
|
|
3657
|
+
setBetween({ from, to });
|
|
3658
|
+
onCommit(from && to ? { kind: "between", from, to } : void 0);
|
|
3534
3659
|
};
|
|
3535
|
-
const switchKind = (
|
|
3536
|
-
if (
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
if (committedRef.current) {
|
|
3542
|
-
committedRef.current = false;
|
|
3543
|
-
onRemove();
|
|
3544
|
-
}
|
|
3545
|
-
}
|
|
3660
|
+
const switchKind = (next) => {
|
|
3661
|
+
if (next === kind) return;
|
|
3662
|
+
setKind(next);
|
|
3663
|
+
if (next === "between") setBetween({ from: "", to: "" });
|
|
3664
|
+
else setDaysDraft(defaultDays);
|
|
3665
|
+
onCommit(void 0);
|
|
3546
3666
|
};
|
|
3547
3667
|
const confirm = () => {
|
|
3548
|
-
if (
|
|
3549
|
-
committedRef.current = true;
|
|
3550
|
-
onChange(draft);
|
|
3551
|
-
}
|
|
3668
|
+
if (kind === "withinDays" && daysDraft != null) onCommit({ kind: "withinDays", days: daysDraft });
|
|
3552
3669
|
onClose();
|
|
3553
3670
|
};
|
|
3554
3671
|
return /* @__PURE__ */ jsx45(
|
|
@@ -3571,16 +3688,16 @@ function DatePanel({
|
|
|
3571
3688
|
{ value: "withinDays", label: "Within last N days" },
|
|
3572
3689
|
{ value: "between", label: "Between dates" }
|
|
3573
3690
|
],
|
|
3574
|
-
value:
|
|
3691
|
+
value: kind,
|
|
3575
3692
|
onChange: (k) => switchKind(k)
|
|
3576
3693
|
}
|
|
3577
3694
|
),
|
|
3578
|
-
|
|
3695
|
+
kind === "withinDays" ? /* @__PURE__ */ jsx45(NumberInput, { label: "Days", min: 1, max: maxDays, step: 1, integer: true, value: daysDraft ?? "", onChange: (e) => applyWithinDays(e.target.value) }) : /* @__PURE__ */ jsx45(
|
|
3579
3696
|
DatePicker,
|
|
3580
3697
|
{
|
|
3581
3698
|
range: true,
|
|
3582
3699
|
label: "Date range",
|
|
3583
|
-
value: { start:
|
|
3700
|
+
value: { start: between.from, end: between.to },
|
|
3584
3701
|
onChange: (e) => applyBetween(e.target.value.start || "", e.target.value.end || "")
|
|
3585
3702
|
}
|
|
3586
3703
|
)
|
|
@@ -3594,6 +3711,21 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3594
3711
|
const addAnchorRef = React24.useRef(null);
|
|
3595
3712
|
const textInputRef = React24.useRef(null);
|
|
3596
3713
|
const optionsRef = React24.useRef(null);
|
|
3714
|
+
if (process.env.NODE_ENV !== "production") {
|
|
3715
|
+
for (const f of fields) if (f.type === "date") assertValidDateField(f);
|
|
3716
|
+
}
|
|
3717
|
+
const commit = (field, candidate) => {
|
|
3718
|
+
if (isEmptyCandidate(field, candidate)) {
|
|
3719
|
+
removeField(field);
|
|
3720
|
+
return { ok: true };
|
|
3721
|
+
}
|
|
3722
|
+
const result = validateFilterValue(field, candidate);
|
|
3723
|
+
if (result.ok) onFilterChange(field.key, candidate);
|
|
3724
|
+
return result;
|
|
3725
|
+
};
|
|
3726
|
+
const removeField = (field) => {
|
|
3727
|
+
if (isFieldSet(field, filters[field.key])) onFilterRemove(field.key);
|
|
3728
|
+
};
|
|
3597
3729
|
const editingField = panel && panel.kind === "edit" ? fields.find((f) => f.key === panel.key) : void 0;
|
|
3598
3730
|
const startEditing = (key) => setPanel({ kind: "edit", key });
|
|
3599
3731
|
const closeEditing = () => setPanel(null);
|
|
@@ -3625,7 +3757,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3625
3757
|
value: value ?? "",
|
|
3626
3758
|
placeholder: field.placeholder || field.label,
|
|
3627
3759
|
"aria-label": field.label,
|
|
3628
|
-
onChange: (e) =>
|
|
3760
|
+
onChange: (e) => commit(field, e.target.value),
|
|
3629
3761
|
onKeyDown: (e) => {
|
|
3630
3762
|
if (e.key === "Enter") setPanel(null);
|
|
3631
3763
|
else if (e.key === "Escape") {
|
|
@@ -3644,7 +3776,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3644
3776
|
"aria-label": "Remove " + field.label,
|
|
3645
3777
|
onClick: (e) => {
|
|
3646
3778
|
e.stopPropagation();
|
|
3647
|
-
|
|
3779
|
+
removeField(field);
|
|
3648
3780
|
setPanel(null);
|
|
3649
3781
|
},
|
|
3650
3782
|
children: /* @__PURE__ */ jsx45("i", { className: "ph ph-x", "aria-hidden": "true" })
|
|
@@ -3657,7 +3789,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3657
3789
|
const chipRef = editing ? editAnchorRef : void 0;
|
|
3658
3790
|
const toggleOrClose = () => editing ? closeEditing() : startEditing(field.key);
|
|
3659
3791
|
const remove = () => {
|
|
3660
|
-
|
|
3792
|
+
removeField(field);
|
|
3661
3793
|
closeEditing();
|
|
3662
3794
|
};
|
|
3663
3795
|
const chipText = field.label + (set ? ": " + chipLabel(field, value) : "");
|
|
@@ -3687,7 +3819,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3687
3819
|
"aria-selected": on,
|
|
3688
3820
|
className: ["fd-opt", on ? "is-selected" : ""].filter(Boolean).join(" "),
|
|
3689
3821
|
onClick: () => {
|
|
3690
|
-
|
|
3822
|
+
commit(field, o.value);
|
|
3691
3823
|
setPanel(null);
|
|
3692
3824
|
},
|
|
3693
3825
|
children: /* @__PURE__ */ jsx45("span", { className: "fd-opt-label", children: o.label })
|
|
@@ -3708,8 +3840,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3708
3840
|
field,
|
|
3709
3841
|
value,
|
|
3710
3842
|
anchorRef: editAnchorRef,
|
|
3711
|
-
|
|
3712
|
-
onRemove: () => onFilterRemove(field.key),
|
|
3843
|
+
onCommit: (v) => commit(field, v),
|
|
3713
3844
|
onClose: closeEditing
|
|
3714
3845
|
}
|
|
3715
3846
|
) : null
|
|
@@ -3724,8 +3855,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3724
3855
|
field,
|
|
3725
3856
|
value,
|
|
3726
3857
|
anchorRef: editAnchorRef,
|
|
3727
|
-
|
|
3728
|
-
onRemove: () => onFilterRemove(field.key),
|
|
3858
|
+
onCommit: (v) => commit(field, v),
|
|
3729
3859
|
onClose: closeEditing
|
|
3730
3860
|
}
|
|
3731
3861
|
) : null
|
|
@@ -3740,8 +3870,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3740
3870
|
field,
|
|
3741
3871
|
value,
|
|
3742
3872
|
anchorRef: editAnchorRef,
|
|
3743
|
-
|
|
3744
|
-
onRemove: () => onFilterRemove(field.key),
|
|
3873
|
+
onCommit: (v) => commit(field, v),
|
|
3745
3874
|
onClose: closeEditing
|
|
3746
3875
|
}
|
|
3747
3876
|
) : null
|