@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.cjs
CHANGED
|
@@ -1756,7 +1756,7 @@ var BANNER_TONE_STYLE = {
|
|
|
1756
1756
|
};
|
|
1757
1757
|
function Banner({ tone = "info", title, detail, actions, role }) {
|
|
1758
1758
|
const t = BANNER_TONE_STYLE[tone];
|
|
1759
|
-
const liveRole = role || (tone === "danger"
|
|
1759
|
+
const liveRole = role || (tone === "danger" ? "alert" : "status");
|
|
1760
1760
|
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
|
|
1761
1761
|
"div",
|
|
1762
1762
|
{
|
|
@@ -3008,8 +3008,10 @@ function NumberInput({
|
|
|
3008
3008
|
max = Infinity,
|
|
3009
3009
|
step = 1,
|
|
3010
3010
|
bigStep,
|
|
3011
|
+
integer = false,
|
|
3011
3012
|
value,
|
|
3012
3013
|
onChange,
|
|
3014
|
+
onValidityChange,
|
|
3013
3015
|
format = true,
|
|
3014
3016
|
className = "",
|
|
3015
3017
|
style,
|
|
@@ -3025,17 +3027,34 @@ function NumberInput({
|
|
|
3025
3027
|
const n = Number(s);
|
|
3026
3028
|
return isNaN(n) ? null : n;
|
|
3027
3029
|
};
|
|
3030
|
+
const validationMessage = (n) => {
|
|
3031
|
+
if (integer && !Number.isInteger(n)) return "Enter a whole number";
|
|
3032
|
+
if (n < min) return "Must be \u2265 " + min;
|
|
3033
|
+
if (n > max) return "Must be \u2264 " + max;
|
|
3034
|
+
return null;
|
|
3035
|
+
};
|
|
3028
3036
|
const [text, setText] = React21.useState(value == null || value === "" ? "" : String(value));
|
|
3029
3037
|
const [editing, setEditing] = React21.useState(false);
|
|
3038
|
+
const [invalidMessage, setInvalidMessage] = React21.useState(null);
|
|
3030
3039
|
const timer = React21.useRef(null);
|
|
3031
3040
|
React21.useEffect(() => {
|
|
3032
|
-
if (!editing)
|
|
3041
|
+
if (!editing) {
|
|
3042
|
+
setText(value == null || value === "" ? "" : String(value));
|
|
3043
|
+
setInvalidMessage(null);
|
|
3044
|
+
}
|
|
3033
3045
|
}, [value, editing]);
|
|
3046
|
+
React21.useEffect(() => {
|
|
3047
|
+
onValidityChange && onValidityChange(invalidMessage == null);
|
|
3048
|
+
}, [invalidMessage]);
|
|
3034
3049
|
const clamp = (n) => Math.min(max, Math.max(min, n));
|
|
3035
3050
|
const fire = (n) => onChange && onChange({ target: { value: n } });
|
|
3036
3051
|
const bump = (dir, mult = 1) => {
|
|
3037
|
-
const cur = num(text) ?? num(value) ?? (min === -Infinity ? 0 : min);
|
|
3038
|
-
const
|
|
3052
|
+
const cur = (invalidMessage ? null : num(text)) ?? num(value) ?? (min === -Infinity ? 0 : min);
|
|
3053
|
+
const amount = (bigStep && mult > 1 ? bigStep : step) * (bigStep ? 1 : mult);
|
|
3054
|
+
let next = cur + dir * amount;
|
|
3055
|
+
if (integer) next = Math.round(next);
|
|
3056
|
+
next = clamp(next);
|
|
3057
|
+
setInvalidMessage(null);
|
|
3039
3058
|
setText(String(next));
|
|
3040
3059
|
fire(next);
|
|
3041
3060
|
};
|
|
@@ -3059,7 +3078,8 @@ function NumberInput({
|
|
|
3059
3078
|
const n = num(text);
|
|
3060
3079
|
return n == null ? "" : format ? n.toLocaleString() : String(n);
|
|
3061
3080
|
})();
|
|
3062
|
-
const
|
|
3081
|
+
const shownError = error || invalidMessage || void 0;
|
|
3082
|
+
const box = ["fd-input", "fd-input-num", shownError ? "fd-input-error" : "", disabled ? "fd-input-disabled" : ""].filter(Boolean).join(" ");
|
|
3063
3083
|
const fieldName = label || ariaLabel;
|
|
3064
3084
|
const stepperName = (verb) => fieldName ? verb + " " + fieldName : verb;
|
|
3065
3085
|
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: ["fd-field", className].filter(Boolean).join(" "), style, children: [
|
|
@@ -3075,30 +3095,29 @@ function NumberInput({
|
|
|
3075
3095
|
disabled,
|
|
3076
3096
|
placeholder,
|
|
3077
3097
|
value: shown,
|
|
3078
|
-
"aria-invalid":
|
|
3079
|
-
"aria-describedby":
|
|
3098
|
+
"aria-invalid": shownError ? "true" : void 0,
|
|
3099
|
+
"aria-describedby": shownError ? errorId : void 0,
|
|
3080
3100
|
onFocus: () => setEditing(true),
|
|
3081
3101
|
onBlur: () => {
|
|
3082
3102
|
setEditing(false);
|
|
3083
|
-
if (text.trim() === "")
|
|
3084
|
-
fire(void 0);
|
|
3085
|
-
return;
|
|
3086
|
-
}
|
|
3087
|
-
const n = num(text);
|
|
3088
|
-
if (n != null) {
|
|
3089
|
-
const c = clamp(n);
|
|
3090
|
-
setText(String(c));
|
|
3091
|
-
fire(c);
|
|
3092
|
-
}
|
|
3103
|
+
if (text.trim() === "") fire(void 0);
|
|
3093
3104
|
},
|
|
3094
3105
|
onChange: (e) => {
|
|
3095
|
-
|
|
3096
|
-
|
|
3106
|
+
const raw = e.target.value;
|
|
3107
|
+
setText(raw);
|
|
3108
|
+
if (raw.trim() === "") {
|
|
3109
|
+
setInvalidMessage(null);
|
|
3097
3110
|
fire(void 0);
|
|
3098
3111
|
return;
|
|
3099
3112
|
}
|
|
3100
|
-
const n = num(
|
|
3101
|
-
if (n
|
|
3113
|
+
const n = num(raw);
|
|
3114
|
+
if (n == null) {
|
|
3115
|
+
setInvalidMessage("Enter a number");
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
3118
|
+
const message = validationMessage(n);
|
|
3119
|
+
setInvalidMessage(message);
|
|
3120
|
+
if (!message) fire(n);
|
|
3102
3121
|
},
|
|
3103
3122
|
onKeyDown: (e) => {
|
|
3104
3123
|
if (e.key === "ArrowUp") {
|
|
@@ -3141,9 +3160,9 @@ function NumberInput({
|
|
|
3141
3160
|
)
|
|
3142
3161
|
] })
|
|
3143
3162
|
] }),
|
|
3144
|
-
|
|
3163
|
+
shownError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { id: errorId, className: "fd-field-error", children: [
|
|
3145
3164
|
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
|
|
3146
|
-
|
|
3165
|
+
shownError
|
|
3147
3166
|
] }) : help ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "fd-field-help", children: help }) : null
|
|
3148
3167
|
] });
|
|
3149
3168
|
}
|
|
@@ -3421,6 +3440,91 @@ function isFieldSet(field, value) {
|
|
|
3421
3440
|
}
|
|
3422
3441
|
}
|
|
3423
3442
|
}
|
|
3443
|
+
function validateFilterValue(field, value) {
|
|
3444
|
+
switch (field.type) {
|
|
3445
|
+
case "text":
|
|
3446
|
+
case "select":
|
|
3447
|
+
return { ok: true };
|
|
3448
|
+
case "multiSelect": {
|
|
3449
|
+
const v = value;
|
|
3450
|
+
if (!v || !Array.isArray(v.values) || v.values.length === 0) {
|
|
3451
|
+
return { ok: false, reason: "Select at least one value" };
|
|
3452
|
+
}
|
|
3453
|
+
if (!operatorsFor(field).includes(v.operator)) {
|
|
3454
|
+
return { ok: false, reason: "That operator isn't allowed for " + field.label };
|
|
3455
|
+
}
|
|
3456
|
+
return { ok: true };
|
|
3457
|
+
}
|
|
3458
|
+
case "range": {
|
|
3459
|
+
const v = value;
|
|
3460
|
+
if (!v || v.min == null && v.max == null) {
|
|
3461
|
+
return { ok: false, reason: "Enter at least one bound" };
|
|
3462
|
+
}
|
|
3463
|
+
const lo = field.min ?? -Infinity;
|
|
3464
|
+
const hi = field.max ?? Infinity;
|
|
3465
|
+
for (const n of [v.min, v.max]) {
|
|
3466
|
+
if (n == null) continue;
|
|
3467
|
+
if (field.integer && !Number.isInteger(n)) return { ok: false, reason: "Enter a whole number" };
|
|
3468
|
+
if (n < lo) return { ok: false, reason: "Must be \u2265 " + lo };
|
|
3469
|
+
if (n > hi) return { ok: false, reason: "Must be \u2264 " + hi };
|
|
3470
|
+
}
|
|
3471
|
+
if (v.min != null && v.max != null && v.min > v.max) {
|
|
3472
|
+
return { ok: false, reason: "Must be \u2265 " + v.min };
|
|
3473
|
+
}
|
|
3474
|
+
return { ok: true };
|
|
3475
|
+
}
|
|
3476
|
+
case "date": {
|
|
3477
|
+
const v = value;
|
|
3478
|
+
if (!v) return { ok: false, reason: "Pick a date value" };
|
|
3479
|
+
if (v.kind === "withinDays") {
|
|
3480
|
+
const maxDays = field.maxDays ?? 3650;
|
|
3481
|
+
if (v.days == null) return { ok: false, reason: "Enter a number of days" };
|
|
3482
|
+
if (!Number.isInteger(v.days)) return { ok: false, reason: "Enter a whole number" };
|
|
3483
|
+
if (v.days < 1) return { ok: false, reason: "Must be \u2265 1" };
|
|
3484
|
+
if (v.days > maxDays) return { ok: false, reason: "Must be \u2264 " + maxDays };
|
|
3485
|
+
return { ok: true };
|
|
3486
|
+
}
|
|
3487
|
+
if (!v.from || !v.to) return { ok: false, reason: "Pick both dates" };
|
|
3488
|
+
if (v.from > v.to) return { ok: false, reason: "The start date must be on or before the end date" };
|
|
3489
|
+
return { ok: true };
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
function isEmptyCandidate(field, candidate) {
|
|
3494
|
+
switch (field.type) {
|
|
3495
|
+
case "text":
|
|
3496
|
+
case "select":
|
|
3497
|
+
return false;
|
|
3498
|
+
case "multiSelect": {
|
|
3499
|
+
const v = candidate;
|
|
3500
|
+
return !v || !Array.isArray(v.values) || v.values.length === 0;
|
|
3501
|
+
}
|
|
3502
|
+
case "range": {
|
|
3503
|
+
const v = candidate;
|
|
3504
|
+
return !v || v.min == null && v.max == null;
|
|
3505
|
+
}
|
|
3506
|
+
case "date": {
|
|
3507
|
+
const v = candidate;
|
|
3508
|
+
if (!v) return true;
|
|
3509
|
+
return v.kind === "withinDays" ? v.days == null : !(v.from && v.to);
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
function assertValidDateField(field) {
|
|
3514
|
+
if (process.env.NODE_ENV === "production") return;
|
|
3515
|
+
const defaultDays = field.defaultDays ?? 30;
|
|
3516
|
+
const maxDays = field.maxDays ?? 3650;
|
|
3517
|
+
if (!Number.isInteger(maxDays) || maxDays < 1 || maxDays > 3650) {
|
|
3518
|
+
throw new Error(
|
|
3519
|
+
'FilterBar: date field "' + field.key + '" has an invalid maxDays (' + field.maxDays + ") \u2014 must be a whole number from 1 to 3650."
|
|
3520
|
+
);
|
|
3521
|
+
}
|
|
3522
|
+
if (!Number.isInteger(defaultDays) || defaultDays < 1 || defaultDays > maxDays) {
|
|
3523
|
+
throw new Error(
|
|
3524
|
+
'FilterBar: date field "' + field.key + '" has an invalid defaultDays (' + field.defaultDays + ") \u2014 must be a whole number from 1 to maxDays (" + maxDays + ")."
|
|
3525
|
+
);
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3424
3528
|
function chipLabel(field, value) {
|
|
3425
3529
|
switch (field.type) {
|
|
3426
3530
|
case "text":
|
|
@@ -3473,18 +3577,30 @@ function MultiSelectPanel({
|
|
|
3473
3577
|
field,
|
|
3474
3578
|
value,
|
|
3475
3579
|
anchorRef,
|
|
3476
|
-
|
|
3477
|
-
onRemove,
|
|
3580
|
+
onCommit,
|
|
3478
3581
|
onClose
|
|
3479
3582
|
}) {
|
|
3480
3583
|
const allowedOps = operatorsFor(field);
|
|
3481
|
-
const
|
|
3584
|
+
const [draftOperator, setDraftOperator] = React24.useState(value?.operator ?? allowedOps[0]);
|
|
3585
|
+
const operator = allowedOps.length === 1 ? allowedOps[0] : value?.operator ?? draftOperator;
|
|
3482
3586
|
const selected = value?.values ?? [];
|
|
3483
3587
|
const selectedRef = React24.useRef(selected);
|
|
3484
3588
|
selectedRef.current = selected;
|
|
3589
|
+
const operatorRef = React24.useRef(operator);
|
|
3590
|
+
operatorRef.current = operator;
|
|
3591
|
+
const mountedRef = React24.useRef(true);
|
|
3592
|
+
React24.useEffect(() => {
|
|
3593
|
+
mountedRef.current = true;
|
|
3594
|
+
return () => {
|
|
3595
|
+
mountedRef.current = false;
|
|
3596
|
+
};
|
|
3597
|
+
}, []);
|
|
3485
3598
|
const commit = (nextValues, nextOperator = operator) => {
|
|
3486
|
-
|
|
3487
|
-
|
|
3599
|
+
onCommit({ operator: nextOperator, values: nextValues });
|
|
3600
|
+
};
|
|
3601
|
+
const setOperator = (op) => {
|
|
3602
|
+
setDraftOperator(op);
|
|
3603
|
+
if (selected.length > 0) commit(selected, op);
|
|
3488
3604
|
};
|
|
3489
3605
|
const [mode2, setMode] = React24.useState("search");
|
|
3490
3606
|
const [query, setQuery] = React24.useState("");
|
|
@@ -3522,18 +3638,20 @@ function MultiSelectPanel({
|
|
|
3522
3638
|
const options = field.loadOptions ? asyncState.options ?? [] : (field.options ?? []).filter((o) => o.label.toLowerCase().includes(query.toLowerCase()));
|
|
3523
3639
|
const showLoading = !!field.loadOptions && asyncState.loading;
|
|
3524
3640
|
const showError = !!field.loadOptions && asyncState.error;
|
|
3525
|
-
const
|
|
3641
|
+
const hasListbox = !showLoading && !showError && options.length > 0;
|
|
3526
3642
|
const isSelected = (o) => selected.some((s) => s.value === o.value);
|
|
3527
3643
|
const toggle = (o) => {
|
|
3528
3644
|
const next = isSelected(o) ? selected.filter((s) => s.value !== o.value) : [...selected, o];
|
|
3529
3645
|
commit(next);
|
|
3530
3646
|
};
|
|
3531
3647
|
const nav = useListboxNav({ count: options.length, onSelect: (i) => toggle(options[i]) });
|
|
3532
|
-
useListboxScrollIntoView(nav.active, listRef,
|
|
3648
|
+
useListboxScrollIntoView(nav.active, listRef, hasListbox);
|
|
3533
3649
|
React24.useEffect(() => {
|
|
3534
3650
|
nav.setActive(0);
|
|
3535
3651
|
}, [query, options.length]);
|
|
3536
|
-
const activeId =
|
|
3652
|
+
const activeId = hasListbox && options[nav.active] ? listId + "-opt-" + nav.active : void 0;
|
|
3653
|
+
const liveRole = showError ? "alert" : "status";
|
|
3654
|
+
const liveMessage = showLoading ? "Loading\u2026" : showError ? "Couldn't load options." : options.length === 0 ? "No matches" : "";
|
|
3537
3655
|
const [pasteText, setPasteText] = React24.useState("");
|
|
3538
3656
|
const [pasteLoading, setPasteLoading] = React24.useState(false);
|
|
3539
3657
|
const [unmatchedTokens, setUnmatchedTokens] = React24.useState([]);
|
|
@@ -3549,19 +3667,21 @@ function MultiSelectPanel({
|
|
|
3549
3667
|
setPasteLoading(true);
|
|
3550
3668
|
setPasteError(null);
|
|
3551
3669
|
field.resolve(tokens).then((result) => {
|
|
3670
|
+
if (!mountedRef.current) return;
|
|
3552
3671
|
setPasteLoading(false);
|
|
3553
3672
|
setUnmatchedTokens(result.unmatched);
|
|
3554
3673
|
const merged = new Map(selectedRef.current.map((s) => [s.value, s]));
|
|
3555
3674
|
for (const m of result.matched) merged.set(m.value, { value: m.value, label: m.label });
|
|
3556
|
-
commit(Array.from(merged.values()));
|
|
3675
|
+
commit(Array.from(merged.values()), operatorRef.current);
|
|
3557
3676
|
setPasteText("");
|
|
3558
3677
|
}).catch((err) => {
|
|
3678
|
+
if (!mountedRef.current) return;
|
|
3559
3679
|
setPasteLoading(false);
|
|
3560
3680
|
setPasteError(err instanceof Error ? err.message : "Could not resolve the pasted list.");
|
|
3561
3681
|
});
|
|
3562
3682
|
};
|
|
3563
3683
|
const header = /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack fd-gap-sm", children: [
|
|
3564
|
-
allowedOps.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(OperatorToggle, { operator, onChange:
|
|
3684
|
+
allowedOps.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(OperatorToggle, { operator, onChange: setOperator, label: field.label }) : null,
|
|
3565
3685
|
field.resolve ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3566
3686
|
SegmentedControl,
|
|
3567
3687
|
{
|
|
@@ -3578,8 +3698,8 @@ function MultiSelectPanel({
|
|
|
3578
3698
|
{
|
|
3579
3699
|
ref: inputRef,
|
|
3580
3700
|
role: "combobox",
|
|
3581
|
-
"aria-expanded":
|
|
3582
|
-
"aria-controls":
|
|
3701
|
+
"aria-expanded": hasListbox,
|
|
3702
|
+
"aria-controls": hasListbox ? listId : void 0,
|
|
3583
3703
|
"aria-autocomplete": "list",
|
|
3584
3704
|
"aria-activedescendant": activeId,
|
|
3585
3705
|
"aria-label": field.label,
|
|
@@ -3635,33 +3755,41 @@ function MultiSelectPanel({
|
|
|
3635
3755
|
] }),
|
|
3636
3756
|
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-row fd-wrap fd-gap-xs", children: selected.map((v) => /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Tag, { onRemove: () => commit(selected.filter((s) => s.value !== v.value)), children: v.label }, v.value)) })
|
|
3637
3757
|
] }) : null
|
|
3638
|
-
] }) :
|
|
3639
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.
|
|
3640
|
-
|
|
3641
|
-
] }) : showError ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack fd-gap-sm", children: [
|
|
3642
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-body-sm", role: "alert", children: "Couldn't load options." }),
|
|
3643
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: () => setRetryTick((n) => n + 1), children: "Retry" })
|
|
3644
|
-
] }) : options.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-body-sm fd-muted", role: "status", children: "No matches" }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { id: listId, role: "listbox", "aria-label": field.label, "aria-multiselectable": "true", ref: listRef, className: "fd-pop-list", children: options.map((o, i) => {
|
|
3645
|
-
const on = isSelected(o);
|
|
3646
|
-
return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
|
|
3647
|
-
"div",
|
|
3758
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack fd-gap-sm", children: [
|
|
3759
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
|
|
3760
|
+
"span",
|
|
3648
3761
|
{
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
role: "option",
|
|
3652
|
-
tabIndex: -1,
|
|
3653
|
-
"aria-selected": on,
|
|
3654
|
-
className: ["fd-opt", i === nav.active ? "is-active" : "", on ? "is-selected" : ""].filter(Boolean).join(" "),
|
|
3655
|
-
onMouseEnter: () => nav.setActive(i),
|
|
3656
|
-
onClick: () => toggle(o),
|
|
3762
|
+
className: "fd-row fd-gap-xs" + (liveMessage && !showError ? " fd-muted" : ""),
|
|
3763
|
+
role: liveRole,
|
|
3657
3764
|
children: [
|
|
3658
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("
|
|
3659
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-
|
|
3765
|
+
showLoading ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-spinner", "aria-hidden": "true" }) : null,
|
|
3766
|
+
liveMessage ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-body-sm", children: liveMessage }) : null
|
|
3660
3767
|
]
|
|
3661
|
-
}
|
|
3662
|
-
|
|
3663
|
-
)
|
|
3664
|
-
|
|
3768
|
+
}
|
|
3769
|
+
),
|
|
3770
|
+
showError ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: () => setRetryTick((n) => n + 1), children: "Retry" }) : null,
|
|
3771
|
+
hasListbox ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { id: listId, role: "listbox", "aria-label": field.label, "aria-multiselectable": "true", ref: listRef, className: "fd-pop-list", children: options.map((o, i) => {
|
|
3772
|
+
const on = isSelected(o);
|
|
3773
|
+
return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
|
|
3774
|
+
"div",
|
|
3775
|
+
{
|
|
3776
|
+
id: listId + "-opt-" + i,
|
|
3777
|
+
"data-i": i,
|
|
3778
|
+
role: "option",
|
|
3779
|
+
tabIndex: -1,
|
|
3780
|
+
"aria-selected": on,
|
|
3781
|
+
className: ["fd-opt", i === nav.active ? "is-active" : "", on ? "is-selected" : ""].filter(Boolean).join(" "),
|
|
3782
|
+
onMouseEnter: () => nav.setActive(i),
|
|
3783
|
+
onClick: () => toggle(o),
|
|
3784
|
+
children: [
|
|
3785
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("i", { className: "fd-filterbar-opt-check ph " + (on ? "ph-check-square is-checked" : "ph-square"), "aria-hidden": "true" }),
|
|
3786
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-opt-label", children: o.label })
|
|
3787
|
+
]
|
|
3788
|
+
},
|
|
3789
|
+
o.value
|
|
3790
|
+
);
|
|
3791
|
+
}) }) : null
|
|
3792
|
+
] });
|
|
3665
3793
|
const footer = mode2 === "paste" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: doResolve, disabled: pasteLoading || !pasteText.trim(), children: pasteLoading ? "Resolving\u2026" : "Resolve" }) : void 0;
|
|
3666
3794
|
return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3667
3795
|
Popover,
|
|
@@ -3683,22 +3811,21 @@ function RangePanel({
|
|
|
3683
3811
|
field,
|
|
3684
3812
|
value,
|
|
3685
3813
|
anchorRef,
|
|
3686
|
-
|
|
3687
|
-
onRemove,
|
|
3814
|
+
onCommit,
|
|
3688
3815
|
onClose
|
|
3689
3816
|
}) {
|
|
3690
3817
|
const [draftMin, setDraftMin] = React24.useState(value?.min);
|
|
3691
3818
|
const [draftMax, setDraftMax] = React24.useState(value?.max);
|
|
3692
|
-
const
|
|
3819
|
+
const [minTextValid, setMinTextValid] = React24.useState(true);
|
|
3820
|
+
const [maxTextValid, setMaxTextValid] = React24.useState(true);
|
|
3821
|
+
const isEmpty = draftMin == null && draftMax == null;
|
|
3822
|
+
const validation = isEmpty ? { ok: true } : validateFilterValue(field, { min: draftMin, max: draftMax });
|
|
3823
|
+
const comboError = !isEmpty && !validation.ok ? validation.reason : void 0;
|
|
3824
|
+
const doneDisabled = !minTextValid || !maxTextValid || !!comboError;
|
|
3693
3825
|
const apply = (nextMin, nextMax) => {
|
|
3694
3826
|
setDraftMin(nextMin);
|
|
3695
3827
|
setDraftMax(nextMax);
|
|
3696
|
-
|
|
3697
|
-
onRemove();
|
|
3698
|
-
return;
|
|
3699
|
-
}
|
|
3700
|
-
if (nextMin != null && nextMax != null && nextMin > nextMax) return;
|
|
3701
|
-
onChange({ min: nextMin, max: nextMax });
|
|
3828
|
+
onCommit(nextMin == null && nextMax == null ? void 0 : { min: nextMin, max: nextMax });
|
|
3702
3829
|
};
|
|
3703
3830
|
return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3704
3831
|
Popover,
|
|
@@ -3710,7 +3837,7 @@ function RangePanel({
|
|
|
3710
3837
|
offset: 6,
|
|
3711
3838
|
width: PANEL_WIDTH.range,
|
|
3712
3839
|
label: field.label,
|
|
3713
|
-
footer: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: onClose, children: "Done" }),
|
|
3840
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: onClose, disabled: doneDisabled, children: "Done" }),
|
|
3714
3841
|
children: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack fd-gap-sm", children: [
|
|
3715
3842
|
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3716
3843
|
NumberInput,
|
|
@@ -3720,8 +3847,10 @@ function RangePanel({
|
|
|
3720
3847
|
min: field.min,
|
|
3721
3848
|
max: field.max,
|
|
3722
3849
|
step: field.step,
|
|
3850
|
+
integer: field.integer,
|
|
3723
3851
|
placeholder: field.minPlaceholder,
|
|
3724
|
-
onChange: (e) => apply(e.target.value, draftMax)
|
|
3852
|
+
onChange: (e) => apply(e.target.value, draftMax),
|
|
3853
|
+
onValidityChange: setMinTextValid
|
|
3725
3854
|
}
|
|
3726
3855
|
),
|
|
3727
3856
|
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
@@ -3732,9 +3861,11 @@ function RangePanel({
|
|
|
3732
3861
|
min: field.min,
|
|
3733
3862
|
max: field.max,
|
|
3734
3863
|
step: field.step,
|
|
3735
|
-
|
|
3864
|
+
integer: field.integer,
|
|
3865
|
+
error: comboError,
|
|
3736
3866
|
placeholder: field.maxPlaceholder,
|
|
3737
|
-
onChange: (e) => apply(draftMin, e.target.value)
|
|
3867
|
+
onChange: (e) => apply(draftMin, e.target.value),
|
|
3868
|
+
onValidityChange: setMaxTextValid
|
|
3738
3869
|
}
|
|
3739
3870
|
)
|
|
3740
3871
|
] })
|
|
@@ -3745,49 +3876,35 @@ function DatePanel({
|
|
|
3745
3876
|
field,
|
|
3746
3877
|
value,
|
|
3747
3878
|
anchorRef,
|
|
3748
|
-
|
|
3749
|
-
onRemove,
|
|
3879
|
+
onCommit,
|
|
3750
3880
|
onClose
|
|
3751
3881
|
}) {
|
|
3752
3882
|
const defaultDays = field.defaultDays ?? 30;
|
|
3753
|
-
const
|
|
3754
|
-
const
|
|
3883
|
+
const maxDays = field.maxDays ?? 3650;
|
|
3884
|
+
const [kind, setKind] = React24.useState(value?.kind ?? "withinDays");
|
|
3885
|
+
const [daysDraft, setDaysDraft] = React24.useState(
|
|
3886
|
+
value?.kind === "withinDays" ? value.days : defaultDays
|
|
3887
|
+
);
|
|
3888
|
+
const [between, setBetween] = React24.useState(
|
|
3889
|
+
value?.kind === "between" ? { from: value.from, to: value.to } : { from: "", to: "" }
|
|
3890
|
+
);
|
|
3755
3891
|
const applyWithinDays = (days) => {
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
if (days != null) {
|
|
3759
|
-
committedRef.current = true;
|
|
3760
|
-
onChange(next);
|
|
3761
|
-
}
|
|
3892
|
+
setDaysDraft(days);
|
|
3893
|
+
onCommit(days == null ? void 0 : { kind: "withinDays", days });
|
|
3762
3894
|
};
|
|
3763
3895
|
const applyBetween = (from, to) => {
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
if (from && to) {
|
|
3767
|
-
committedRef.current = true;
|
|
3768
|
-
onChange(next);
|
|
3769
|
-
} else if (committedRef.current) {
|
|
3770
|
-
committedRef.current = false;
|
|
3771
|
-
onRemove();
|
|
3772
|
-
}
|
|
3896
|
+
setBetween({ from, to });
|
|
3897
|
+
onCommit(from && to ? { kind: "between", from, to } : void 0);
|
|
3773
3898
|
};
|
|
3774
|
-
const switchKind = (
|
|
3775
|
-
if (
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
if (committedRef.current) {
|
|
3781
|
-
committedRef.current = false;
|
|
3782
|
-
onRemove();
|
|
3783
|
-
}
|
|
3784
|
-
}
|
|
3899
|
+
const switchKind = (next) => {
|
|
3900
|
+
if (next === kind) return;
|
|
3901
|
+
setKind(next);
|
|
3902
|
+
if (next === "between") setBetween({ from: "", to: "" });
|
|
3903
|
+
else setDaysDraft(defaultDays);
|
|
3904
|
+
onCommit(void 0);
|
|
3785
3905
|
};
|
|
3786
3906
|
const confirm = () => {
|
|
3787
|
-
if (
|
|
3788
|
-
committedRef.current = true;
|
|
3789
|
-
onChange(draft);
|
|
3790
|
-
}
|
|
3907
|
+
if (kind === "withinDays" && daysDraft != null) onCommit({ kind: "withinDays", days: daysDraft });
|
|
3791
3908
|
onClose();
|
|
3792
3909
|
};
|
|
3793
3910
|
return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
@@ -3810,16 +3927,16 @@ function DatePanel({
|
|
|
3810
3927
|
{ value: "withinDays", label: "Within last N days" },
|
|
3811
3928
|
{ value: "between", label: "Between dates" }
|
|
3812
3929
|
],
|
|
3813
|
-
value:
|
|
3930
|
+
value: kind,
|
|
3814
3931
|
onChange: (k) => switchKind(k)
|
|
3815
3932
|
}
|
|
3816
3933
|
),
|
|
3817
|
-
|
|
3934
|
+
kind === "withinDays" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(NumberInput, { label: "Days", min: 1, max: maxDays, step: 1, integer: true, value: daysDraft ?? "", onChange: (e) => applyWithinDays(e.target.value) }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3818
3935
|
DatePicker,
|
|
3819
3936
|
{
|
|
3820
3937
|
range: true,
|
|
3821
3938
|
label: "Date range",
|
|
3822
|
-
value: { start:
|
|
3939
|
+
value: { start: between.from, end: between.to },
|
|
3823
3940
|
onChange: (e) => applyBetween(e.target.value.start || "", e.target.value.end || "")
|
|
3824
3941
|
}
|
|
3825
3942
|
)
|
|
@@ -3833,6 +3950,21 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3833
3950
|
const addAnchorRef = React24.useRef(null);
|
|
3834
3951
|
const textInputRef = React24.useRef(null);
|
|
3835
3952
|
const optionsRef = React24.useRef(null);
|
|
3953
|
+
if (process.env.NODE_ENV !== "production") {
|
|
3954
|
+
for (const f of fields) if (f.type === "date") assertValidDateField(f);
|
|
3955
|
+
}
|
|
3956
|
+
const commit = (field, candidate) => {
|
|
3957
|
+
if (isEmptyCandidate(field, candidate)) {
|
|
3958
|
+
removeField(field);
|
|
3959
|
+
return { ok: true };
|
|
3960
|
+
}
|
|
3961
|
+
const result = validateFilterValue(field, candidate);
|
|
3962
|
+
if (result.ok) onFilterChange(field.key, candidate);
|
|
3963
|
+
return result;
|
|
3964
|
+
};
|
|
3965
|
+
const removeField = (field) => {
|
|
3966
|
+
if (isFieldSet(field, filters[field.key])) onFilterRemove(field.key);
|
|
3967
|
+
};
|
|
3836
3968
|
const editingField = panel && panel.kind === "edit" ? fields.find((f) => f.key === panel.key) : void 0;
|
|
3837
3969
|
const startEditing = (key) => setPanel({ kind: "edit", key });
|
|
3838
3970
|
const closeEditing = () => setPanel(null);
|
|
@@ -3864,7 +3996,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3864
3996
|
value: value ?? "",
|
|
3865
3997
|
placeholder: field.placeholder || field.label,
|
|
3866
3998
|
"aria-label": field.label,
|
|
3867
|
-
onChange: (e) =>
|
|
3999
|
+
onChange: (e) => commit(field, e.target.value),
|
|
3868
4000
|
onKeyDown: (e) => {
|
|
3869
4001
|
if (e.key === "Enter") setPanel(null);
|
|
3870
4002
|
else if (e.key === "Escape") {
|
|
@@ -3883,7 +4015,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3883
4015
|
"aria-label": "Remove " + field.label,
|
|
3884
4016
|
onClick: (e) => {
|
|
3885
4017
|
e.stopPropagation();
|
|
3886
|
-
|
|
4018
|
+
removeField(field);
|
|
3887
4019
|
setPanel(null);
|
|
3888
4020
|
},
|
|
3889
4021
|
children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("i", { className: "ph ph-x", "aria-hidden": "true" })
|
|
@@ -3896,7 +4028,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3896
4028
|
const chipRef = editing ? editAnchorRef : void 0;
|
|
3897
4029
|
const toggleOrClose = () => editing ? closeEditing() : startEditing(field.key);
|
|
3898
4030
|
const remove = () => {
|
|
3899
|
-
|
|
4031
|
+
removeField(field);
|
|
3900
4032
|
closeEditing();
|
|
3901
4033
|
};
|
|
3902
4034
|
const chipText = field.label + (set ? ": " + chipLabel(field, value) : "");
|
|
@@ -3926,7 +4058,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3926
4058
|
"aria-selected": on,
|
|
3927
4059
|
className: ["fd-opt", on ? "is-selected" : ""].filter(Boolean).join(" "),
|
|
3928
4060
|
onClick: () => {
|
|
3929
|
-
|
|
4061
|
+
commit(field, o.value);
|
|
3930
4062
|
setPanel(null);
|
|
3931
4063
|
},
|
|
3932
4064
|
children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-opt-label", children: o.label })
|
|
@@ -3947,8 +4079,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3947
4079
|
field,
|
|
3948
4080
|
value,
|
|
3949
4081
|
anchorRef: editAnchorRef,
|
|
3950
|
-
|
|
3951
|
-
onRemove: () => onFilterRemove(field.key),
|
|
4082
|
+
onCommit: (v) => commit(field, v),
|
|
3952
4083
|
onClose: closeEditing
|
|
3953
4084
|
}
|
|
3954
4085
|
) : null
|
|
@@ -3963,8 +4094,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3963
4094
|
field,
|
|
3964
4095
|
value,
|
|
3965
4096
|
anchorRef: editAnchorRef,
|
|
3966
|
-
|
|
3967
|
-
onRemove: () => onFilterRemove(field.key),
|
|
4097
|
+
onCommit: (v) => commit(field, v),
|
|
3968
4098
|
onClose: closeEditing
|
|
3969
4099
|
}
|
|
3970
4100
|
) : null
|
|
@@ -3979,8 +4109,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
|
|
|
3979
4109
|
field,
|
|
3980
4110
|
value,
|
|
3981
4111
|
anchorRef: editAnchorRef,
|
|
3982
|
-
|
|
3983
|
-
onRemove: () => onFilterRemove(field.key),
|
|
4112
|
+
onCommit: (v) => commit(field, v),
|
|
3984
4113
|
onClose: closeEditing
|
|
3985
4114
|
}
|
|
3986
4115
|
) : null
|