122 lines
3.3 KiB
TypeScript
122 lines
3.3 KiB
TypeScript
/**
|
|
* https://www.naiveui.com/zh-CN/os-theme/components/form
|
|
*
|
|
* FIXME: `NForm` 和 `NFormItem` 的 slots 还没有实现。`NFormItemGi`组件。
|
|
*/
|
|
|
|
import { get, set } from 'lodash-es';
|
|
import type { FormInst, FormItemProps, FormProps } from 'naive-ui';
|
|
import { NForm, NFormItem, formItemProps, NInput, formProps } from 'naive-ui';
|
|
import type { Get, Paths } from 'type-fest';
|
|
import type { SlotsType } from 'vue';
|
|
import { Comment } from 'vue';
|
|
|
|
type UseSafeNFormOptions<FormValue> = {
|
|
initialFormValue?: FormValue;
|
|
};
|
|
|
|
export function useSafeNForm<T extends Record<string, any> = Record<string, unknown>>(
|
|
options: UseSafeNFormOptions<T> = {},
|
|
) {
|
|
const formInst = ref<FormInst | null>(null);
|
|
const formValue = ref<T>(structuredClone(toRaw(options.initialFormValue)) || ({} as T));
|
|
|
|
// 创建类型安全的 Form 组件
|
|
type SafeNFormProps = FormProps;
|
|
|
|
type SafeNFormSlots = SlotsType<{
|
|
default?: { count?: number };
|
|
}>;
|
|
|
|
const SafeNForm = defineComponent<SafeNFormProps, /* Emits */ [], /* EE */ never, SafeNFormSlots>(
|
|
(props, ctx) => {
|
|
return () => (
|
|
<NForm
|
|
{...props}
|
|
model={formValue.value}
|
|
ref={(inst) => {
|
|
formInst.value = inst as unknown as FormInst;
|
|
}}
|
|
>
|
|
{ctx.slots.default?.({})}
|
|
</NForm>
|
|
);
|
|
},
|
|
{
|
|
name: 'SafeNForm',
|
|
inheritAttrs: true,
|
|
props: formProps,
|
|
},
|
|
);
|
|
// <<<<<
|
|
|
|
// >>>>> 创建类型安全的 FormItem 组件
|
|
type SafeNFormItemProps<P extends Paths<T> & string> = FormItemProps & {
|
|
path: P;
|
|
};
|
|
|
|
type SafeNFormItemDefaultSlot<P extends Paths<T> & string> = {
|
|
value: Get<T, P>;
|
|
setValue: (val: Get<T, P>) => void;
|
|
};
|
|
|
|
const SafeNFormItemImpl = defineComponent<
|
|
SafeNFormItemProps<Paths<T> & string>,
|
|
/* Emits */ [],
|
|
/* EE */ never,
|
|
SlotsType<{ default: SafeNFormItemDefaultSlot<Paths<T> & string> }>
|
|
>(
|
|
(props, ctx) => {
|
|
return () => {
|
|
const value = get(formValue.value, props.path);
|
|
function setValue(val: typeof value) {
|
|
set(formValue.value, props.path, val);
|
|
}
|
|
|
|
const defaultSlotContent = ctx.slots.default?.({
|
|
value,
|
|
setValue,
|
|
});
|
|
|
|
// 如果没有提供默认 slot 内容,则渲染一个 NInput 作为默认输入组件
|
|
const renderDefaultNInput = defaultSlotContent?.some((v) => v.type !== Comment) ? null : (
|
|
<NInput value={value} onUpdate:value={setValue} />
|
|
);
|
|
|
|
return (
|
|
<NFormItem {...props} path={props.path}>
|
|
{defaultSlotContent}
|
|
{renderDefaultNInput}
|
|
</NFormItem>
|
|
);
|
|
};
|
|
},
|
|
{
|
|
name: 'SafeNFormItem',
|
|
inheritAttrs: false,
|
|
props: Object.keys(formItemProps) as unknown as [keyof FormItemProps],
|
|
},
|
|
);
|
|
|
|
// Expose a generic constructor so template literals narrow `path`.
|
|
type SafeNFormItemComponent = {
|
|
new <P extends Paths<T> & string>(
|
|
props: SafeNFormItemProps<P>,
|
|
): {
|
|
$props: SafeNFormItemProps<P>;
|
|
$slots: {
|
|
default?: (scope: SafeNFormItemDefaultSlot<P>) => VNode[];
|
|
};
|
|
};
|
|
};
|
|
const SafeNFormItem = SafeNFormItemImpl as SafeNFormItemComponent;
|
|
// <<<<<
|
|
|
|
return {
|
|
formValue,
|
|
SafeNForm,
|
|
SafeNFormItem,
|
|
formInst,
|
|
};
|
|
}
|