From 150f8a7ab541dcbd99d4e31b25f08ce601dfd1aa Mon Sep 17 00:00:00 2001 From: CCherry07 <2405693142@qq.com> Date: Sat, 29 Apr 2023 15:31:29 +0800 Subject: [PATCH 1/5] feat(modal): add useModal hook --- components/modal/Modal.tsx | 3 +- components/modal/confirm.tsx | 3 +- components/modal/destroyFns.ts | 2 + components/modal/index.tsx | 9 +- components/modal/useModal/HookModal.tsx | 73 ++++++++++++ components/modal/useModal/index.tsx | 140 ++++++++++++++++++++++++ 6 files changed, 224 insertions(+), 6 deletions(-) create mode 100644 components/modal/destroyFns.ts create mode 100644 components/modal/useModal/HookModal.tsx create mode 100644 components/modal/useModal/index.tsx diff --git a/components/modal/Modal.tsx b/components/modal/Modal.tsx index 695a63dd16..e469f0e1e5 100644 --- a/components/modal/Modal.tsx +++ b/components/modal/Modal.tsx @@ -149,8 +149,6 @@ export interface ModalLocale { justOkText: string; } -export const destroyFns = []; - export default defineComponent({ compatConfig: { MODE: 3 }, name: 'AModal', @@ -167,6 +165,7 @@ export default defineComponent({ props, ); const [wrapSSR, hashId] = useStyle(prefixCls); + warning( props.visible === undefined, 'Modal', diff --git a/components/modal/confirm.tsx b/components/modal/confirm.tsx index 77c4ff4808..1ed23dbbda 100644 --- a/components/modal/confirm.tsx +++ b/components/modal/confirm.tsx @@ -1,13 +1,14 @@ import { createVNode, render as vueRender } from 'vue'; import ConfirmDialog from './ConfirmDialog'; import type { ModalFuncProps } from './Modal'; -import { destroyFns } from './Modal'; import ConfigProvider, { globalConfigForApi } from '../config-provider'; import omit from '../_util/omit'; import { getConfirmLocale } from './locale'; +import destroyFns from './destroyFns'; type ConfigUpdate = ModalFuncProps | ((prevConfig: ModalFuncProps) => ModalFuncProps); +export type ModalStaticFunctions = Record, ModalFunc>; export type ModalFunc = (props: ModalFuncProps) => { destroy: () => void; diff --git a/components/modal/destroyFns.ts b/components/modal/destroyFns.ts new file mode 100644 index 0000000000..eaf13e4939 --- /dev/null +++ b/components/modal/destroyFns.ts @@ -0,0 +1,2 @@ +const destroyFns: Array = []; +export default destroyFns; diff --git a/components/modal/index.tsx b/components/modal/index.tsx index 05f20e3282..ccb3986d5e 100644 --- a/components/modal/index.tsx +++ b/components/modal/index.tsx @@ -1,15 +1,16 @@ import type { App, Plugin } from 'vue'; import type { ModalFunc, ModalFuncProps } from './Modal'; -import Modal, { destroyFns } from './Modal'; +import Modal from './Modal'; import confirm, { withWarn, withInfo, withSuccess, withError, withConfirm } from './confirm'; - +import useModal from './useModal'; +import destroyFns from './destroyFns'; export type { ActionButtonProps } from '../_util/ActionButton'; export type { ModalProps, ModalFuncProps } from './Modal'; function modalWarn(props: ModalFuncProps) { return confirm(withWarn(props)); } - +Modal.useModal = useModal; Modal.info = function infoFn(props: ModalFuncProps) { return confirm(withInfo(props)); }; @@ -60,4 +61,6 @@ export default Modal as typeof Modal & readonly confirm: ModalFunc; readonly destroyAll: () => void; + + readonly useModal: typeof useModal; }; diff --git a/components/modal/useModal/HookModal.tsx b/components/modal/useModal/HookModal.tsx new file mode 100644 index 0000000000..b54ca717e9 --- /dev/null +++ b/components/modal/useModal/HookModal.tsx @@ -0,0 +1,73 @@ +import type { PropType } from 'vue'; +import { computed, defineComponent } from 'vue'; +import { useConfigContextInject } from '../../config-provider/context'; +import { useLocaleReceiver } from '../../locale-provider/LocaleReceiver'; +import defaultLocale from '../../locale/en_US'; +import ConfirmDialog from '../ConfirmDialog'; +import type { ModalFuncProps } from '../Modal'; +import initDefaultProps from '../../_util/props-util/initDefaultProps'; +export interface HookModalProps { + afterClose: () => void; + config: ModalFuncProps; + destroyAction: (...args: any[]) => void; + open: boolean; +} + +export interface HookModalRef { + destroy: () => void; + update: (config: ModalFuncProps) => void; +} + +const comfirmFuncProps = () => ({ + config: Object as PropType, + afterClose: Function as PropType<() => void>, + destroyAction: Function as PropType<(e: any) => void>, + open: Boolean, +}); + +export default defineComponent({ + name: 'HookModal', + inheritAttrs: false, + props: initDefaultProps(comfirmFuncProps(), { + config: { + width: 520, + okType: 'primary', + }, + }), + setup(props: HookModalProps, { expose }) { + const open = computed(() => props.open); + const innerConfig = computed(() => props.config); + const { direction, getPrefixCls } = useConfigContextInject(); + const prefixCls = getPrefixCls('modal'); + const rootPrefixCls = getPrefixCls(); + + const afterClose = () => { + props?.afterClose(); + innerConfig.value.afterClose?.(); + }; + + const close = (...args: any[]) => { + props.destroyAction(...args); + }; + + expose({ destroy: close }); + const mergedOkCancel = innerConfig.value.okCancel ?? innerConfig.value.type === 'confirm'; + const [contextLocale] = useLocaleReceiver('Modal', defaultLocale.Modal); + return () => ( + + ); + }, +}); diff --git a/components/modal/useModal/index.tsx b/components/modal/useModal/index.tsx new file mode 100644 index 0000000000..49ea853a77 --- /dev/null +++ b/components/modal/useModal/index.tsx @@ -0,0 +1,140 @@ +import type { ComputedRef } from 'vue'; +import { unref, computed, defineComponent, ref, watch } from 'vue'; +import type { VueNode } from '../../_util/type'; +import type { ModalFuncProps } from '../Modal'; +import type { HookModalRef } from './HookModal'; +import type { ModalStaticFunctions } from '../confirm'; +import { withConfirm, withError, withInfo, withSuccess, withWarn } from '../confirm'; + +import HookModal from './HookModal'; +import destroyFns from '../destroyFns'; + +let uuid = 0; + +interface ElementsHolderRef { + addModal: (modal: ComputedRef) => () => void; +} + +const ElementsHolder = defineComponent({ + name: 'ElementsHolder', + inheritAttrs: false, + setup(_, { expose }) { + const modals = ref[]>([]); + const addModal = (modal: ComputedRef) => { + modals.value.push(modal); + return () => { + modals.value = modals.value.filter(currentModal => currentModal !== modal); + }; + }; + + expose({ addModal }); + return () => { + return <>{modals.value.map(modal => modal.value)}; + }; + }, +}); + +function useModal(): readonly [Omit, () => VueNode] { + const holderRef = ref(null); + // ========================== Effect ========================== + const actionQueue = ref([]); + watch( + actionQueue, + () => { + if (actionQueue.value.length) { + const cloneQueue = [...actionQueue.value]; + cloneQueue.forEach(action => { + action(); + }); + actionQueue.value = []; + } + }, + { + immediate: true, + }, + ); + + // =========================== Hook =========================== + const getConfirmFunc = (withFunc: (config: ModalFuncProps) => ModalFuncProps) => + function hookConfirm(config: ModalFuncProps) { + uuid += 1; + const open = ref(true); + const modalRef = ref(null); + const configRef = ref(unref(config)); + const updateConfig = ref({}); + watch(config, val => { + updateAction({ + ...val, + ...updateConfig.value, + }); + }); + // eslint-disable-next-line prefer-const + let closeFunc: Function | undefined; + const modal = computed(() => ( + { + closeFunc?.(); + }} + /> + )); + + closeFunc = holderRef.value?.addModal(modal); + + if (closeFunc) { + destroyFns.push(closeFunc); + } + + const destroyAction = (...args: any[]) => { + open.value = false; + const triggerCancel = args.some(param => param && param.triggerCancel); + if (configRef.value.onCancel && triggerCancel) { + configRef.value.onCancel(() => {}, ...args.slice(1)); + } + }; + + const updateAction = (newConfig: ModalFuncProps) => { + configRef.value = { + ...configRef.value, + ...newConfig, + }; + }; + + const destroy = () => { + if (modalRef.value) { + destroyAction(); + } else { + actionQueue.value = [...actionQueue.value, destroyAction]; + } + }; + + const update = (newConfig: ModalFuncProps) => { + updateConfig.value = newConfig; + if (modalRef.value) { + updateAction(newConfig); + } else { + actionQueue.value = [...actionQueue.value, () => updateAction(newConfig)]; + } + }; + return { + destroy, + update, + }; + }; + + const fns = computed(() => ({ + info: getConfirmFunc(withInfo), + success: getConfirmFunc(withSuccess), + error: getConfirmFunc(withError), + warning: getConfirmFunc(withWarn), + confirm: getConfirmFunc(withConfirm), + })); + + return [fns.value, () => ] as const; +} + +export default useModal; From ccb896d0027f78b7689b1e7a71eb8bc6c29fa900 Mon Sep 17 00:00:00 2001 From: CCherry07 <2405693142@qq.com> Date: Sat, 29 Apr 2023 16:25:45 +0800 Subject: [PATCH 2/5] feat(modal): add HookModal demo --- components/modal/demo/HookModal.vue | 101 ++++++++++++++++++++++++++++ components/modal/demo/index.vue | 3 + 2 files changed, 104 insertions(+) create mode 100644 components/modal/demo/HookModal.vue diff --git a/components/modal/demo/HookModal.vue b/components/modal/demo/HookModal.vue new file mode 100644 index 0000000000..c9580fb0d7 --- /dev/null +++ b/components/modal/demo/HookModal.vue @@ -0,0 +1,101 @@ + +--- +order: 12 +title: + zh-CN: 使用useModal获取上下文 + en-US: Use useModal to get context +--- + +## zh-CN + +通过 `Modal.useModal` 创建支持读取 context 的 `contextHolder`。 + +## en-US + +Use `Modal.useModal` to get `contextHolder` with context accessible issue. + + + + + + diff --git a/components/modal/demo/index.vue b/components/modal/demo/index.vue index 7b5bc75c9a..213570f2cc 100644 --- a/components/modal/demo/index.vue +++ b/components/modal/demo/index.vue @@ -5,6 +5,7 @@ + @@ -31,6 +32,7 @@ import Width from './width.vue'; import Fullscreen from './fullscreen.vue'; import ButtonProps from './button-props.vue'; import modalRenderVue from './modal-render.vue'; +import HookModal from './HookModal.vue'; import CN from '../index.zh-CN.md'; import US from '../index.en-US.md'; import { defineComponent } from 'vue'; @@ -52,6 +54,7 @@ export default defineComponent({ ButtonProps, Fullscreen, modalRenderVue, + HookModal, }, setup() { return {}; From 28acd12284714812a64da45b0ed2fbc8c1b3f44b Mon Sep 17 00:00:00 2001 From: CCherry07 <2405693142@qq.com> Date: Sat, 29 Apr 2023 16:26:32 +0800 Subject: [PATCH 3/5] test(modal): update HookModal demo snap --- .../modal/__tests__/__snapshots__/demo.test.js.snap | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/components/modal/__tests__/__snapshots__/demo.test.js.snap b/components/modal/__tests__/__snapshots__/demo.test.js.snap index 2ded29922d..d96f05e0e1 100644 --- a/components/modal/__tests__/__snapshots__/demo.test.js.snap +++ b/components/modal/__tests__/__snapshots__/demo.test.js.snap @@ -1,5 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`renders ./components/modal/demo/HookModal.vue correctly 1`] = ` +
+`; + exports[`renders ./components/modal/demo/async.vue correctly 1`] = `