import React, { Fragment, useEffect, useRef } from 'react' import { createControllerBox, ISchemaVirtualFieldComponentProps, createEffectHook, useFormEffects, useFieldState, FormEffectHooks, SchemaField, FormPath, IVirtualBoxProps } from '@xformily/react-schema-renderer' import { Tabs, Badge } from 'antd' import { TabPaneProps } from 'antd/lib/tabs' import { IFormTab } from '../types' import { createMatchUpdate } from '../shared' enum StateMap { ON_FORM_TAB_ACTIVE_KEY_CHANGE = 'onFormTabActiveKeyChange' } const { onFormChange$ } = FormEffectHooks const EffectHooks = { onTabActiveKeyChange$: createEffectHook<{ name?: string path?: string value?: any }>(StateMap.ON_FORM_TAB_ACTIVE_KEY_CHANGE) } const parseTabItems = (items: any, hiddenKeys?: string[]) => { return items.reduce((buf: any, { schema, key }) => { if (Array.isArray(hiddenKeys)) { if (hiddenKeys.includes(key)) { return buf } } if (schema.getExtendsComponent() === 'tabpane') { return buf.concat({ props: schema.getExtendsComponentProps(), schema, key }) } return buf }, []) } const parseDefaultActiveKey = (hiddenKeys: Array = [], items: any, defaultActiveKey) => { if(!hiddenKeys.includes(defaultActiveKey))return defaultActiveKey const index = items.findIndex(item => !hiddenKeys.includes(item.key)) return index >= 0 ? items[index].key : '' } const parseChildrenErrors = (errors: any, target: string) => { return errors.filter(({ path }) => { return FormPath.parse(path).includes(target) }) } const addErrorBadge = ( tab: React.ReactNode, currentPath: FormPath, childrenErrors: any[] ) => { const currentErrors = childrenErrors.filter(({ path }) => { return FormPath.parse(path).includes(currentPath) }) if (currentErrors.length > 0) { return ( {tab} ) } return tab } type ExtendsProps = StateMap & typeof EffectHooks & { TabPane: React.FC } type ExtendsState = { activeKey?: string childrenErrors?: any } export const FormTab: React.FC> & ExtendsProps = createControllerBox( 'tab', ({ form, schema, name, path }: ISchemaVirtualFieldComponentProps) => { const orderProperties = schema.getOrderProperties() let { hiddenKeys, defaultActiveKey, ...componentProps } = schema.getExtendsComponentProps() hiddenKeys = hiddenKeys || [] const [{ activeKey, childrenErrors }, setFieldState] = useFieldState< ExtendsState >({ activeKey: parseDefaultActiveKey(hiddenKeys, orderProperties, defaultActiveKey), childrenErrors: [] }) const itemsRef = useRef([]) itemsRef.current = parseTabItems(orderProperties, hiddenKeys) const update = (cur: string) => { form.notify(StateMap.ON_FORM_TAB_ACTIVE_KEY_CHANGE, { name, path, value: cur }) } const matchUpdate = createMatchUpdate(name, path) useEffect(() => { if (Array.isArray(hiddenKeys)) { setFieldState({ activeKey: parseDefaultActiveKey(hiddenKeys, orderProperties, defaultActiveKey) }) } }, [hiddenKeys.length]) useFormEffects(({ hasChanged }) => { onFormChange$().subscribe(formState => { const errorsChanged = hasChanged(formState, 'errors') if (errorsChanged) { setFieldState({ childrenErrors: parseChildrenErrors(formState.errors, path) }) } }) EffectHooks.onTabActiveKeyChange$().subscribe( ({ value, name, path }: any = {}) => { if(!itemsRef.current.map(item => item.key).includes(value))return matchUpdate(name, path, () => { setFieldState({ activeKey: value }) }) } ) }) return ( {itemsRef.current.map(({ props, schema, key }) => { const currentPath = FormPath.parse(path).concat(key) return ( ) })} ) } ) as any FormTab.TabPane = createControllerBox( 'tabpane', ({ children }) => { return {children} } ) Object.assign(FormTab, StateMap, EffectHooks) export default FormTab