From 849ba7c078d14eacb283a69af363433c42fa7aba Mon Sep 17 00:00:00 2001 From: Janry Date: Sat, 24 Jul 2021 15:49:11 +0800 Subject: [PATCH] refactor(antd/core): refactor FormDialog/FormDrawer/Form.setValues/setInitialValues (#1876) --- .gitignore | 1 - .vscode/cspell.json | 19 ++ docs/guide/advanced/build.zh-CN.md | 2 +- packages/antd/docs/components/FormDialog.md | 112 +++++-- .../antd/docs/components/FormDialog.zh-CN.md | 273 +++++++++++------- packages/antd/docs/components/FormDrawer.md | 92 ++++-- .../antd/docs/components/FormDrawer.zh-CN.md | 82 ++++-- packages/antd/src/__builtins__/index.ts | 2 + packages/antd/src/__builtins__/loading.ts | 12 + packages/antd/src/__builtins__/portal.tsx | 62 ++++ packages/antd/src/form-dialog/index.tsx | 207 ++++++++----- packages/antd/src/form-drawer/index.tsx | 174 +++++------ packages/antd/src/form-item/index.tsx | 7 +- packages/core/src/__tests__/form.spec.ts | 31 ++ packages/core/src/models/Form.ts | 6 +- packages/next/docs/components/FormDialog.md | 49 ++-- .../next/docs/components/FormDialog.zh-CN.md | 180 +++++++----- packages/next/docs/components/FormDrawer.md | 45 +-- .../next/docs/components/FormDrawer.zh-CN.md | 64 ++-- packages/next/src/__builtins__/index.ts | 2 + packages/next/src/__builtins__/loading.ts | 11 + packages/next/src/__builtins__/portal.tsx | 62 ++++ packages/next/src/array-table/main.scss | 14 +- packages/next/src/form-dialog/index.tsx | 245 ++++++++++------ packages/next/src/form-drawer/index.tsx | 178 +++++++----- packages/next/src/form-item/index.tsx | 7 +- packages/next/src/form-item/main.scss | 1 + packages/shared/src/__tests__/index.spec.ts | 7 + packages/shared/src/merge.ts | 9 +- packages/shared/src/middleware.ts | 9 +- 30 files changed, 1306 insertions(+), 659 deletions(-) create mode 100644 .vscode/cspell.json create mode 100644 packages/antd/src/__builtins__/loading.ts create mode 100644 packages/antd/src/__builtins__/portal.tsx create mode 100644 packages/next/src/__builtins__/loading.ts create mode 100644 packages/next/src/__builtins__/portal.tsx diff --git a/.gitignore b/.gitignore index e0c858b6a80..6390c5cca47 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ coverage/ node_modules/ examples/test .idea/ -.vscode/ TODO.md tsconfig.tsbuildinfo package/ diff --git a/.vscode/cspell.json b/.vscode/cspell.json new file mode 100644 index 00000000000..a9906c7e8a9 --- /dev/null +++ b/.vscode/cspell.json @@ -0,0 +1,19 @@ +{ + "version": "0.1", + "language": "en", + "ignoreWords": [ + "autorun", + "Formily", + "formily", + "untracked", + "Unmount", + "antd", + "Antd", + "alifd", + "Mixins", + "builtins", + "cascader", + "Cascader", + "middlewares" + ] +} diff --git a/docs/guide/advanced/build.zh-CN.md b/docs/guide/advanced/build.zh-CN.md index 862a4168252..2bf6dd8f46b 100644 --- a/docs/guide/advanced/build.zh-CN.md +++ b/docs/guide/advanced/build.zh-CN.md @@ -136,7 +136,7 @@ yarn add babel-plugin-import --dev "import", { "libraryName": "@formily/antd", - "libraryDirectory": "esn", + "libraryDirectory": "esm", "style": true } ] diff --git a/packages/antd/docs/components/FormDialog.md b/packages/antd/docs/components/FormDialog.md index 2329aa09cb9..8f318149ef4 100644 --- a/packages/antd/docs/components/FormDialog.md +++ b/packages/antd/docs/components/FormDialog.md @@ -60,6 +60,27 @@ export default () => { ) }) + .forOpen((payload, next) => { + setTimeout(() => { + next({ + initialValues: { + aaa: '123', + }, + }) + }, 1000) + }) + .forConfirm((payload, next) => { + setTimeout(() => { + console.log(payload) + next(payload) + }, 1000) + }) + .forCancel((payload, next) => { + setTimeout(() => { + console.log(payload) + next(payload) + }, 1000) + }) .open({ initialValues: { aaa: '123', @@ -137,6 +158,27 @@ export default () => { ) }) + .forOpen((payload, next) => { + setTimeout(() => { + next({ + initialValues: { + aaa: '123', + }, + }) + }, 1000) + }) + .forConfirm((payload, next) => { + setTimeout(() => { + console.log(payload) + next(payload) + }, 1000) + }) + .forCancel((payload, next) => { + setTimeout(() => { + console.log(payload) + next(payload) + }, 1000) + }) .open({ initialValues: { aaa: '123', @@ -200,6 +242,27 @@ export default () => { ) }) + .forOpen((payload, next) => { + setTimeout(() => { + next({ + initialValues: { + aaa: '123', + }, + }) + }, 1000) + }) + .forConfirm((payload, next) => { + setTimeout(() => { + console.log(payload) + next(payload) + }, 1000) + }) + .forCancel((payload, next) => { + setTimeout(() => { + console.log(payload) + next(payload) + }, 1000) + }) .open({ initialValues: { aaa: '123', @@ -219,33 +282,36 @@ export default () => { ### FormDialog ```ts pure -import { IFormProps } from '@formily/core' +import { IFormProps, Form } from '@formily/core' + +type FormDialogRenderer = + | React.ReactElement + | ((form: Form) => React.ReactElement) -type FormDialogHandler = { - //Open the pop-up window and receive the form attributes, you can pass in initialValues/values/effects etc. +interface IFormDialog { + forOpen( + middleware: ( + props: IFormProps, + next: (props?: IFormProps) => Promise + ) => any + ): any //Middleware interceptor, can intercept Dialog to open + forConfirm( + middleware: (props: Form, next: (props?: Form) => Promise) => any + ): any //Middleware interceptor, which can intercept Dialog confirmation + forCancel( + middleware: (props: Form, next: (props?: Form) => Promise) => any + ): any //Middleware interceptor, can intercept Dialog to cancel + //Open the pop-up window to receive form attributes, you can pass in initialValues/values/effects etc. open(props: IFormProps): Promise //return form data //Close the pop-up window close(): void } -interface IFormDialog { - ( - title: React.ReactNode, //If it is ReactNode, it will be passed in as a pop-up window title - renderer: (resolve: () => void, reject: () => void) => React.ReactElement - ): FormDialogHandler - ( - title: IFormModalProps, //If it is an object, it is passed in as IFormModalProps - renderer: (resolve: () => void, reject: () => void) => React.ReactElement - ): FormDialogHandler -} -``` - -### IFormModalProps - -```ts pure -interface IFormModalProps extends ModalProps { - // If the return value is true, the dialog will not be closed after clicking Cancel or OK. At this time, you need to manually call FormDialogHandler.close() to close the dialog. - onCancel?: (e: React.MouseEvent) => boolean | void +interface FormDialog { + (title: ModalProps, id: string, renderer: FormDialogRenderer): IFormDialog + (title: ModalProps, id: FormDialogRenderer, renderer: unknown): IFormDialog + (title: ModalTitle, id: string, renderer: FormDialogRenderer): IFormDialog + (title: ModalTitle, id: FormDialogRenderer, renderer: unknown): IFormDialog } ``` @@ -254,3 +320,7 @@ interface IFormModalProps extends ModalProps { ### FormDialog.Footer No attributes, only child nodes are received + +### FormDialog.Portal + +Receive the optional id attribute, the default value is `form-dialog`, if there are multiple prefixCls in an application, and the prefixCls in the pop-up window of different regions are different, then it is recommended to specify the id as the region-level id diff --git a/packages/antd/docs/components/FormDialog.zh-CN.md b/packages/antd/docs/components/FormDialog.zh-CN.md index 961b712adee..603c2041d31 100644 --- a/packages/antd/docs/components/FormDialog.zh-CN.md +++ b/packages/antd/docs/components/FormDialog.zh-CN.md @@ -4,8 +4,15 @@ ## Markup Schema 案例 +以下例子演示了 FormDialog 的几个能力: + +- 快速打开,关闭能力 +- 中间件能力,自动出现加载态 +- 渲染函数内可以响应式能力 +- 上下文共享能力 + ```tsx -import React from 'react' +import React, { createContext, useContext } from 'react' import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd' import { createSchemaField } from '@formily/react' import { Button } from 'antd' @@ -17,59 +24,88 @@ const SchemaField = createSchemaField({ }, }) +const Context = createContext() + +const PortalId = '可以传,也可以不传的ID,默认是form-dialog' + export default () => { return ( - + + + + + ) } ``` @@ -125,28 +161,47 @@ const schema = { export default () => { return ( - + .forOpen((payload, next) => { + setTimeout(() => { + next({ + initialValues: { + aaa: '123', + }, + }) + }, 1000) + }) + .forConfirm((payload, next) => { + setTimeout(() => { + console.log(payload) + next(payload) + }, 1000) + }) + .forCancel((payload, next) => { + setTimeout(() => { + console.log(payload) + next(payload) + }, 1000) + }) + .open() + .then(console.log) + }} + > + 点我打开表单 + + ) } ``` @@ -200,11 +255,28 @@ export default () => { ) }) - .open({ - initialValues: { - aaa: '123', - }, + .forOpen((payload, next) => { + setTimeout(() => { + next({ + initialValues: { + aaa: '123', + }, + }) + }, 1000) }) + .forConfirm((payload, next) => { + setTimeout(() => { + console.log(payload) + next(payload) + }, 1000) + }) + .forCancel((payload, next) => { + setTimeout(() => { + console.log(payload) + next(payload) + }, 1000) + }) + .open() .then(console.log) }} > @@ -219,33 +291,36 @@ export default () => { ### FormDialog ```ts pure -import { IFormProps } from '@formily/core' +import { IFormProps, Form } from '@formily/core' + +type FormDialogRenderer = + | React.ReactElement + | ((form: Form) => React.ReactElement) -type FormDialogHandler = { +interface IFormDialog { + forOpen( + middleware: ( + props: IFormProps, + next: (props?: IFormProps) => Promise + ) => any + ): any //中间件拦截器,可以拦截Dialog打开 + forConfirm( + middleware: (props: Form, next: (props?: Form) => Promise) => any + ): any //中间件拦截器,可以拦截Dialog确认 + forCancel( + middleware: (props: Form, next: (props?: Form) => Promise) => any + ): any //中间件拦截器,可以拦截Dialog取消 //打开弹窗,接收表单属性,可以传入initialValues/values/effects etc. open(props: IFormProps): Promise //返回表单数据 //关闭弹窗 close(): void } -interface IFormDialog { - ( - title: React.ReactNode, //如果是ReactNode,则作为弹窗title传入 - renderer: (resolve: () => void, reject: () => void) => React.ReactElement - ): FormDialogHandler - ( - title: IFormModalProps, //如果是对象,则作为IFormModalProps传入 - renderer: (resolve: () => void, reject: () => void) => React.ReactElement - ): FormDialogHandler -} -``` - -### IFormModalProps - -```ts pure -interface IFormModalProps extends ModalProps { - // 如果返回值是true时,点取消或确定后不会关闭dialog,此时关闭dialog需要手动调用FormDialogHandler.close() - onCancel?: (e: React.MouseEvent) => boolean | void +interface FormDialog { + (title: ModalProps, id: string, renderer: FormDialogRenderer): IFormDialog + (title: ModalProps, id: FormDialogRenderer, renderer: unknown): IFormDialog + (title: ModalTitle, id: string, renderer: FormDialogRenderer): IFormDialog + (title: ModalTitle, id: FormDialogRenderer, renderer: unknown): IFormDialog } ``` @@ -254,3 +329,7 @@ interface IFormModalProps extends ModalProps { ### FormDialog.Footer 无属性,只接收子节点 + +### FormDialog.Portal + +接收可选的 id 属性,默认值为`form-dialog`,如果一个应用存在多个 prefixCls,不同区域的弹窗内部 prefixCls 不一样,那推荐指定 id 为区域级 id diff --git a/packages/antd/docs/components/FormDrawer.md b/packages/antd/docs/components/FormDrawer.md index 19967f173e5..001dca2f3e0 100644 --- a/packages/antd/docs/components/FormDrawer.md +++ b/packages/antd/docs/components/FormDrawer.md @@ -29,7 +29,7 @@ export default () => { return ( + + + + + ) } ``` @@ -297,33 +326,36 @@ export default () => { ### FormDialog ```ts pure -import { IFormProps } from '@formily/core' +import { IFormProps, Form } from '@formily/core' + +type FormDialogRenderer = + | React.ReactElement + | ((form: Form) => React.ReactElement) -type FormDialogHandler = { +interface IFormDialog { + forOpen( + middleware: ( + props: IFormProps, + next: (props?: IFormProps) => Promise + ) => any + ): any //中间件拦截器,可以拦截Dialog打开 + forConfirm( + middleware: (props: Form, next: (props?: Form) => Promise) => any + ): any //中间件拦截器,可以拦截Dialog确认 + forCancel( + middleware: (props: Form, next: (props?: Form) => Promise) => any + ): any //中间件拦截器,可以拦截Dialog取消 //打开弹窗,接收表单属性,可以传入initialValues/values/effects etc. open(props: IFormProps): Promise //返回表单数据 //关闭弹窗 close(): void } -interface IFormDialog { - ( - title: React.ReactNode, //如果是ReactNode,则作为弹窗title传入 - renderer: (resolve: () => void, reject: () => void) => React.ReactElement - ): FormDialogHandler - ( - title: IFormDialogProps, //如果是对象,则作为IFormDialogProps传入 - renderer: (resolve: () => void, reject: () => void) => React.ReactElement - ): FormDialogHandler -} -``` - -### IFormDialogProps - -```ts pure -interface IFormDialogProps extends DialogProps { - // 如果返回值是true时,点取消或确定后不会关闭dialog,如果需要关闭dialog需要手动调用FormDialogHandler.close() - onCancel?: (e: React.MouseEvent) => boolean | void +interface FormDialog { + (title: DialogProps, id: string, renderer: FormDialogRenderer): IFormDialog + (title: DialogProps, id: FormDialogRenderer, renderer: unknown): IFormDialog + (title: ModalTitle, id: string, renderer: FormDialogRenderer): IFormDialog + (title: ModalTitle, id: FormDialogRenderer, renderer: unknown): IFormDialog } ``` @@ -332,3 +364,7 @@ interface IFormDialogProps extends DialogProps { ### FormDialog.Footer 无属性,只接收子节点 + +### FormDialog.Portal + +接收可选的 id 属性,默认值为`form-dialog`,如果一个应用存在多个 prefixCls,不同区域的弹窗内部 prefixCls 不一样,那推荐指定 id 为区域级 id diff --git a/packages/next/docs/components/FormDrawer.md b/packages/next/docs/components/FormDrawer.md index 7a825f540de..5782a696434 100644 --- a/packages/next/docs/components/FormDrawer.md +++ b/packages/next/docs/components/FormDrawer.md @@ -332,38 +332,39 @@ export default () => { ### FormDrawer ```ts pure -import { IFormProps } from '@formily/core' +import { IFormProps, Form } from '@formily/core' -type FormDrawerHandler = { - //Open the pop-up window and receive the form attributes, you can pass in initialValues/values/effects etc. +type FormDrawerRenderer = + | React.ReactElement + | ((form: Form) => React.ReactElement) + +interface IFormDrawer { + forOpen( + middleware: ( + props: IFormProps, + next: (props?: IFormProps) => Promise + ) => any + ): any //Middleware interceptor, can intercept Drawer to open + //Open the pop-up window to receive form attributes, you can pass in initialValues/values/effects etc. open(props: IFormProps): Promise //return form data //Close the pop-up window close(): void } -interface IFormDrawer { - ( - title: React.ReactNode, //If it is ReactNode, it will be passed in as a pop-up window title - renderer: (resolve: () => void, reject: () => void) => React.ReactElement - ): FormDrawerHandler - ( - title: IFormDrawerProps, //If it is an object, it is passed in as IFormDrawerProps - renderer: (resolve: () => void, reject: () => void) => React.ReactElement - ): FormDrawerHandler -} -``` - -### IFormDrawerProps - -```ts pure -interface IFormDrawerProps extends DrawerProps { - // If the return value is true, the drawer will not be closed after clicking Cancel or OK. If you need to close the drawer, you need to manually call FormDrawerHandler.close() - onClose?: (reason: string, e: React.MouseEvent) => boolean | void +interface FormDrawer { + (title: DrawerProps, id: string, renderer: FormDrawerRenderer): IFormDrawer + (title: DrawerProps, id: FormDrawerRenderer, renderer: unknown): IFormDrawer + (title: ModalTitle, id: string, renderer: FormDrawerRenderer): IFormDrawer + (title: ModalTitle, id: FormDrawerRenderer, renderer: unknown): IFormDrawer } ``` -`DrawerProps` type definition reference fusion [Drawer API](https://fusion.design/pc/component/drawer?themeid=2#API) +`DrawerProps` type definition reference ant design [Drawer API](https://fusion.design/pc/component/drawer?themeid=2#API) ### FormDrawer.Footer No attributes, only child nodes are received + +### FormDrawer.Portal + +Receive an optional id attribute, the default value is `form-drawer`, if there are multiple prefixCls in an application, and the prefixCls in the pop-up window of different regions are different, then it is recommended to specify the id as the region-level id diff --git a/packages/next/docs/components/FormDrawer.zh-CN.md b/packages/next/docs/components/FormDrawer.zh-CN.md index 219d8236099..df1b187db61 100644 --- a/packages/next/docs/components/FormDrawer.zh-CN.md +++ b/packages/next/docs/components/FormDrawer.zh-CN.md @@ -29,9 +29,9 @@ export default () => { return ( + + + } + onClose={(trigger, e) => { + modal?.onClose?.(trigger, e) + reject() + }} + > + + + + + )} + + ) } - document.body.appendChild(env.root) + document.body.appendChild(env.host) const formDialog = { - open: (props: IFormProps) => { + forOpen: (middleware: IMiddleware) => { + if (isFn(middleware)) { + env.openMiddlewares.push(middleware) + } + return formDialog + }, + forConfirm: (middleware: IMiddleware) => { + if (isFn(middleware)) { + env.confirmMiddlewares.push(middleware) + } + return formDialog + }, + forCancel: (middleware: IMiddleware) => { + if (isFn(middleware)) { + env.cancelMiddlewares.push(middleware) + } + return formDialog + }, + open: async (props: IFormProps) => { if (env.promise) return env.promise - env.form = env.form || createForm(props) - env.promise = new Promise((resolve) => { - render( - true, - () => { - env.form.submit((values: any) => { - resolve(values) + env.promise = new Promise(async (resolve, reject) => { + try { + props = await loading(() => + applyMiddleware(props, env.openMiddlewares) + ) + env.form = env.form || createForm(props) + } catch (e) { + reject(e) + } + root.render(() => + renderDialog( + true, + () => { + env.form + .submit(async () => { + await applyMiddleware(env.form, env.confirmMiddlewares) + resolve(toJS(env.form.values)) + formDialog.close() + }) + .catch(() => {}) + }, + async () => { + await loading(() => + applyMiddleware(env.form, env.cancelMiddlewares) + ) formDialog.close() - }) - }, - () => { - formDialog.close() - } + } + ) ) }) return env.promise }, close: () => { - if (!env.root) return - render(false) + if (!env.host) return + root.render(() => renderDialog(false)) }, } return formDialog @@ -179,4 +248,6 @@ const DialogFooter: React.FC = (props) => { FormDialog.Footer = DialogFooter +FormDialog.Portal = createPortalProvider('form-dialog') + export default FormDialog diff --git a/packages/next/src/form-drawer/index.tsx b/packages/next/src/form-drawer/index.tsx index 55d35394ecc..763e015fc7e 100644 --- a/packages/next/src/form-drawer/index.tsx +++ b/packages/next/src/form-drawer/index.tsx @@ -1,29 +1,45 @@ import React, { Fragment, useLayoutEffect, useRef, useState } from 'react' -import ReactDOM, { createPortal } from 'react-dom' -import { createForm, IFormProps } from '@formily/core' -import { FormProvider } from '@formily/react' -import { isNum, isStr, isBool, isFn } from '@formily/shared' +import { createPortal } from 'react-dom' +import { + createForm, + onFormSubmitSuccess, + IFormProps, + Form, +} from '@formily/core' +import { toJS } from '@formily/reactive' +import { FormProvider, observer, Observer } from '@formily/react' +import { + isNum, + isStr, + isBool, + isFn, + applyMiddleware, + IMiddleware, +} from '@formily/shared' import { ConfigProvider, Drawer } from '@alifd/next' import { DrawerProps } from '@alifd/next/lib/drawer' -import { usePrefixCls } from '../__builtins__' +import { + usePrefixCls, + loading, + createPortalProvider, + createPortalRoot, +} from '../__builtins__' -type FormDrawerContent = +type FormDrawerRenderer = | React.ReactElement - | ((resolve: () => any, reject: () => any) => React.ReactElement) + | ((form: Form) => React.ReactElement) type DrawerTitle = string | number | React.ReactElement +const getContext: () => any = ConfigProvider['getContext'] + const isDrawerTitle = (props: any): props is DrawerTitle => { return ( isNum(props) || isStr(props) || isBool(props) || React.isValidElement(props) ) } -interface IFormDrawerProps extends DrawerProps { - onClose?: (reason: string, e: React.MouseEvent) => boolean | void -} - -const getDrawerProps = (props: any): IFormDrawerProps => { +const getDrawerProps = (props: any): DrawerProps => { if (isDrawerTitle(props)) { return { title: props, @@ -38,95 +54,109 @@ export interface IFormDrawer { close(): void } -export interface IFormDrawerComponentProps { - content: FormDrawerContent - resolve: () => any - reject: () => any -} - export function FormDrawer( - title: IFormDrawerProps, - content: FormDrawerContent + title: DrawerProps, + id: string, + renderer: FormDrawerRenderer +): IFormDrawer +export function FormDrawer( + title: DrawerProps, + id: FormDrawerRenderer, + renderer: unknown +): IFormDrawer +export function FormDrawer( + title: DrawerTitle, + id: string, + renderer: FormDrawerRenderer ): IFormDrawer export function FormDrawer( title: DrawerTitle, - content: FormDrawerContent + id: FormDrawerRenderer, + renderer: unknown ): IFormDrawer -export function FormDrawer(title: any, content: any): IFormDrawer { +export function FormDrawer(title: any, id: any, renderer: any): IFormDrawer { + if (isFn(id) || React.isValidElement(id)) { + renderer = id + id = 'form-drawer' + } const env = { - root: document.createElement('div'), + host: document.createElement('div'), form: null, promise: null, + openMiddlewares: [], } - - let contextProps = {} - try { - contextProps = (ConfigProvider as any).getContext() - } catch (e) {} - + const root = createPortalRoot(env.host, id) const props = getDrawerProps(title) - const drawer: IFormDrawerProps = { + const drawer: DrawerProps = { width: '40%', ...props, onClose: (reason: string, e: any) => { - const closeable = !props?.onClose?.(reason, e) - closeable && formDrawer.close() + props?.onClose?.(reason, e) + formDrawer.close() }, afterClose() { - ReactDOM.unmountComponentAtNode(env.root) - env.root?.parentNode?.removeChild(env.root) - env.root = undefined + props?.afterClose?.() + root.unmount() }, } - const component = (props: IFormDrawerComponentProps) => { + const DrawerContent = observer(() => { + return {isFn(renderer) ? renderer(env.form) : renderer} + }) + const renderDrawer = (visible = true) => { return ( - - {isFn(props.content) - ? props.content(props.resolve, props.reject) - : props.content} - - ) - } - const render = (visible = true, resolve?: () => any, reject?: () => any) => { - ReactDOM.render( - - - - {React.createElement(component, { - content, - resolve, - reject, - })} - - - , - env.root + + + {() => ( + + + + + + )} + + ) } - document.body.appendChild(env.root) + document.body.appendChild(env.host) const formDrawer = { + forOpen: (middleware: IMiddleware) => { + if (isFn(middleware)) { + env.openMiddlewares.push(middleware) + } + return formDrawer + }, open: (props: IFormProps) => { if (env.promise) return env.promise - env.form = env.form || createForm(props) - env.promise = new Promise((resolve) => { - render( - true, - () => { - env.form.submit((values: any) => { - resolve(values) - formDrawer.close() + env.promise = new Promise(async (resolve, reject) => { + try { + props = await loading(() => + applyMiddleware(props, env.openMiddlewares) + ) + env.form = + env.form || + createForm({ + ...props, + effects(form) { + onFormSubmitSuccess(() => { + resolve(toJS(form.values)) + formDrawer.close() + }) + props?.effects?.(form) + }, }) - }, - () => { - formDrawer.close() - } - ) + } catch (e) { + reject(e) + } + root.render(() => renderDrawer(false)) + setTimeout(() => { + root.render(() => renderDrawer(true)) + }, 16) }) return env.promise }, close: () => { - if (!env.root) return - render(false) + if (!env.host) return + root.render(() => renderDrawer(false)) }, } return formDrawer @@ -166,4 +196,6 @@ const DrawerFooter: React.FC = (props) => { FormDrawer.Footer = DrawerFooter +FormDrawer.Portal = createPortalProvider('form-drawer') + export default FormDrawer diff --git a/packages/next/src/form-item/index.tsx b/packages/next/src/form-item/index.tsx index f0cd04001bd..780bf1cd9f2 100644 --- a/packages/next/src/form-item/index.tsx +++ b/packages/next/src/form-item/index.tsx @@ -89,10 +89,9 @@ function useOverflow< useLayoutEffect(() => { if (containerRef.current && contentRef.current) { - if ( - contentRef.current.getBoundingClientRect().width > - containerRef.current.getBoundingClientRect().width - ) { + const contentWidth = contentRef.current.getBoundingClientRect().width + const containerWidth = containerRef.current.getBoundingClientRect().width + if (contentWidth && containerWidth && containerWidth < contentWidth) { if (!overflow) setOverflow(true) } else { if (overflow) setOverflow(false) diff --git a/packages/next/src/form-item/main.scss b/packages/next/src/form-item/main.scss index 8e89abe67c1..f0584147bd4 100644 --- a/packages/next/src/form-item/main.scss +++ b/packages/next/src/form-item/main.scss @@ -128,6 +128,7 @@ .#{$form-item-cls}-control { flex: 1; max-width: 100%; + .#{$form-item-cls}-control-content { display: flex; diff --git a/packages/shared/src/__tests__/index.spec.ts b/packages/shared/src/__tests__/index.spec.ts index b4809098e04..fb436b6eab0 100644 --- a/packages/shared/src/__tests__/index.spec.ts +++ b/packages/shared/src/__tests__/index.spec.ts @@ -708,6 +708,13 @@ test('applyMiddleware', async () => { (num: number, next) => next(num + 1), ]) ).toEqual(3) + expect( + await applyMiddleware(0, [ + (num: number, next) => next(), + (num: number, next) => next(num + 1), + (num: number, next) => next(num + 1), + ]) + ).toEqual(2) const resolved = jest.fn() applyMiddleware(0, [ (num: number, next) => next(num + 1), diff --git a/packages/shared/src/merge.ts b/packages/shared/src/merge.ts index 7e7219b7d10..1049a6a8149 100644 --- a/packages/shared/src/merge.ts +++ b/packages/shared/src/merge.ts @@ -125,15 +125,10 @@ interface Options { options?: Options ) => ((x: any, y: any) => any) | undefined isMergeableObject?(value: object): boolean + cloneUnlessOtherwiseSpecified?: (value: any, options: Options) => any } -function deepmerge(x: Partial, y: Partial, options?: Options): T -function deepmerge( - x: Partial, - y: Partial, - options?: Options -): T1 & T2 -function deepmerge(target: any, source: any, options: any) { +function deepmerge(target: any, source: any, options?: Options) { options = options || {} options.arrayMerge = options.arrayMerge || defaultArrayMerge options.isMergeableObject = diff --git a/packages/shared/src/middleware.ts b/packages/shared/src/middleware.ts index 893c68f065b..44d1cc68b23 100644 --- a/packages/shared/src/middleware.ts +++ b/packages/shared/src/middleware.ts @@ -1,11 +1,14 @@ -export interface IMiddleware { - (payload: any, next: (payload: any) => any): any +export interface IMiddleware { + (payload: Payload, next: (payload?: Payload) => Result): Result } export const applyMiddleware = (payload: any, fns: IMiddleware[] = []) => { const compose = (payload: any, fns: IMiddleware[]) => { + const prevPayload = payload return Promise.resolve( - fns[0](payload, (payload) => compose(payload, fns.slice(1))) + fns[0](payload, (payload) => + compose(payload ?? prevPayload, fns.slice(1)) + ) ) } return new Promise((resolve) => {