@2urgseui/core 0.1.0 → 0.1.2
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 +276 -20
- package/dist/index.d.cts +74 -10
- package/dist/index.d.ts +74 -10
- package/dist/index.js +270 -20
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -226,6 +226,12 @@ __export(index_exports, {
|
|
|
226
226
|
colors: () => colors,
|
|
227
227
|
containerVariants: () => containerVariants,
|
|
228
228
|
dismissToast: () => dismissToast,
|
|
229
|
+
formFieldCanonicalizeNumericInput: () => formFieldCanonicalizeNumericInput,
|
|
230
|
+
formFieldFormatNumericDisplay: () => formFieldFormatNumericDisplay,
|
|
231
|
+
formFieldMaskExtractCanonical: () => formFieldMaskExtractCanonical,
|
|
232
|
+
formFieldMaskFormatDisplay: () => formFieldMaskFormatDisplay,
|
|
233
|
+
formFieldMaskSlotCount: () => formFieldMaskSlotCount,
|
|
234
|
+
formValueToAsyncSelectOption: () => formValueToAsyncSelectOption,
|
|
229
235
|
getIcon: () => getIcon,
|
|
230
236
|
headingVariants: () => headingVariants,
|
|
231
237
|
inputGroupSelectTriggerTextAlignClass: () => inputGroupSelectTriggerTextAlignClass,
|
|
@@ -638,6 +644,15 @@ function resolveLabel(item, labelKey) {
|
|
|
638
644
|
if (typeof labelKey === "function") return labelKey(item);
|
|
639
645
|
return String(item[labelKey] ?? "");
|
|
640
646
|
}
|
|
647
|
+
function reactNodeToLabelString(node) {
|
|
648
|
+
if (node == null || typeof node === "boolean") return "";
|
|
649
|
+
if (typeof node === "string" || typeof node === "number") return String(node);
|
|
650
|
+
if (Array.isArray(node)) return node.map(reactNodeToLabelString).join("");
|
|
651
|
+
return "";
|
|
652
|
+
}
|
|
653
|
+
function resolveStringLabel(item, labelKey) {
|
|
654
|
+
return reactNodeToLabelString(resolveLabel(item, labelKey));
|
|
655
|
+
}
|
|
641
656
|
function AsyncSelectInner(props) {
|
|
642
657
|
const {
|
|
643
658
|
url,
|
|
@@ -653,6 +668,7 @@ function AsyncSelectInner(props) {
|
|
|
653
668
|
disabled = false,
|
|
654
669
|
error: error2 = false,
|
|
655
670
|
name,
|
|
671
|
+
id,
|
|
656
672
|
className,
|
|
657
673
|
fetcher,
|
|
658
674
|
dataPath = "data",
|
|
@@ -674,8 +690,8 @@ function AsyncSelectInner(props) {
|
|
|
674
690
|
const searchInputRef = React6.useRef(null);
|
|
675
691
|
const abortRef = React6.useRef(null);
|
|
676
692
|
React6.useEffect(() => {
|
|
677
|
-
const
|
|
678
|
-
return () => clearTimeout(
|
|
693
|
+
const id2 = setTimeout(() => setDebouncedSearch(search), debounceMs);
|
|
694
|
+
return () => clearTimeout(id2);
|
|
679
695
|
}, [search, debounceMs]);
|
|
680
696
|
React6.useEffect(() => {
|
|
681
697
|
setPage(1);
|
|
@@ -749,38 +765,43 @@ function AsyncSelectInner(props) {
|
|
|
749
765
|
}
|
|
750
766
|
}, [open]);
|
|
751
767
|
React6.useEffect(() => {
|
|
752
|
-
|
|
768
|
+
const v = value?.value;
|
|
769
|
+
if (v == null || v === "") {
|
|
753
770
|
setSelectedItem(null);
|
|
754
771
|
return;
|
|
755
772
|
}
|
|
756
773
|
const found = items.find(
|
|
757
|
-
(it) => String(it[valueKey]) ===
|
|
774
|
+
(it) => String(it[valueKey]) === v
|
|
758
775
|
);
|
|
759
|
-
|
|
776
|
+
setSelectedItem(found ?? null);
|
|
760
777
|
}, [value, items, valueKey]);
|
|
761
778
|
const handleSelect = (item) => {
|
|
762
779
|
const itemValue = String(item[valueKey]);
|
|
763
|
-
if (itemValue === value && clearable) {
|
|
764
|
-
onValueChange?.(
|
|
780
|
+
if (itemValue === value?.value && clearable) {
|
|
781
|
+
onValueChange?.(null);
|
|
765
782
|
setSelectedItem(null);
|
|
766
783
|
} else {
|
|
767
|
-
onValueChange?.(
|
|
784
|
+
onValueChange?.({
|
|
785
|
+
value: itemValue,
|
|
786
|
+
label: resolveStringLabel(item, labelKey)
|
|
787
|
+
});
|
|
768
788
|
setSelectedItem(item);
|
|
769
789
|
}
|
|
770
790
|
setOpen(false);
|
|
771
791
|
};
|
|
772
792
|
const handleClear = (e) => {
|
|
773
793
|
e.stopPropagation();
|
|
774
|
-
onValueChange?.(
|
|
794
|
+
onValueChange?.(null);
|
|
775
795
|
setSelectedItem(null);
|
|
776
796
|
};
|
|
777
|
-
const triggerLabel = selectedItem ? resolveLabel(selectedItem, labelKey) : null;
|
|
797
|
+
const triggerLabel = selectedItem ? resolveLabel(selectedItem, labelKey) : value?.label ? value.label : null;
|
|
778
798
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Popover, { open, onOpenChange: setOpen, children: [
|
|
779
|
-
name && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "hidden", name, value: value ?? "" }),
|
|
799
|
+
name && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "hidden", name, value: value?.value ?? "" }),
|
|
780
800
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
781
801
|
"button",
|
|
782
802
|
{
|
|
783
803
|
ref: innerRef,
|
|
804
|
+
id,
|
|
784
805
|
type: "button",
|
|
785
806
|
role: "combobox",
|
|
786
807
|
"aria-expanded": open,
|
|
@@ -802,7 +823,7 @@ function AsyncSelectInner(props) {
|
|
|
802
823
|
}
|
|
803
824
|
),
|
|
804
825
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "flex shrink-0 items-center gap-1", children: [
|
|
805
|
-
clearable && value && !disabled && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
826
|
+
clearable && value?.value && !disabled && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
806
827
|
"span",
|
|
807
828
|
{
|
|
808
829
|
role: "button",
|
|
@@ -849,7 +870,7 @@ function AsyncSelectInner(props) {
|
|
|
849
870
|
const itemValue = String(
|
|
850
871
|
item[valueKey]
|
|
851
872
|
);
|
|
852
|
-
const isSelected = itemValue === value;
|
|
873
|
+
const isSelected = itemValue === value?.value;
|
|
853
874
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
854
875
|
"button",
|
|
855
876
|
{
|
|
@@ -2740,6 +2761,106 @@ Textarea.displayName = "Textarea";
|
|
|
2740
2761
|
|
|
2741
2762
|
// source/components/primitive/FormField/form-field.tsx
|
|
2742
2763
|
var import_jsx_runtime33 = require("react/jsx-runtime");
|
|
2764
|
+
function stripThousands(s) {
|
|
2765
|
+
return s.replace(/,/g, "");
|
|
2766
|
+
}
|
|
2767
|
+
function formFieldCanonicalizeNumericInput(raw, allowDecimal) {
|
|
2768
|
+
const withoutCommas = stripThousands(raw);
|
|
2769
|
+
const pattern = allowDecimal ? /[^\d.]/g : /\D/g;
|
|
2770
|
+
let cleaned = withoutCommas.replace(pattern, "");
|
|
2771
|
+
if (!allowDecimal) return cleaned;
|
|
2772
|
+
const parts = cleaned.split(".");
|
|
2773
|
+
if (parts.length <= 1) return cleaned;
|
|
2774
|
+
const intPart = parts[0] ?? "";
|
|
2775
|
+
const frac = parts.slice(1).join("");
|
|
2776
|
+
return intPart + "." + frac;
|
|
2777
|
+
}
|
|
2778
|
+
function formatThousands(intPart) {
|
|
2779
|
+
if (!intPart) return "";
|
|
2780
|
+
return intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
2781
|
+
}
|
|
2782
|
+
function formFieldFormatNumericDisplay(stored, allowDecimal, useGrouping) {
|
|
2783
|
+
if (stored == null) return "";
|
|
2784
|
+
const s = String(stored);
|
|
2785
|
+
if (s === "") return "";
|
|
2786
|
+
if (!useGrouping) return s;
|
|
2787
|
+
if (allowDecimal && s.includes(".")) {
|
|
2788
|
+
const [intPart, ...rest] = s.split(".");
|
|
2789
|
+
const frac = rest.join("");
|
|
2790
|
+
return formatThousands(intPart ?? "") + "." + frac;
|
|
2791
|
+
}
|
|
2792
|
+
return formatThousands(s);
|
|
2793
|
+
}
|
|
2794
|
+
var FORM_FIELD_MASK_SLOTS = /* @__PURE__ */ new Set(["#", "h", "H", "X", "x", "*"]);
|
|
2795
|
+
function isFormFieldMaskSlotChar(p) {
|
|
2796
|
+
return FORM_FIELD_MASK_SLOTS.has(p);
|
|
2797
|
+
}
|
|
2798
|
+
function matchesMaskSlot(slot, ch) {
|
|
2799
|
+
switch (slot) {
|
|
2800
|
+
case "#":
|
|
2801
|
+
return /\d/.test(ch);
|
|
2802
|
+
case "h":
|
|
2803
|
+
case "H":
|
|
2804
|
+
return /[0-9a-fA-F]/.test(ch);
|
|
2805
|
+
case "X":
|
|
2806
|
+
case "x":
|
|
2807
|
+
return /[A-Za-z]/.test(ch);
|
|
2808
|
+
case "*":
|
|
2809
|
+
return /[A-Za-z0-9]/.test(ch);
|
|
2810
|
+
default:
|
|
2811
|
+
return false;
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
function normalizeMaskSlotChar(slot, ch) {
|
|
2815
|
+
switch (slot) {
|
|
2816
|
+
case "#":
|
|
2817
|
+
return ch;
|
|
2818
|
+
case "h":
|
|
2819
|
+
return ch.toLowerCase();
|
|
2820
|
+
case "H":
|
|
2821
|
+
return /\d/.test(ch) ? ch : ch.toUpperCase();
|
|
2822
|
+
case "X":
|
|
2823
|
+
return ch.toUpperCase();
|
|
2824
|
+
case "x":
|
|
2825
|
+
return ch.toLowerCase();
|
|
2826
|
+
case "*":
|
|
2827
|
+
return ch;
|
|
2828
|
+
default:
|
|
2829
|
+
return ch;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
function formFieldMaskSlotCount(pattern) {
|
|
2833
|
+
return [...pattern].filter(isFormFieldMaskSlotChar).length;
|
|
2834
|
+
}
|
|
2835
|
+
function formFieldMaskExtractCanonical(raw, pattern) {
|
|
2836
|
+
const slots = [...pattern].filter(isFormFieldMaskSlotChar);
|
|
2837
|
+
if (!slots.length) return "";
|
|
2838
|
+
const out = [];
|
|
2839
|
+
let si = 0;
|
|
2840
|
+
for (const ch of raw) {
|
|
2841
|
+
if (si >= slots.length) break;
|
|
2842
|
+
const slot = slots[si];
|
|
2843
|
+
if (matchesMaskSlot(slot, ch)) {
|
|
2844
|
+
out.push(normalizeMaskSlotChar(slot, ch));
|
|
2845
|
+
si++;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
return out.join("");
|
|
2849
|
+
}
|
|
2850
|
+
function formFieldMaskFormatDisplay(pattern, canonical) {
|
|
2851
|
+
let ci = 0;
|
|
2852
|
+
let result = "";
|
|
2853
|
+
for (const p of pattern) {
|
|
2854
|
+
if (isFormFieldMaskSlotChar(p)) {
|
|
2855
|
+
if (ci >= canonical.length) break;
|
|
2856
|
+
result += canonical[ci];
|
|
2857
|
+
ci++;
|
|
2858
|
+
} else if (ci < canonical.length) {
|
|
2859
|
+
result += p;
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
return result;
|
|
2863
|
+
}
|
|
2743
2864
|
var VARIANTS_NEED_CONTROL = [
|
|
2744
2865
|
"checkbox",
|
|
2745
2866
|
"switch",
|
|
@@ -2778,6 +2899,8 @@ function FormField({
|
|
|
2778
2899
|
richTextProps,
|
|
2779
2900
|
dropzoneProps,
|
|
2780
2901
|
asyncSelectProps,
|
|
2902
|
+
numericInput,
|
|
2903
|
+
maskInput,
|
|
2781
2904
|
className,
|
|
2782
2905
|
renderInput
|
|
2783
2906
|
}) {
|
|
@@ -2785,6 +2908,39 @@ function FormField({
|
|
|
2785
2908
|
const inputId = `field-${generatedId}`;
|
|
2786
2909
|
const descriptionId = description ? `${inputId}-description` : void 0;
|
|
2787
2910
|
const externalError = error2 ? String(error2) : void 0;
|
|
2911
|
+
if (maskInput && numericInput) {
|
|
2912
|
+
throw new Error(
|
|
2913
|
+
"FormField does not support `maskInput` and `numericInput` together; use only one."
|
|
2914
|
+
);
|
|
2915
|
+
}
|
|
2916
|
+
if (maskInput) {
|
|
2917
|
+
if (!control) {
|
|
2918
|
+
throw new Error(
|
|
2919
|
+
"FormField `maskInput` requires the `control` prop (Controller). Use `control`, not `register`, for masked fields."
|
|
2920
|
+
);
|
|
2921
|
+
}
|
|
2922
|
+
if (variant !== "input") {
|
|
2923
|
+
throw new Error('FormField `maskInput` is only supported with the default `variant="input"`.');
|
|
2924
|
+
}
|
|
2925
|
+
if (!maskInput.pattern?.trim()) {
|
|
2926
|
+
throw new Error("FormField `maskInput.pattern` must be a non-empty string.");
|
|
2927
|
+
}
|
|
2928
|
+
if (formFieldMaskSlotCount(maskInput.pattern) === 0) {
|
|
2929
|
+
throw new Error(
|
|
2930
|
+
"FormField `maskInput.pattern` must include at least one slot token: `#`, `h`, `H`, `X`, `x`, or `*`."
|
|
2931
|
+
);
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
if (numericInput) {
|
|
2935
|
+
if (!control) {
|
|
2936
|
+
throw new Error(
|
|
2937
|
+
"FormField `numericInput` requires the `control` prop (Controller). Use `control`, not `register`, for formatted numeric fields."
|
|
2938
|
+
);
|
|
2939
|
+
}
|
|
2940
|
+
if (variant !== "input") {
|
|
2941
|
+
throw new Error('FormField `numericInput` is only supported with the default `variant="input"`.');
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2788
2944
|
if (!control && VARIANTS_NEED_CONTROL.includes(variant)) {
|
|
2789
2945
|
throw new Error(
|
|
2790
2946
|
`FormField variant "${variant}" requires the control prop (React Hook Form Controller).`
|
|
@@ -2814,7 +2970,7 @@ function FormField({
|
|
|
2814
2970
|
};
|
|
2815
2971
|
const controlNode = renderInput ? renderInput({
|
|
2816
2972
|
...sharedInputShell,
|
|
2817
|
-
value: field.value ?? "",
|
|
2973
|
+
value: variant === "async-select" ? formValueToAsyncSelectOption(field.value) ?? null : field.value ?? "",
|
|
2818
2974
|
onChange: field.onChange,
|
|
2819
2975
|
onBlur: field.onBlur,
|
|
2820
2976
|
ref: field.ref
|
|
@@ -2838,7 +2994,9 @@ function FormField({
|
|
|
2838
2994
|
otpProps,
|
|
2839
2995
|
richTextProps,
|
|
2840
2996
|
dropzoneProps,
|
|
2841
|
-
asyncSelectProps
|
|
2997
|
+
asyncSelectProps,
|
|
2998
|
+
numericInput: renderInput ? void 0 : numericInput,
|
|
2999
|
+
maskInput: renderInput ? void 0 : maskInput
|
|
2842
3000
|
}
|
|
2843
3001
|
);
|
|
2844
3002
|
const labelBlock = isCheckboxInline || !hasFieldLabel2 ? null : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
@@ -2958,6 +3116,17 @@ function FormField({
|
|
|
2958
3116
|
externalError && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Text, { id: `${inputId}-error`, size: "sm", tone: "destructive", children: externalError })
|
|
2959
3117
|
] });
|
|
2960
3118
|
}
|
|
3119
|
+
function formValueToAsyncSelectOption(v) {
|
|
3120
|
+
if (v == null || v === "") return void 0;
|
|
3121
|
+
if (typeof v === "object" && v !== null && "value" in v) {
|
|
3122
|
+
const o = v;
|
|
3123
|
+
return {
|
|
3124
|
+
value: String(o.value ?? ""),
|
|
3125
|
+
label: o.label != null ? String(o.label) : ""
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
return { value: String(v), label: "" };
|
|
3129
|
+
}
|
|
2961
3130
|
function FormFieldVariantControl({
|
|
2962
3131
|
variant,
|
|
2963
3132
|
inputId,
|
|
@@ -2976,7 +3145,9 @@ function FormFieldVariantControl({
|
|
|
2976
3145
|
otpProps,
|
|
2977
3146
|
richTextProps,
|
|
2978
3147
|
dropzoneProps,
|
|
2979
|
-
asyncSelectProps
|
|
3148
|
+
asyncSelectProps,
|
|
3149
|
+
numericInput,
|
|
3150
|
+
maskInput
|
|
2980
3151
|
}) {
|
|
2981
3152
|
switch (variant) {
|
|
2982
3153
|
case "textarea":
|
|
@@ -3082,7 +3253,8 @@ function FormFieldVariantControl({
|
|
|
3082
3253
|
AsyncSelect,
|
|
3083
3254
|
{
|
|
3084
3255
|
...asyncSelectProps,
|
|
3085
|
-
|
|
3256
|
+
id: inputId,
|
|
3257
|
+
value: formValueToAsyncSelectOption(field.value),
|
|
3086
3258
|
onValueChange: field.onChange,
|
|
3087
3259
|
disabled: field.disabled,
|
|
3088
3260
|
error: hasError,
|
|
@@ -3213,22 +3385,100 @@ function FormFieldVariantControl({
|
|
|
3213
3385
|
`${String(field.name)}-${field.value === null ? "cleared" : typeof FileList !== "undefined" && field.value instanceof FileList ? field.value.length : "open"}`
|
|
3214
3386
|
) });
|
|
3215
3387
|
case "input":
|
|
3216
|
-
default:
|
|
3388
|
+
default: {
|
|
3389
|
+
const {
|
|
3390
|
+
onChange: inputOnChangeFromProps,
|
|
3391
|
+
value: _omitValueFromInputProps,
|
|
3392
|
+
type: inputTypeProp,
|
|
3393
|
+
inputMode: inputModeProp,
|
|
3394
|
+
...restInputProps
|
|
3395
|
+
} = inputProps ?? {};
|
|
3396
|
+
if (maskInput) {
|
|
3397
|
+
const pattern = maskInput.pattern;
|
|
3398
|
+
const rawStored = field.value == null || field.value === "" ? "" : String(field.value);
|
|
3399
|
+
const displayValue = formFieldMaskFormatDisplay(pattern, rawStored);
|
|
3400
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "w-full min-w-0", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
3401
|
+
Input,
|
|
3402
|
+
{
|
|
3403
|
+
...restInputProps,
|
|
3404
|
+
id: inputId,
|
|
3405
|
+
name: field.name,
|
|
3406
|
+
"aria-describedby": describedBy,
|
|
3407
|
+
error: hasError,
|
|
3408
|
+
type: "text",
|
|
3409
|
+
inputMode: maskInput.inputMode ?? "text",
|
|
3410
|
+
maxLength: pattern.length,
|
|
3411
|
+
autoComplete: "off",
|
|
3412
|
+
spellCheck: false,
|
|
3413
|
+
value: displayValue,
|
|
3414
|
+
onChange: (e) => {
|
|
3415
|
+
const canonical = formFieldMaskExtractCanonical(
|
|
3416
|
+
e.target.value,
|
|
3417
|
+
pattern
|
|
3418
|
+
);
|
|
3419
|
+
field.onChange(canonical);
|
|
3420
|
+
},
|
|
3421
|
+
onBlur: field.onBlur,
|
|
3422
|
+
ref: field.ref,
|
|
3423
|
+
disabled: field.disabled
|
|
3424
|
+
}
|
|
3425
|
+
) });
|
|
3426
|
+
}
|
|
3427
|
+
if (numericInput) {
|
|
3428
|
+
const allowDecimal = numericInput.allowDecimal ?? false;
|
|
3429
|
+
const useGrouping = numericInput.useGrouping ?? true;
|
|
3430
|
+
const rawStored = field.value == null || field.value === "" ? "" : String(field.value);
|
|
3431
|
+
const displayValue = formFieldFormatNumericDisplay(
|
|
3432
|
+
rawStored,
|
|
3433
|
+
allowDecimal,
|
|
3434
|
+
useGrouping
|
|
3435
|
+
);
|
|
3436
|
+
const defaultInputMode = allowDecimal ? "decimal" : "numeric";
|
|
3437
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "w-full min-w-0", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
3438
|
+
Input,
|
|
3439
|
+
{
|
|
3440
|
+
...restInputProps,
|
|
3441
|
+
id: inputId,
|
|
3442
|
+
name: field.name,
|
|
3443
|
+
"aria-describedby": describedBy,
|
|
3444
|
+
error: hasError,
|
|
3445
|
+
type: "text",
|
|
3446
|
+
inputMode: numericInput.inputMode ?? defaultInputMode,
|
|
3447
|
+
value: displayValue,
|
|
3448
|
+
onChange: (e) => {
|
|
3449
|
+
const canonical = formFieldCanonicalizeNumericInput(
|
|
3450
|
+
e.target.value,
|
|
3451
|
+
allowDecimal
|
|
3452
|
+
);
|
|
3453
|
+
field.onChange(canonical);
|
|
3454
|
+
},
|
|
3455
|
+
onBlur: field.onBlur,
|
|
3456
|
+
ref: field.ref,
|
|
3457
|
+
disabled: field.disabled
|
|
3458
|
+
}
|
|
3459
|
+
) });
|
|
3460
|
+
}
|
|
3217
3461
|
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "w-full min-w-0", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
3218
3462
|
Input,
|
|
3219
3463
|
{
|
|
3220
|
-
...
|
|
3464
|
+
...restInputProps,
|
|
3221
3465
|
id: inputId,
|
|
3222
3466
|
name: field.name,
|
|
3223
3467
|
"aria-describedby": describedBy,
|
|
3224
3468
|
error: hasError,
|
|
3469
|
+
type: inputTypeProp,
|
|
3470
|
+
inputMode: inputModeProp,
|
|
3225
3471
|
value: field.value ?? "",
|
|
3226
|
-
onChange:
|
|
3472
|
+
onChange: (e) => {
|
|
3473
|
+
inputOnChangeFromProps?.(e);
|
|
3474
|
+
field.onChange(e);
|
|
3475
|
+
},
|
|
3227
3476
|
onBlur: field.onBlur,
|
|
3228
3477
|
ref: field.ref,
|
|
3229
3478
|
disabled: field.disabled
|
|
3230
3479
|
}
|
|
3231
3480
|
) });
|
|
3481
|
+
}
|
|
3232
3482
|
}
|
|
3233
3483
|
}
|
|
3234
3484
|
|
|
@@ -5455,6 +5705,12 @@ var typography = {
|
|
|
5455
5705
|
colors,
|
|
5456
5706
|
containerVariants,
|
|
5457
5707
|
dismissToast,
|
|
5708
|
+
formFieldCanonicalizeNumericInput,
|
|
5709
|
+
formFieldFormatNumericDisplay,
|
|
5710
|
+
formFieldMaskExtractCanonical,
|
|
5711
|
+
formFieldMaskFormatDisplay,
|
|
5712
|
+
formFieldMaskSlotCount,
|
|
5713
|
+
formValueToAsyncSelectOption,
|
|
5458
5714
|
getIcon,
|
|
5459
5715
|
headingVariants,
|
|
5460
5716
|
inputGroupSelectTriggerTextAlignClass,
|
package/dist/index.d.cts
CHANGED
|
@@ -64,6 +64,11 @@ declare const AccordionItem: React.ForwardRefExoticComponent<Omit<AccordionPrimi
|
|
|
64
64
|
declare const AccordionTrigger: React.ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
65
65
|
declare const AccordionContent: React.ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
66
66
|
|
|
67
|
+
/** Controlled value / `onValueChange` payload (same shape for prepopulation and after select). */
|
|
68
|
+
type AsyncSelectOption = {
|
|
69
|
+
value: string;
|
|
70
|
+
label: string;
|
|
71
|
+
};
|
|
67
72
|
interface AsyncSelectPage<T = Record<string, unknown>> {
|
|
68
73
|
data: T[];
|
|
69
74
|
hasMore: boolean;
|
|
@@ -80,10 +85,10 @@ interface AsyncSelectProps<T = Record<string, unknown>> {
|
|
|
80
85
|
valueKey: keyof T & string;
|
|
81
86
|
/** Key (or render fn) for the display label. */
|
|
82
87
|
labelKey: (keyof T & string) | ((item: T) => React.ReactNode);
|
|
83
|
-
/** Controlled value. */
|
|
84
|
-
value?:
|
|
85
|
-
/**
|
|
86
|
-
onValueChange?: (
|
|
88
|
+
/** Controlled value (`value` + `label`, e.g. from the server). */
|
|
89
|
+
value?: AsyncSelectOption | null;
|
|
90
|
+
/** Called with `{ value, label }` for a selection, or `null` when cleared. */
|
|
91
|
+
onValueChange?: (option: AsyncSelectOption | null) => void;
|
|
87
92
|
/** Placeholder shown in the trigger when nothing is selected. */
|
|
88
93
|
placeholder?: string;
|
|
89
94
|
/** Placeholder for the search input inside the dropdown. */
|
|
@@ -97,6 +102,8 @@ interface AsyncSelectProps<T = Record<string, unknown>> {
|
|
|
97
102
|
disabled?: boolean;
|
|
98
103
|
error?: boolean;
|
|
99
104
|
name?: string;
|
|
105
|
+
/** Sets `id` on the combobox trigger (use with `Label htmlFor`). */
|
|
106
|
+
id?: string;
|
|
100
107
|
className?: string;
|
|
101
108
|
/** Custom fetcher. Defaults to a JSON fetch that appends `?search=&page=` to `url`. */
|
|
102
109
|
fetcher?: AsyncSelectFetcher<T>;
|
|
@@ -124,8 +131,8 @@ interface AsyncSelectProps<T = Record<string, unknown>> {
|
|
|
124
131
|
* url="/api/employees"
|
|
125
132
|
* valueKey="id"
|
|
126
133
|
* labelKey="fullName"
|
|
127
|
-
* value={
|
|
128
|
-
* onValueChange={
|
|
134
|
+
* value={{ value: employee.id, label: employee.fullName }}
|
|
135
|
+
* onValueChange={setEmployee}
|
|
129
136
|
* placeholder="Choose an employee…"
|
|
130
137
|
* />
|
|
131
138
|
* ```
|
|
@@ -476,7 +483,49 @@ type FormFieldOtpConfig = {
|
|
|
476
483
|
containerClassName?: string;
|
|
477
484
|
} & Omit<React.ComponentPropsWithoutRef<typeof InputOTP>, "maxLength" | "value" | "onChange" | "containerClassName" | "render" | "invalid">;
|
|
478
485
|
type FormFieldDropzoneConfig = Omit<FileDropzoneProps, "id" | "name" | "onChange" | "onBlur" | "ref" | "disabled" | "error" | "value">;
|
|
479
|
-
|
|
486
|
+
/**
|
|
487
|
+
* Props for `AsyncSelect` when used via `FormField` (`variant="async-select"`).
|
|
488
|
+
* The controlled field value is `{ value, label } | null | undefined` (legacy plain `string` is still read as id-only).
|
|
489
|
+
*/
|
|
490
|
+
type FormFieldAsyncSelectConfig = Omit<AsyncSelectProps, "value" | "onValueChange" | "disabled" | "error" | "name" | "id">;
|
|
491
|
+
/**
|
|
492
|
+
* Form state holds a **canonical string** (digits only, or digits + a single `.` + fraction when `allowDecimal`).
|
|
493
|
+
* The input shows **grouped** thousands when `useGrouping` is true. Use with `control` and default `input` variant only.
|
|
494
|
+
*/
|
|
495
|
+
type FormFieldNumericInputConfig = {
|
|
496
|
+
/** Allow a single decimal separator in the stored value. @default false */
|
|
497
|
+
allowDecimal?: boolean;
|
|
498
|
+
/** Show `,` thousands separators in the input while the stored value has none. @default true */
|
|
499
|
+
useGrouping?: boolean;
|
|
500
|
+
/** Passed to `<input inputMode>`. Defaults to `"decimal"` if `allowDecimal`, otherwise `"numeric"`. */
|
|
501
|
+
inputMode?: React.HTMLAttributes<HTMLInputElement>["inputMode"];
|
|
502
|
+
};
|
|
503
|
+
/** Raw input → canonical string for form state (no commas, optional single dot). */
|
|
504
|
+
declare function formFieldCanonicalizeNumericInput(raw: string, allowDecimal: boolean): string;
|
|
505
|
+
/** Canonical stored string → display string with optional grouping. */
|
|
506
|
+
declare function formFieldFormatNumericDisplay(stored: string, allowDecimal: boolean, useGrouping: boolean): string;
|
|
507
|
+
/** Number of editable slots in `pattern` (each `#`, `h`, `H`, `X`, `x`, or `*`). */
|
|
508
|
+
declare function formFieldMaskSlotCount(pattern: string): number;
|
|
509
|
+
/**
|
|
510
|
+
* Reads `raw` left-to-right and fills slots in order (ignores typed/pasted literals such as `-`).
|
|
511
|
+
* Form state should hold this canonical string (no separators).
|
|
512
|
+
*/
|
|
513
|
+
declare function formFieldMaskExtractCanonical(raw: string, pattern: string): string;
|
|
514
|
+
/** Inserts pattern literals between filled slots; omits trailing literals when incomplete. */
|
|
515
|
+
declare function formFieldMaskFormatDisplay(pattern: string, canonical: string): string;
|
|
516
|
+
/**
|
|
517
|
+
* Fixed mask for `control` + default `input`. Form state = **slot characters only** (no `-` etc.).
|
|
518
|
+
*
|
|
519
|
+
* **Tokens:** `#` digit · `h` hex (stored lower) · `H` hex (stored upper for a–f) · `X` letter upper · `x` letter lower · `*` alphanumeric.
|
|
520
|
+
* Any other character in `pattern` is a **literal** shown in that position (e.g. `-`).
|
|
521
|
+
*
|
|
522
|
+
* Example: `XXX-xxx-xxx` (letters). Hex / UUID-style: `hhhhhhhh-hhhh-hhhh-hhhh-hhhh-hhhh-hhhh-hhhh` (use `h`, not `x`, for hex digits).
|
|
523
|
+
*/
|
|
524
|
+
type FormFieldMaskInputConfig = {
|
|
525
|
+
pattern: string;
|
|
526
|
+
/** Passed to `<input inputMode>`. @default "text" */
|
|
527
|
+
inputMode?: React.HTMLAttributes<HTMLInputElement>["inputMode"];
|
|
528
|
+
};
|
|
480
529
|
interface FormFieldProps<TFieldValues extends FieldValues> {
|
|
481
530
|
name: Path<TFieldValues>;
|
|
482
531
|
/** When omitted, no visible label is rendered; use `aria-label` on controls or descriptions for a11y. */
|
|
@@ -498,16 +547,31 @@ interface FormFieldProps<TFieldValues extends FieldValues> {
|
|
|
498
547
|
otpProps?: FormFieldOtpConfig;
|
|
499
548
|
richTextProps?: Omit<RichTextEditorProps, "value" | "onChange" | "disabled">;
|
|
500
549
|
dropzoneProps?: FormFieldDropzoneConfig;
|
|
550
|
+
/** With `variant="async-select"`: URL fetch config; form state is `AsyncSelectOption | null` (see `FormFieldAsyncSelectConfig`). */
|
|
501
551
|
asyncSelectProps?: FormFieldAsyncSelectConfig;
|
|
552
|
+
/**
|
|
553
|
+
* When set with `control` and default `input` variant (and no `renderInput`), formats integers/decimals in the field.
|
|
554
|
+
* Ignored when using `renderInput` (build a custom input there instead).
|
|
555
|
+
*/
|
|
556
|
+
numericInput?: FormFieldNumericInputConfig;
|
|
557
|
+
/**
|
|
558
|
+
* Fixed character mask (`pattern` with slot tokens + literals). Mutually exclusive with `numericInput`.
|
|
559
|
+
* Ignored when using `renderInput`.
|
|
560
|
+
*/
|
|
561
|
+
maskInput?: FormFieldMaskInputConfig;
|
|
502
562
|
className?: string;
|
|
503
|
-
renderInput?: (props: FormFieldRenderProps & {
|
|
563
|
+
renderInput?: (props: Omit<FormFieldRenderProps, "value"> & {
|
|
504
564
|
id: string;
|
|
505
565
|
name: string;
|
|
506
566
|
"aria-describedby"?: string;
|
|
507
567
|
error?: boolean;
|
|
568
|
+
/** With `variant="async-select"`, this is `AsyncSelectOption | null`; otherwise the field primitive value. */
|
|
569
|
+
value?: InputProps["value"] | AsyncSelectOption | null;
|
|
508
570
|
}) => React.ReactNode;
|
|
509
571
|
}
|
|
510
|
-
declare function FormField<TFieldValues extends FieldValues>({ name, label, register, control, rules, description, required, error, variant, inputProps, textareaProps, checkboxProps, switchProps, selectProps, radioProps, otpProps, richTextProps, dropzoneProps, asyncSelectProps, className, renderInput, }: FormFieldProps<TFieldValues>): react_jsx_runtime.JSX.Element;
|
|
572
|
+
declare function FormField<TFieldValues extends FieldValues>({ name, label, register, control, rules, description, required, error, variant, inputProps, textareaProps, checkboxProps, switchProps, selectProps, radioProps, otpProps, richTextProps, dropzoneProps, asyncSelectProps, numericInput, maskInput, className, renderInput, }: FormFieldProps<TFieldValues>): react_jsx_runtime.JSX.Element;
|
|
573
|
+
/** Maps react-hook-form field values to `AsyncSelect`’s `value` prop (supports `{ value, label }`, legacy `string`, `null`/`undefined`). */
|
|
574
|
+
declare function formValueToAsyncSelectOption(v: unknown): AsyncSelectOption | undefined;
|
|
511
575
|
|
|
512
576
|
/**
|
|
513
577
|
* Heading levels map to the correct HTML element and default size/weight from
|
|
@@ -876,4 +940,4 @@ declare namespace Toaster {
|
|
|
876
940
|
var displayName: string;
|
|
877
941
|
}
|
|
878
942
|
|
|
879
|
-
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, AppShell, type AppShellProps, AspectRatio, type AspectRatioProps, AsyncSelect, type AsyncSelectFetcher, type AsyncSelectPage, type AsyncSelectProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, CalendarDayButton, Caption, type CaptionProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, type CheckboxProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Container, type ContainerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FileDropzone, type FileDropzoneProps, FormField, type FormFieldAsyncSelectConfig, type FormFieldDropzoneConfig, type FormFieldOtpConfig, type FormFieldProps, type FormFieldRadioConfig, type FormFieldRadioOption, type FormFieldSelectConfig, type FormFieldSelectItem, type FormFieldVariant, Heading, type HeadingProps, Icon, type IconComponent, type IconComponentProps, type IconProps, Input, InputGroup, InputGroupIcon, InputGroupInput, type InputGroupInputProps, type InputGroupProps, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, type LabelProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Progress, type ProgressProps, RadioGroup, RadioGroupItem, RichHtml, type RichHtmlProps, RichTextEditor, type RichTextEditorProps, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, type SkeletonProps, Slider, type SliderProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, type TextProps, Textarea, type TextareaProps, Toast, ToastAction, ToastClose, ToastDescription, type ToastPayload, type ToastProps, ToastProvider, type ToastRecord, ToastTitle, ToastViewport, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, captionVariants, checkboxVariants, containerVariants, dismissToast, getIcon, headingVariants, inputGroupSelectTriggerTextAlignClass, inputVariants, labelVariants, registerIcons, separatorVariants, sidebarMenuButtonVariants, skeletonVariants, switchRootVariants, switchThumbVariants, textVariants, toast, toastVariants, useSidebar, useToast };
|
|
943
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, AppShell, type AppShellProps, AspectRatio, type AspectRatioProps, AsyncSelect, type AsyncSelectFetcher, type AsyncSelectOption, type AsyncSelectPage, type AsyncSelectProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, CalendarDayButton, Caption, type CaptionProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, type CheckboxProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Container, type ContainerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FileDropzone, type FileDropzoneProps, FormField, type FormFieldAsyncSelectConfig, type FormFieldDropzoneConfig, type FormFieldMaskInputConfig, type FormFieldNumericInputConfig, type FormFieldOtpConfig, type FormFieldProps, type FormFieldRadioConfig, type FormFieldRadioOption, type FormFieldSelectConfig, type FormFieldSelectItem, type FormFieldVariant, Heading, type HeadingProps, Icon, type IconComponent, type IconComponentProps, type IconProps, Input, InputGroup, InputGroupIcon, InputGroupInput, type InputGroupInputProps, type InputGroupProps, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, type LabelProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Progress, type ProgressProps, RadioGroup, RadioGroupItem, RichHtml, type RichHtmlProps, RichTextEditor, type RichTextEditorProps, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, type SkeletonProps, Slider, type SliderProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, type TextProps, Textarea, type TextareaProps, Toast, ToastAction, ToastClose, ToastDescription, type ToastPayload, type ToastProps, ToastProvider, type ToastRecord, ToastTitle, ToastViewport, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, captionVariants, checkboxVariants, containerVariants, dismissToast, formFieldCanonicalizeNumericInput, formFieldFormatNumericDisplay, formFieldMaskExtractCanonical, formFieldMaskFormatDisplay, formFieldMaskSlotCount, formValueToAsyncSelectOption, getIcon, headingVariants, inputGroupSelectTriggerTextAlignClass, inputVariants, labelVariants, registerIcons, separatorVariants, sidebarMenuButtonVariants, skeletonVariants, switchRootVariants, switchThumbVariants, textVariants, toast, toastVariants, useSidebar, useToast };
|
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,11 @@ declare const AccordionItem: React.ForwardRefExoticComponent<Omit<AccordionPrimi
|
|
|
64
64
|
declare const AccordionTrigger: React.ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
65
65
|
declare const AccordionContent: React.ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
66
66
|
|
|
67
|
+
/** Controlled value / `onValueChange` payload (same shape for prepopulation and after select). */
|
|
68
|
+
type AsyncSelectOption = {
|
|
69
|
+
value: string;
|
|
70
|
+
label: string;
|
|
71
|
+
};
|
|
67
72
|
interface AsyncSelectPage<T = Record<string, unknown>> {
|
|
68
73
|
data: T[];
|
|
69
74
|
hasMore: boolean;
|
|
@@ -80,10 +85,10 @@ interface AsyncSelectProps<T = Record<string, unknown>> {
|
|
|
80
85
|
valueKey: keyof T & string;
|
|
81
86
|
/** Key (or render fn) for the display label. */
|
|
82
87
|
labelKey: (keyof T & string) | ((item: T) => React.ReactNode);
|
|
83
|
-
/** Controlled value. */
|
|
84
|
-
value?:
|
|
85
|
-
/**
|
|
86
|
-
onValueChange?: (
|
|
88
|
+
/** Controlled value (`value` + `label`, e.g. from the server). */
|
|
89
|
+
value?: AsyncSelectOption | null;
|
|
90
|
+
/** Called with `{ value, label }` for a selection, or `null` when cleared. */
|
|
91
|
+
onValueChange?: (option: AsyncSelectOption | null) => void;
|
|
87
92
|
/** Placeholder shown in the trigger when nothing is selected. */
|
|
88
93
|
placeholder?: string;
|
|
89
94
|
/** Placeholder for the search input inside the dropdown. */
|
|
@@ -97,6 +102,8 @@ interface AsyncSelectProps<T = Record<string, unknown>> {
|
|
|
97
102
|
disabled?: boolean;
|
|
98
103
|
error?: boolean;
|
|
99
104
|
name?: string;
|
|
105
|
+
/** Sets `id` on the combobox trigger (use with `Label htmlFor`). */
|
|
106
|
+
id?: string;
|
|
100
107
|
className?: string;
|
|
101
108
|
/** Custom fetcher. Defaults to a JSON fetch that appends `?search=&page=` to `url`. */
|
|
102
109
|
fetcher?: AsyncSelectFetcher<T>;
|
|
@@ -124,8 +131,8 @@ interface AsyncSelectProps<T = Record<string, unknown>> {
|
|
|
124
131
|
* url="/api/employees"
|
|
125
132
|
* valueKey="id"
|
|
126
133
|
* labelKey="fullName"
|
|
127
|
-
* value={
|
|
128
|
-
* onValueChange={
|
|
134
|
+
* value={{ value: employee.id, label: employee.fullName }}
|
|
135
|
+
* onValueChange={setEmployee}
|
|
129
136
|
* placeholder="Choose an employee…"
|
|
130
137
|
* />
|
|
131
138
|
* ```
|
|
@@ -476,7 +483,49 @@ type FormFieldOtpConfig = {
|
|
|
476
483
|
containerClassName?: string;
|
|
477
484
|
} & Omit<React.ComponentPropsWithoutRef<typeof InputOTP>, "maxLength" | "value" | "onChange" | "containerClassName" | "render" | "invalid">;
|
|
478
485
|
type FormFieldDropzoneConfig = Omit<FileDropzoneProps, "id" | "name" | "onChange" | "onBlur" | "ref" | "disabled" | "error" | "value">;
|
|
479
|
-
|
|
486
|
+
/**
|
|
487
|
+
* Props for `AsyncSelect` when used via `FormField` (`variant="async-select"`).
|
|
488
|
+
* The controlled field value is `{ value, label } | null | undefined` (legacy plain `string` is still read as id-only).
|
|
489
|
+
*/
|
|
490
|
+
type FormFieldAsyncSelectConfig = Omit<AsyncSelectProps, "value" | "onValueChange" | "disabled" | "error" | "name" | "id">;
|
|
491
|
+
/**
|
|
492
|
+
* Form state holds a **canonical string** (digits only, or digits + a single `.` + fraction when `allowDecimal`).
|
|
493
|
+
* The input shows **grouped** thousands when `useGrouping` is true. Use with `control` and default `input` variant only.
|
|
494
|
+
*/
|
|
495
|
+
type FormFieldNumericInputConfig = {
|
|
496
|
+
/** Allow a single decimal separator in the stored value. @default false */
|
|
497
|
+
allowDecimal?: boolean;
|
|
498
|
+
/** Show `,` thousands separators in the input while the stored value has none. @default true */
|
|
499
|
+
useGrouping?: boolean;
|
|
500
|
+
/** Passed to `<input inputMode>`. Defaults to `"decimal"` if `allowDecimal`, otherwise `"numeric"`. */
|
|
501
|
+
inputMode?: React.HTMLAttributes<HTMLInputElement>["inputMode"];
|
|
502
|
+
};
|
|
503
|
+
/** Raw input → canonical string for form state (no commas, optional single dot). */
|
|
504
|
+
declare function formFieldCanonicalizeNumericInput(raw: string, allowDecimal: boolean): string;
|
|
505
|
+
/** Canonical stored string → display string with optional grouping. */
|
|
506
|
+
declare function formFieldFormatNumericDisplay(stored: string, allowDecimal: boolean, useGrouping: boolean): string;
|
|
507
|
+
/** Number of editable slots in `pattern` (each `#`, `h`, `H`, `X`, `x`, or `*`). */
|
|
508
|
+
declare function formFieldMaskSlotCount(pattern: string): number;
|
|
509
|
+
/**
|
|
510
|
+
* Reads `raw` left-to-right and fills slots in order (ignores typed/pasted literals such as `-`).
|
|
511
|
+
* Form state should hold this canonical string (no separators).
|
|
512
|
+
*/
|
|
513
|
+
declare function formFieldMaskExtractCanonical(raw: string, pattern: string): string;
|
|
514
|
+
/** Inserts pattern literals between filled slots; omits trailing literals when incomplete. */
|
|
515
|
+
declare function formFieldMaskFormatDisplay(pattern: string, canonical: string): string;
|
|
516
|
+
/**
|
|
517
|
+
* Fixed mask for `control` + default `input`. Form state = **slot characters only** (no `-` etc.).
|
|
518
|
+
*
|
|
519
|
+
* **Tokens:** `#` digit · `h` hex (stored lower) · `H` hex (stored upper for a–f) · `X` letter upper · `x` letter lower · `*` alphanumeric.
|
|
520
|
+
* Any other character in `pattern` is a **literal** shown in that position (e.g. `-`).
|
|
521
|
+
*
|
|
522
|
+
* Example: `XXX-xxx-xxx` (letters). Hex / UUID-style: `hhhhhhhh-hhhh-hhhh-hhhh-hhhh-hhhh-hhhh-hhhh` (use `h`, not `x`, for hex digits).
|
|
523
|
+
*/
|
|
524
|
+
type FormFieldMaskInputConfig = {
|
|
525
|
+
pattern: string;
|
|
526
|
+
/** Passed to `<input inputMode>`. @default "text" */
|
|
527
|
+
inputMode?: React.HTMLAttributes<HTMLInputElement>["inputMode"];
|
|
528
|
+
};
|
|
480
529
|
interface FormFieldProps<TFieldValues extends FieldValues> {
|
|
481
530
|
name: Path<TFieldValues>;
|
|
482
531
|
/** When omitted, no visible label is rendered; use `aria-label` on controls or descriptions for a11y. */
|
|
@@ -498,16 +547,31 @@ interface FormFieldProps<TFieldValues extends FieldValues> {
|
|
|
498
547
|
otpProps?: FormFieldOtpConfig;
|
|
499
548
|
richTextProps?: Omit<RichTextEditorProps, "value" | "onChange" | "disabled">;
|
|
500
549
|
dropzoneProps?: FormFieldDropzoneConfig;
|
|
550
|
+
/** With `variant="async-select"`: URL fetch config; form state is `AsyncSelectOption | null` (see `FormFieldAsyncSelectConfig`). */
|
|
501
551
|
asyncSelectProps?: FormFieldAsyncSelectConfig;
|
|
552
|
+
/**
|
|
553
|
+
* When set with `control` and default `input` variant (and no `renderInput`), formats integers/decimals in the field.
|
|
554
|
+
* Ignored when using `renderInput` (build a custom input there instead).
|
|
555
|
+
*/
|
|
556
|
+
numericInput?: FormFieldNumericInputConfig;
|
|
557
|
+
/**
|
|
558
|
+
* Fixed character mask (`pattern` with slot tokens + literals). Mutually exclusive with `numericInput`.
|
|
559
|
+
* Ignored when using `renderInput`.
|
|
560
|
+
*/
|
|
561
|
+
maskInput?: FormFieldMaskInputConfig;
|
|
502
562
|
className?: string;
|
|
503
|
-
renderInput?: (props: FormFieldRenderProps & {
|
|
563
|
+
renderInput?: (props: Omit<FormFieldRenderProps, "value"> & {
|
|
504
564
|
id: string;
|
|
505
565
|
name: string;
|
|
506
566
|
"aria-describedby"?: string;
|
|
507
567
|
error?: boolean;
|
|
568
|
+
/** With `variant="async-select"`, this is `AsyncSelectOption | null`; otherwise the field primitive value. */
|
|
569
|
+
value?: InputProps["value"] | AsyncSelectOption | null;
|
|
508
570
|
}) => React.ReactNode;
|
|
509
571
|
}
|
|
510
|
-
declare function FormField<TFieldValues extends FieldValues>({ name, label, register, control, rules, description, required, error, variant, inputProps, textareaProps, checkboxProps, switchProps, selectProps, radioProps, otpProps, richTextProps, dropzoneProps, asyncSelectProps, className, renderInput, }: FormFieldProps<TFieldValues>): react_jsx_runtime.JSX.Element;
|
|
572
|
+
declare function FormField<TFieldValues extends FieldValues>({ name, label, register, control, rules, description, required, error, variant, inputProps, textareaProps, checkboxProps, switchProps, selectProps, radioProps, otpProps, richTextProps, dropzoneProps, asyncSelectProps, numericInput, maskInput, className, renderInput, }: FormFieldProps<TFieldValues>): react_jsx_runtime.JSX.Element;
|
|
573
|
+
/** Maps react-hook-form field values to `AsyncSelect`’s `value` prop (supports `{ value, label }`, legacy `string`, `null`/`undefined`). */
|
|
574
|
+
declare function formValueToAsyncSelectOption(v: unknown): AsyncSelectOption | undefined;
|
|
511
575
|
|
|
512
576
|
/**
|
|
513
577
|
* Heading levels map to the correct HTML element and default size/weight from
|
|
@@ -876,4 +940,4 @@ declare namespace Toaster {
|
|
|
876
940
|
var displayName: string;
|
|
877
941
|
}
|
|
878
942
|
|
|
879
|
-
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, AppShell, type AppShellProps, AspectRatio, type AspectRatioProps, AsyncSelect, type AsyncSelectFetcher, type AsyncSelectPage, type AsyncSelectProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, CalendarDayButton, Caption, type CaptionProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, type CheckboxProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Container, type ContainerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FileDropzone, type FileDropzoneProps, FormField, type FormFieldAsyncSelectConfig, type FormFieldDropzoneConfig, type FormFieldOtpConfig, type FormFieldProps, type FormFieldRadioConfig, type FormFieldRadioOption, type FormFieldSelectConfig, type FormFieldSelectItem, type FormFieldVariant, Heading, type HeadingProps, Icon, type IconComponent, type IconComponentProps, type IconProps, Input, InputGroup, InputGroupIcon, InputGroupInput, type InputGroupInputProps, type InputGroupProps, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, type LabelProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Progress, type ProgressProps, RadioGroup, RadioGroupItem, RichHtml, type RichHtmlProps, RichTextEditor, type RichTextEditorProps, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, type SkeletonProps, Slider, type SliderProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, type TextProps, Textarea, type TextareaProps, Toast, ToastAction, ToastClose, ToastDescription, type ToastPayload, type ToastProps, ToastProvider, type ToastRecord, ToastTitle, ToastViewport, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, captionVariants, checkboxVariants, containerVariants, dismissToast, getIcon, headingVariants, inputGroupSelectTriggerTextAlignClass, inputVariants, labelVariants, registerIcons, separatorVariants, sidebarMenuButtonVariants, skeletonVariants, switchRootVariants, switchThumbVariants, textVariants, toast, toastVariants, useSidebar, useToast };
|
|
943
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, AppShell, type AppShellProps, AspectRatio, type AspectRatioProps, AsyncSelect, type AsyncSelectFetcher, type AsyncSelectOption, type AsyncSelectPage, type AsyncSelectProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, CalendarDayButton, Caption, type CaptionProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, type CheckboxProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Container, type ContainerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FileDropzone, type FileDropzoneProps, FormField, type FormFieldAsyncSelectConfig, type FormFieldDropzoneConfig, type FormFieldMaskInputConfig, type FormFieldNumericInputConfig, type FormFieldOtpConfig, type FormFieldProps, type FormFieldRadioConfig, type FormFieldRadioOption, type FormFieldSelectConfig, type FormFieldSelectItem, type FormFieldVariant, Heading, type HeadingProps, Icon, type IconComponent, type IconComponentProps, type IconProps, Input, InputGroup, InputGroupIcon, InputGroupInput, type InputGroupInputProps, type InputGroupProps, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, type LabelProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Progress, type ProgressProps, RadioGroup, RadioGroupItem, RichHtml, type RichHtmlProps, RichTextEditor, type RichTextEditorProps, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, type SkeletonProps, Slider, type SliderProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, type TextProps, Textarea, type TextareaProps, Toast, ToastAction, ToastClose, ToastDescription, type ToastPayload, type ToastProps, ToastProvider, type ToastRecord, ToastTitle, ToastViewport, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, captionVariants, checkboxVariants, containerVariants, dismissToast, formFieldCanonicalizeNumericInput, formFieldFormatNumericDisplay, formFieldMaskExtractCanonical, formFieldMaskFormatDisplay, formFieldMaskSlotCount, formValueToAsyncSelectOption, getIcon, headingVariants, inputGroupSelectTriggerTextAlignClass, inputVariants, labelVariants, registerIcons, separatorVariants, sidebarMenuButtonVariants, skeletonVariants, switchRootVariants, switchThumbVariants, textVariants, toast, toastVariants, useSidebar, useToast };
|
package/dist/index.js
CHANGED
|
@@ -398,6 +398,15 @@ function resolveLabel(item, labelKey) {
|
|
|
398
398
|
if (typeof labelKey === "function") return labelKey(item);
|
|
399
399
|
return String(item[labelKey] ?? "");
|
|
400
400
|
}
|
|
401
|
+
function reactNodeToLabelString(node) {
|
|
402
|
+
if (node == null || typeof node === "boolean") return "";
|
|
403
|
+
if (typeof node === "string" || typeof node === "number") return String(node);
|
|
404
|
+
if (Array.isArray(node)) return node.map(reactNodeToLabelString).join("");
|
|
405
|
+
return "";
|
|
406
|
+
}
|
|
407
|
+
function resolveStringLabel(item, labelKey) {
|
|
408
|
+
return reactNodeToLabelString(resolveLabel(item, labelKey));
|
|
409
|
+
}
|
|
401
410
|
function AsyncSelectInner(props) {
|
|
402
411
|
const {
|
|
403
412
|
url,
|
|
@@ -413,6 +422,7 @@ function AsyncSelectInner(props) {
|
|
|
413
422
|
disabled = false,
|
|
414
423
|
error = false,
|
|
415
424
|
name,
|
|
425
|
+
id,
|
|
416
426
|
className,
|
|
417
427
|
fetcher,
|
|
418
428
|
dataPath = "data",
|
|
@@ -434,8 +444,8 @@ function AsyncSelectInner(props) {
|
|
|
434
444
|
const searchInputRef = React6.useRef(null);
|
|
435
445
|
const abortRef = React6.useRef(null);
|
|
436
446
|
React6.useEffect(() => {
|
|
437
|
-
const
|
|
438
|
-
return () => clearTimeout(
|
|
447
|
+
const id2 = setTimeout(() => setDebouncedSearch(search), debounceMs);
|
|
448
|
+
return () => clearTimeout(id2);
|
|
439
449
|
}, [search, debounceMs]);
|
|
440
450
|
React6.useEffect(() => {
|
|
441
451
|
setPage(1);
|
|
@@ -509,38 +519,43 @@ function AsyncSelectInner(props) {
|
|
|
509
519
|
}
|
|
510
520
|
}, [open]);
|
|
511
521
|
React6.useEffect(() => {
|
|
512
|
-
|
|
522
|
+
const v = value?.value;
|
|
523
|
+
if (v == null || v === "") {
|
|
513
524
|
setSelectedItem(null);
|
|
514
525
|
return;
|
|
515
526
|
}
|
|
516
527
|
const found = items.find(
|
|
517
|
-
(it) => String(it[valueKey]) ===
|
|
528
|
+
(it) => String(it[valueKey]) === v
|
|
518
529
|
);
|
|
519
|
-
|
|
530
|
+
setSelectedItem(found ?? null);
|
|
520
531
|
}, [value, items, valueKey]);
|
|
521
532
|
const handleSelect = (item) => {
|
|
522
533
|
const itemValue = String(item[valueKey]);
|
|
523
|
-
if (itemValue === value && clearable) {
|
|
524
|
-
onValueChange?.(
|
|
534
|
+
if (itemValue === value?.value && clearable) {
|
|
535
|
+
onValueChange?.(null);
|
|
525
536
|
setSelectedItem(null);
|
|
526
537
|
} else {
|
|
527
|
-
onValueChange?.(
|
|
538
|
+
onValueChange?.({
|
|
539
|
+
value: itemValue,
|
|
540
|
+
label: resolveStringLabel(item, labelKey)
|
|
541
|
+
});
|
|
528
542
|
setSelectedItem(item);
|
|
529
543
|
}
|
|
530
544
|
setOpen(false);
|
|
531
545
|
};
|
|
532
546
|
const handleClear = (e) => {
|
|
533
547
|
e.stopPropagation();
|
|
534
|
-
onValueChange?.(
|
|
548
|
+
onValueChange?.(null);
|
|
535
549
|
setSelectedItem(null);
|
|
536
550
|
};
|
|
537
|
-
const triggerLabel = selectedItem ? resolveLabel(selectedItem, labelKey) : null;
|
|
551
|
+
const triggerLabel = selectedItem ? resolveLabel(selectedItem, labelKey) : value?.label ? value.label : null;
|
|
538
552
|
return /* @__PURE__ */ jsxs5(Popover, { open, onOpenChange: setOpen, children: [
|
|
539
|
-
name && /* @__PURE__ */ jsx6("input", { type: "hidden", name, value: value ?? "" }),
|
|
553
|
+
name && /* @__PURE__ */ jsx6("input", { type: "hidden", name, value: value?.value ?? "" }),
|
|
540
554
|
/* @__PURE__ */ jsx6(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs5(
|
|
541
555
|
"button",
|
|
542
556
|
{
|
|
543
557
|
ref: innerRef,
|
|
558
|
+
id,
|
|
544
559
|
type: "button",
|
|
545
560
|
role: "combobox",
|
|
546
561
|
"aria-expanded": open,
|
|
@@ -562,7 +577,7 @@ function AsyncSelectInner(props) {
|
|
|
562
577
|
}
|
|
563
578
|
),
|
|
564
579
|
/* @__PURE__ */ jsxs5("span", { className: "flex shrink-0 items-center gap-1", children: [
|
|
565
|
-
clearable && value && !disabled && /* @__PURE__ */ jsx6(
|
|
580
|
+
clearable && value?.value && !disabled && /* @__PURE__ */ jsx6(
|
|
566
581
|
"span",
|
|
567
582
|
{
|
|
568
583
|
role: "button",
|
|
@@ -609,7 +624,7 @@ function AsyncSelectInner(props) {
|
|
|
609
624
|
const itemValue = String(
|
|
610
625
|
item[valueKey]
|
|
611
626
|
);
|
|
612
|
-
const isSelected = itemValue === value;
|
|
627
|
+
const isSelected = itemValue === value?.value;
|
|
613
628
|
return /* @__PURE__ */ jsxs5(
|
|
614
629
|
"button",
|
|
615
630
|
{
|
|
@@ -2506,6 +2521,106 @@ Textarea.displayName = "Textarea";
|
|
|
2506
2521
|
|
|
2507
2522
|
// source/components/primitive/FormField/form-field.tsx
|
|
2508
2523
|
import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
2524
|
+
function stripThousands(s) {
|
|
2525
|
+
return s.replace(/,/g, "");
|
|
2526
|
+
}
|
|
2527
|
+
function formFieldCanonicalizeNumericInput(raw, allowDecimal) {
|
|
2528
|
+
const withoutCommas = stripThousands(raw);
|
|
2529
|
+
const pattern = allowDecimal ? /[^\d.]/g : /\D/g;
|
|
2530
|
+
let cleaned = withoutCommas.replace(pattern, "");
|
|
2531
|
+
if (!allowDecimal) return cleaned;
|
|
2532
|
+
const parts = cleaned.split(".");
|
|
2533
|
+
if (parts.length <= 1) return cleaned;
|
|
2534
|
+
const intPart = parts[0] ?? "";
|
|
2535
|
+
const frac = parts.slice(1).join("");
|
|
2536
|
+
return intPart + "." + frac;
|
|
2537
|
+
}
|
|
2538
|
+
function formatThousands(intPart) {
|
|
2539
|
+
if (!intPart) return "";
|
|
2540
|
+
return intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
2541
|
+
}
|
|
2542
|
+
function formFieldFormatNumericDisplay(stored, allowDecimal, useGrouping) {
|
|
2543
|
+
if (stored == null) return "";
|
|
2544
|
+
const s = String(stored);
|
|
2545
|
+
if (s === "") return "";
|
|
2546
|
+
if (!useGrouping) return s;
|
|
2547
|
+
if (allowDecimal && s.includes(".")) {
|
|
2548
|
+
const [intPart, ...rest] = s.split(".");
|
|
2549
|
+
const frac = rest.join("");
|
|
2550
|
+
return formatThousands(intPart ?? "") + "." + frac;
|
|
2551
|
+
}
|
|
2552
|
+
return formatThousands(s);
|
|
2553
|
+
}
|
|
2554
|
+
var FORM_FIELD_MASK_SLOTS = /* @__PURE__ */ new Set(["#", "h", "H", "X", "x", "*"]);
|
|
2555
|
+
function isFormFieldMaskSlotChar(p) {
|
|
2556
|
+
return FORM_FIELD_MASK_SLOTS.has(p);
|
|
2557
|
+
}
|
|
2558
|
+
function matchesMaskSlot(slot, ch) {
|
|
2559
|
+
switch (slot) {
|
|
2560
|
+
case "#":
|
|
2561
|
+
return /\d/.test(ch);
|
|
2562
|
+
case "h":
|
|
2563
|
+
case "H":
|
|
2564
|
+
return /[0-9a-fA-F]/.test(ch);
|
|
2565
|
+
case "X":
|
|
2566
|
+
case "x":
|
|
2567
|
+
return /[A-Za-z]/.test(ch);
|
|
2568
|
+
case "*":
|
|
2569
|
+
return /[A-Za-z0-9]/.test(ch);
|
|
2570
|
+
default:
|
|
2571
|
+
return false;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
function normalizeMaskSlotChar(slot, ch) {
|
|
2575
|
+
switch (slot) {
|
|
2576
|
+
case "#":
|
|
2577
|
+
return ch;
|
|
2578
|
+
case "h":
|
|
2579
|
+
return ch.toLowerCase();
|
|
2580
|
+
case "H":
|
|
2581
|
+
return /\d/.test(ch) ? ch : ch.toUpperCase();
|
|
2582
|
+
case "X":
|
|
2583
|
+
return ch.toUpperCase();
|
|
2584
|
+
case "x":
|
|
2585
|
+
return ch.toLowerCase();
|
|
2586
|
+
case "*":
|
|
2587
|
+
return ch;
|
|
2588
|
+
default:
|
|
2589
|
+
return ch;
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
function formFieldMaskSlotCount(pattern) {
|
|
2593
|
+
return [...pattern].filter(isFormFieldMaskSlotChar).length;
|
|
2594
|
+
}
|
|
2595
|
+
function formFieldMaskExtractCanonical(raw, pattern) {
|
|
2596
|
+
const slots = [...pattern].filter(isFormFieldMaskSlotChar);
|
|
2597
|
+
if (!slots.length) return "";
|
|
2598
|
+
const out = [];
|
|
2599
|
+
let si = 0;
|
|
2600
|
+
for (const ch of raw) {
|
|
2601
|
+
if (si >= slots.length) break;
|
|
2602
|
+
const slot = slots[si];
|
|
2603
|
+
if (matchesMaskSlot(slot, ch)) {
|
|
2604
|
+
out.push(normalizeMaskSlotChar(slot, ch));
|
|
2605
|
+
si++;
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
return out.join("");
|
|
2609
|
+
}
|
|
2610
|
+
function formFieldMaskFormatDisplay(pattern, canonical) {
|
|
2611
|
+
let ci = 0;
|
|
2612
|
+
let result = "";
|
|
2613
|
+
for (const p of pattern) {
|
|
2614
|
+
if (isFormFieldMaskSlotChar(p)) {
|
|
2615
|
+
if (ci >= canonical.length) break;
|
|
2616
|
+
result += canonical[ci];
|
|
2617
|
+
ci++;
|
|
2618
|
+
} else if (ci < canonical.length) {
|
|
2619
|
+
result += p;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
return result;
|
|
2623
|
+
}
|
|
2509
2624
|
var VARIANTS_NEED_CONTROL = [
|
|
2510
2625
|
"checkbox",
|
|
2511
2626
|
"switch",
|
|
@@ -2544,6 +2659,8 @@ function FormField({
|
|
|
2544
2659
|
richTextProps,
|
|
2545
2660
|
dropzoneProps,
|
|
2546
2661
|
asyncSelectProps,
|
|
2662
|
+
numericInput,
|
|
2663
|
+
maskInput,
|
|
2547
2664
|
className,
|
|
2548
2665
|
renderInput
|
|
2549
2666
|
}) {
|
|
@@ -2551,6 +2668,39 @@ function FormField({
|
|
|
2551
2668
|
const inputId = `field-${generatedId}`;
|
|
2552
2669
|
const descriptionId = description ? `${inputId}-description` : void 0;
|
|
2553
2670
|
const externalError = error ? String(error) : void 0;
|
|
2671
|
+
if (maskInput && numericInput) {
|
|
2672
|
+
throw new Error(
|
|
2673
|
+
"FormField does not support `maskInput` and `numericInput` together; use only one."
|
|
2674
|
+
);
|
|
2675
|
+
}
|
|
2676
|
+
if (maskInput) {
|
|
2677
|
+
if (!control) {
|
|
2678
|
+
throw new Error(
|
|
2679
|
+
"FormField `maskInput` requires the `control` prop (Controller). Use `control`, not `register`, for masked fields."
|
|
2680
|
+
);
|
|
2681
|
+
}
|
|
2682
|
+
if (variant !== "input") {
|
|
2683
|
+
throw new Error('FormField `maskInput` is only supported with the default `variant="input"`.');
|
|
2684
|
+
}
|
|
2685
|
+
if (!maskInput.pattern?.trim()) {
|
|
2686
|
+
throw new Error("FormField `maskInput.pattern` must be a non-empty string.");
|
|
2687
|
+
}
|
|
2688
|
+
if (formFieldMaskSlotCount(maskInput.pattern) === 0) {
|
|
2689
|
+
throw new Error(
|
|
2690
|
+
"FormField `maskInput.pattern` must include at least one slot token: `#`, `h`, `H`, `X`, `x`, or `*`."
|
|
2691
|
+
);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
if (numericInput) {
|
|
2695
|
+
if (!control) {
|
|
2696
|
+
throw new Error(
|
|
2697
|
+
"FormField `numericInput` requires the `control` prop (Controller). Use `control`, not `register`, for formatted numeric fields."
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
if (variant !== "input") {
|
|
2701
|
+
throw new Error('FormField `numericInput` is only supported with the default `variant="input"`.');
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2554
2704
|
if (!control && VARIANTS_NEED_CONTROL.includes(variant)) {
|
|
2555
2705
|
throw new Error(
|
|
2556
2706
|
`FormField variant "${variant}" requires the control prop (React Hook Form Controller).`
|
|
@@ -2580,7 +2730,7 @@ function FormField({
|
|
|
2580
2730
|
};
|
|
2581
2731
|
const controlNode = renderInput ? renderInput({
|
|
2582
2732
|
...sharedInputShell,
|
|
2583
|
-
value: field.value ?? "",
|
|
2733
|
+
value: variant === "async-select" ? formValueToAsyncSelectOption(field.value) ?? null : field.value ?? "",
|
|
2584
2734
|
onChange: field.onChange,
|
|
2585
2735
|
onBlur: field.onBlur,
|
|
2586
2736
|
ref: field.ref
|
|
@@ -2604,7 +2754,9 @@ function FormField({
|
|
|
2604
2754
|
otpProps,
|
|
2605
2755
|
richTextProps,
|
|
2606
2756
|
dropzoneProps,
|
|
2607
|
-
asyncSelectProps
|
|
2757
|
+
asyncSelectProps,
|
|
2758
|
+
numericInput: renderInput ? void 0 : numericInput,
|
|
2759
|
+
maskInput: renderInput ? void 0 : maskInput
|
|
2608
2760
|
}
|
|
2609
2761
|
);
|
|
2610
2762
|
const labelBlock = isCheckboxInline || !hasFieldLabel2 ? null : /* @__PURE__ */ jsx32(
|
|
@@ -2724,6 +2876,17 @@ function FormField({
|
|
|
2724
2876
|
externalError && /* @__PURE__ */ jsx32(Text, { id: `${inputId}-error`, size: "sm", tone: "destructive", children: externalError })
|
|
2725
2877
|
] });
|
|
2726
2878
|
}
|
|
2879
|
+
function formValueToAsyncSelectOption(v) {
|
|
2880
|
+
if (v == null || v === "") return void 0;
|
|
2881
|
+
if (typeof v === "object" && v !== null && "value" in v) {
|
|
2882
|
+
const o = v;
|
|
2883
|
+
return {
|
|
2884
|
+
value: String(o.value ?? ""),
|
|
2885
|
+
label: o.label != null ? String(o.label) : ""
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
return { value: String(v), label: "" };
|
|
2889
|
+
}
|
|
2727
2890
|
function FormFieldVariantControl({
|
|
2728
2891
|
variant,
|
|
2729
2892
|
inputId,
|
|
@@ -2742,7 +2905,9 @@ function FormFieldVariantControl({
|
|
|
2742
2905
|
otpProps,
|
|
2743
2906
|
richTextProps,
|
|
2744
2907
|
dropzoneProps,
|
|
2745
|
-
asyncSelectProps
|
|
2908
|
+
asyncSelectProps,
|
|
2909
|
+
numericInput,
|
|
2910
|
+
maskInput
|
|
2746
2911
|
}) {
|
|
2747
2912
|
switch (variant) {
|
|
2748
2913
|
case "textarea":
|
|
@@ -2848,7 +3013,8 @@ function FormFieldVariantControl({
|
|
|
2848
3013
|
AsyncSelect,
|
|
2849
3014
|
{
|
|
2850
3015
|
...asyncSelectProps,
|
|
2851
|
-
|
|
3016
|
+
id: inputId,
|
|
3017
|
+
value: formValueToAsyncSelectOption(field.value),
|
|
2852
3018
|
onValueChange: field.onChange,
|
|
2853
3019
|
disabled: field.disabled,
|
|
2854
3020
|
error: hasError,
|
|
@@ -2979,22 +3145,100 @@ function FormFieldVariantControl({
|
|
|
2979
3145
|
`${String(field.name)}-${field.value === null ? "cleared" : typeof FileList !== "undefined" && field.value instanceof FileList ? field.value.length : "open"}`
|
|
2980
3146
|
) });
|
|
2981
3147
|
case "input":
|
|
2982
|
-
default:
|
|
3148
|
+
default: {
|
|
3149
|
+
const {
|
|
3150
|
+
onChange: inputOnChangeFromProps,
|
|
3151
|
+
value: _omitValueFromInputProps,
|
|
3152
|
+
type: inputTypeProp,
|
|
3153
|
+
inputMode: inputModeProp,
|
|
3154
|
+
...restInputProps
|
|
3155
|
+
} = inputProps ?? {};
|
|
3156
|
+
if (maskInput) {
|
|
3157
|
+
const pattern = maskInput.pattern;
|
|
3158
|
+
const rawStored = field.value == null || field.value === "" ? "" : String(field.value);
|
|
3159
|
+
const displayValue = formFieldMaskFormatDisplay(pattern, rawStored);
|
|
3160
|
+
return /* @__PURE__ */ jsx32("div", { className: "w-full min-w-0", children: /* @__PURE__ */ jsx32(
|
|
3161
|
+
Input,
|
|
3162
|
+
{
|
|
3163
|
+
...restInputProps,
|
|
3164
|
+
id: inputId,
|
|
3165
|
+
name: field.name,
|
|
3166
|
+
"aria-describedby": describedBy,
|
|
3167
|
+
error: hasError,
|
|
3168
|
+
type: "text",
|
|
3169
|
+
inputMode: maskInput.inputMode ?? "text",
|
|
3170
|
+
maxLength: pattern.length,
|
|
3171
|
+
autoComplete: "off",
|
|
3172
|
+
spellCheck: false,
|
|
3173
|
+
value: displayValue,
|
|
3174
|
+
onChange: (e) => {
|
|
3175
|
+
const canonical = formFieldMaskExtractCanonical(
|
|
3176
|
+
e.target.value,
|
|
3177
|
+
pattern
|
|
3178
|
+
);
|
|
3179
|
+
field.onChange(canonical);
|
|
3180
|
+
},
|
|
3181
|
+
onBlur: field.onBlur,
|
|
3182
|
+
ref: field.ref,
|
|
3183
|
+
disabled: field.disabled
|
|
3184
|
+
}
|
|
3185
|
+
) });
|
|
3186
|
+
}
|
|
3187
|
+
if (numericInput) {
|
|
3188
|
+
const allowDecimal = numericInput.allowDecimal ?? false;
|
|
3189
|
+
const useGrouping = numericInput.useGrouping ?? true;
|
|
3190
|
+
const rawStored = field.value == null || field.value === "" ? "" : String(field.value);
|
|
3191
|
+
const displayValue = formFieldFormatNumericDisplay(
|
|
3192
|
+
rawStored,
|
|
3193
|
+
allowDecimal,
|
|
3194
|
+
useGrouping
|
|
3195
|
+
);
|
|
3196
|
+
const defaultInputMode = allowDecimal ? "decimal" : "numeric";
|
|
3197
|
+
return /* @__PURE__ */ jsx32("div", { className: "w-full min-w-0", children: /* @__PURE__ */ jsx32(
|
|
3198
|
+
Input,
|
|
3199
|
+
{
|
|
3200
|
+
...restInputProps,
|
|
3201
|
+
id: inputId,
|
|
3202
|
+
name: field.name,
|
|
3203
|
+
"aria-describedby": describedBy,
|
|
3204
|
+
error: hasError,
|
|
3205
|
+
type: "text",
|
|
3206
|
+
inputMode: numericInput.inputMode ?? defaultInputMode,
|
|
3207
|
+
value: displayValue,
|
|
3208
|
+
onChange: (e) => {
|
|
3209
|
+
const canonical = formFieldCanonicalizeNumericInput(
|
|
3210
|
+
e.target.value,
|
|
3211
|
+
allowDecimal
|
|
3212
|
+
);
|
|
3213
|
+
field.onChange(canonical);
|
|
3214
|
+
},
|
|
3215
|
+
onBlur: field.onBlur,
|
|
3216
|
+
ref: field.ref,
|
|
3217
|
+
disabled: field.disabled
|
|
3218
|
+
}
|
|
3219
|
+
) });
|
|
3220
|
+
}
|
|
2983
3221
|
return /* @__PURE__ */ jsx32("div", { className: "w-full min-w-0", children: /* @__PURE__ */ jsx32(
|
|
2984
3222
|
Input,
|
|
2985
3223
|
{
|
|
2986
|
-
...
|
|
3224
|
+
...restInputProps,
|
|
2987
3225
|
id: inputId,
|
|
2988
3226
|
name: field.name,
|
|
2989
3227
|
"aria-describedby": describedBy,
|
|
2990
3228
|
error: hasError,
|
|
3229
|
+
type: inputTypeProp,
|
|
3230
|
+
inputMode: inputModeProp,
|
|
2991
3231
|
value: field.value ?? "",
|
|
2992
|
-
onChange:
|
|
3232
|
+
onChange: (e) => {
|
|
3233
|
+
inputOnChangeFromProps?.(e);
|
|
3234
|
+
field.onChange(e);
|
|
3235
|
+
},
|
|
2993
3236
|
onBlur: field.onBlur,
|
|
2994
3237
|
ref: field.ref,
|
|
2995
3238
|
disabled: field.disabled
|
|
2996
3239
|
}
|
|
2997
3240
|
) });
|
|
3241
|
+
}
|
|
2998
3242
|
}
|
|
2999
3243
|
}
|
|
3000
3244
|
|
|
@@ -4913,6 +5157,12 @@ export {
|
|
|
4913
5157
|
colors,
|
|
4914
5158
|
containerVariants,
|
|
4915
5159
|
dismissToast,
|
|
5160
|
+
formFieldCanonicalizeNumericInput,
|
|
5161
|
+
formFieldFormatNumericDisplay,
|
|
5162
|
+
formFieldMaskExtractCanonical,
|
|
5163
|
+
formFieldMaskFormatDisplay,
|
|
5164
|
+
formFieldMaskSlotCount,
|
|
5165
|
+
formValueToAsyncSelectOption,
|
|
4916
5166
|
getIcon,
|
|
4917
5167
|
headingVariants,
|
|
4918
5168
|
inputGroupSelectTriggerTextAlignClass,
|