@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
167 lines (166 loc) • 6.48 kB
JavaScript
"use client";
import { useTranslation } from "../../i18n/useTranslation.mjs";
import form_default from "../../i18n/resources/en/form.mjs";
import { fieldStyles, fieldVariants } from "../Form/style.mjs";
import FormDivider from "../Form/components/FormDivider.mjs";
import FormTitle from "../Form/components/FormTitle.mjs";
import { resolveBinding } from "./binding.mjs";
import { useFormKitContext } from "./context.mjs";
import { toFieldValidator } from "./schema.mjs";
import { getInternals } from "./instance.mjs";
import { fieldKitStyles } from "./style.mjs";
import { useStoreSelector } from "./useStoreSelector.mjs";
import { cloneElement, isValidElement, memo, useCallback, useEffect, useId, useRef } from "react";
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
import { cx, useResponsive } from "antd-style";
//#region src/base-ui/FormKit/Field.tsx
const isEmpty = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
const FieldControl = memo(({ bare, children, deps, emptyValue, getValue, id, name, render, required, trigger, validate, validateDebounce, validateOn, valueProp }) => {
const { form } = useFormKitContext();
const { engine, validateOn: formValidateOn } = getInternals(form);
const { t } = useTranslation(form_default);
const errorId = `${id}-error`;
const requiredMessage = typeof required === "string" ? required : t("form.required");
const fieldValidator = validate ? toFieldValidator(validate) : void 0;
const validatorRef = useRef(void 0);
validatorRef.current = async (value, values) => {
if (required && isEmpty(value)) return requiredMessage;
return fieldValidator?.(value, values);
};
const hasValidator = Boolean(required || validate);
const depsKey = deps?.join("\0");
useEffect(() => engine.registerField(name, {
debounceMs: validateDebounce,
deps: depsKey ? depsKey.split("\0") : void 0,
validate: hasValidator ? (value, values) => validatorRef.current(value, values) : void 0,
validateOn: validateOn ?? formValidateOn
}), [
engine,
name,
hasValidator,
depsKey,
validateDebounce,
validateOn,
formValidateOn
]);
const field = useStoreSelector(engine, (e) => e.getField(name));
const child = isValidElement(children) ? children : void 0;
const binding = child ? resolveBinding(child, {
emptyValue,
getValue,
trigger,
valueProp
}) : void 0;
const handlerRef = useRef({
binding,
childProps: child?.props
});
handlerRef.current = {
binding,
childProps: child?.props
};
const onChange = useCallback((...args) => {
const { binding: current, childProps } = handlerRef.current;
if (current) childProps?.[current.trigger]?.(...args);
const next = current ? current.getValue(...args) : args[0];
engine.setValue(name, next, "user");
}, [engine, name]);
const onBlur = useCallback((...args) => {
handlerRef.current.childProps?.onBlur?.(...args);
engine.blurField(name);
}, [engine, name]);
const warnedRef = useRef(false);
useEffect(() => {
if (render || child || warnedRef.current || process.env.NODE_ENV === "production") return;
warnedRef.current = true;
console.warn(`[@lobehub/ui/base-ui/form] Form.Field "${name}" needs a single element child or a render prop; nothing was bound.`);
}, [
render,
child,
name
]);
const errorNode = field.error && !bare ? /* @__PURE__ */ jsx("div", {
className: fieldStyles.error,
id: errorId,
children: field.error
}) : null;
if (render) {
const renderProps = {
error: field.error,
name,
onBlur,
onChange,
touched: field.touched,
validating: field.validating,
value: field.value
};
return /* @__PURE__ */ jsxs(Fragment$1, { children: [render(renderProps), errorNode] });
}
if (!child || !binding) return /* @__PURE__ */ jsx(Fragment$1, { children });
const control = cloneElement(child, {
"aria-describedby": field.error ? errorId : void 0,
"aria-invalid": field.error ? true : void 0,
"id": child.props.id ?? id,
[binding.trigger]: onChange,
[binding.valueProp]: field.value === void 0 ? binding.emptyValue : field.value,
"onBlur": onBlur
});
return /* @__PURE__ */ jsxs(Fragment$1, { children: [control, errorNode] });
});
FieldControl.displayName = "FormFieldControl";
const FormField = memo(({ avatar, bare, children, className, deps, desc, divider, emptyValue, extra, getValue, hidden, label, layout, minWidth, name, render, required, style, tag, trigger, validate, validateDebounce, validateOn, valueProp, variant }) => {
const id = useId();
const { mobile } = useResponsive();
const config = useFormKitContext();
if (hidden) return null;
const control = !name ? children : /* @__PURE__ */ jsx(FieldControl, {
bare,
deps,
emptyValue,
getValue,
id,
name,
render,
required,
trigger,
validate,
validateDebounce,
validateOn,
valueProp,
children
});
if (bare) return /* @__PURE__ */ jsx(Fragment$1, { children: control });
const labelFor = render ? void 0 : name ? id : isValidElement(children) ? children.props.id : void 0;
const mergedLayout = layout || (mobile ? "vertical" : config.layout);
const mergedVariant = variant || config.variant;
const controlWidth = mergedLayout === "vertical" || minWidth === void 0 || minWidth === "" ? void 0 : typeof minWidth === "number" ? `${minWidth}px` : minWidth;
return /* @__PURE__ */ jsxs(Fragment$1, { children: [divider && /* @__PURE__ */ jsx(FormDivider, { visible: mergedVariant !== "borderless" }), /* @__PURE__ */ jsxs("div", {
className: cx(fieldVariants({ layout: mergedLayout }), className),
style,
children: [/* @__PURE__ */ jsx("label", {
className: fieldStyles.label,
htmlFor: labelFor,
children: /* @__PURE__ */ jsx(FormTitle, {
avatar,
desc,
tag,
title: required ? /* @__PURE__ */ jsxs(Fragment$1, { children: [label, /* @__PURE__ */ jsx("span", {
"aria-hidden": true,
className: fieldKitStyles.required,
children: "*"
})] }) : label
})
}), /* @__PURE__ */ jsxs("div", {
style: controlWidth ? { width: controlWidth } : void 0,
className: cx(fieldStyles.control, mergedLayout === "vertical" && fieldStyles.controlVertical, controlWidth && fieldKitStyles.controlFixed),
children: [control, extra && /* @__PURE__ */ jsx("div", {
className: fieldKitStyles.extra,
children: extra
})]
})]
})] });
});
FormField.displayName = "FormField";
//#endregion
export { FormField as default };
//# sourceMappingURL=Field.mjs.map