2019-07-03 20:14:39 +08:00
|
|
|
import * as React from 'react';
|
2019-07-04 15:00:11 +08:00
|
|
|
import { useForm as useRcForm, FormInstance as RcFormInstance } from 'rc-field-form';
|
2019-12-23 18:33:08 +08:00
|
|
|
import scrollIntoView from 'scroll-into-view-if-needed';
|
2020-01-14 22:50:49 +08:00
|
|
|
import { ScrollOptions } from './interface';
|
2019-07-04 15:00:11 +08:00
|
|
|
|
|
|
|
type InternalNamePath = (string | number)[];
|
2019-07-03 20:14:39 +08:00
|
|
|
|
|
|
|
/**
|
2019-12-26 15:48:52 +08:00
|
|
|
* Always debounce error to avoid [error -> null -> error] blink
|
2019-07-03 20:14:39 +08:00
|
|
|
*/
|
|
|
|
export function useCacheErrors(
|
|
|
|
errors: React.ReactNode[],
|
|
|
|
changeTrigger: (visible: boolean) => void,
|
2020-01-14 14:45:22 +08:00
|
|
|
directly: boolean,
|
2019-12-26 15:48:52 +08:00
|
|
|
): [boolean, React.ReactNode[]] {
|
|
|
|
const cacheRef = React.useRef({
|
|
|
|
errors,
|
|
|
|
visible: !!errors.length,
|
|
|
|
});
|
2019-07-03 20:14:39 +08:00
|
|
|
|
2020-01-07 16:20:18 +08:00
|
|
|
const [, forceUpdate] = React.useState({});
|
|
|
|
|
2020-01-14 14:45:22 +08:00
|
|
|
const update = () => {
|
|
|
|
const prevVisible = cacheRef.current.visible;
|
|
|
|
const newVisible = !!errors.length;
|
|
|
|
|
|
|
|
const prevErrors = cacheRef.current.errors;
|
|
|
|
cacheRef.current.errors = errors;
|
|
|
|
cacheRef.current.visible = newVisible;
|
|
|
|
|
|
|
|
if (prevVisible !== newVisible) {
|
|
|
|
changeTrigger(newVisible);
|
|
|
|
} else if (
|
|
|
|
prevErrors.length !== errors.length ||
|
|
|
|
prevErrors.some((prevErr, index) => prevErr !== errors[index])
|
|
|
|
) {
|
|
|
|
forceUpdate({});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-07-03 20:14:39 +08:00
|
|
|
React.useEffect(() => {
|
2020-01-14 14:45:22 +08:00
|
|
|
if (!directly) {
|
|
|
|
const timeout = setTimeout(update, 10);
|
|
|
|
return () => clearTimeout(timeout);
|
|
|
|
}
|
2019-12-26 15:48:52 +08:00
|
|
|
}, [errors]);
|
2019-07-03 20:14:39 +08:00
|
|
|
|
2020-01-14 14:45:22 +08:00
|
|
|
if (directly) {
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
2019-12-26 15:48:52 +08:00
|
|
|
return [cacheRef.current.visible, cacheRef.current.errors];
|
2019-07-03 20:14:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function toArray<T>(candidate?: T | T[] | false): T[] {
|
|
|
|
if (candidate === undefined || candidate === false) return [];
|
|
|
|
|
|
|
|
return Array.isArray(candidate) ? candidate : [candidate];
|
|
|
|
}
|
2019-07-04 15:00:11 +08:00
|
|
|
|
|
|
|
export function getFieldId(namePath: InternalNamePath, formName?: string): string | undefined {
|
|
|
|
if (!namePath.length) return undefined;
|
|
|
|
|
|
|
|
const mergedId = namePath.join('_');
|
|
|
|
return formName ? `${formName}_${mergedId}` : mergedId;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface FormInstance extends RcFormInstance {
|
2020-01-14 22:50:49 +08:00
|
|
|
scrollToField: (name: string | number | InternalNamePath, options?: ScrollOptions) => void;
|
2019-07-04 15:00:11 +08:00
|
|
|
__INTERNAL__: {
|
|
|
|
name?: string;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function useForm(form?: FormInstance): [FormInstance] {
|
2020-03-06 12:07:55 +08:00
|
|
|
const [rcForm] = useRcForm();
|
|
|
|
|
|
|
|
const wrapForm: FormInstance = React.useMemo(
|
|
|
|
() =>
|
|
|
|
form || {
|
|
|
|
...rcForm,
|
|
|
|
__INTERNAL__: {},
|
|
|
|
scrollToField: (name: string, options: ScrollOptions = {}) => {
|
|
|
|
const namePath = toArray(name);
|
|
|
|
const fieldId = getFieldId(namePath, wrapForm.__INTERNAL__.name);
|
|
|
|
const node: HTMLElement | null = fieldId ? document.getElementById(fieldId) : null;
|
|
|
|
|
|
|
|
if (node) {
|
|
|
|
scrollIntoView(node, {
|
|
|
|
scrollMode: 'if-needed',
|
|
|
|
block: 'nearest',
|
|
|
|
...options,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
[form, rcForm],
|
|
|
|
);
|
2019-07-04 15:00:11 +08:00
|
|
|
|
|
|
|
return [wrapForm];
|
|
|
|
}
|