use-modal.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import type { ExtendedModalApi, ModalApiOptions, ModalProps } from './modal';
  2. import {
  3. defineComponent,
  4. h,
  5. inject,
  6. nextTick,
  7. provide,
  8. reactive,
  9. ref,
  10. } from 'vue';
  11. import { useStore } from '@vben-core/shared/store';
  12. import VbenModal from './modal.vue';
  13. import { ModalApi } from './modal-api';
  14. const USER_MODAL_INJECT_KEY = Symbol('VBEN_MODAL_INJECT');
  15. export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
  16. options: ModalApiOptions = {},
  17. ) {
  18. // Modal一般会抽离出来,所以如果有传入 connectedComponent,则表示为外部调用,与内部组件进行连接
  19. // 外部的Modal通过provide/inject传递api
  20. const { connectedComponent } = options;
  21. if (connectedComponent) {
  22. const extendedApi = reactive({});
  23. const isModalReady = ref(true);
  24. const Modal = defineComponent(
  25. (props: TParentModalProps, { attrs, slots }) => {
  26. provide(USER_MODAL_INJECT_KEY, {
  27. extendApi(api: ExtendedModalApi) {
  28. // 不能直接给 reactive 赋值,会丢失响应
  29. // 不能用 Object.assign,会丢失 api 的原型函数
  30. Object.setPrototypeOf(extendedApi, api);
  31. },
  32. options,
  33. async reCreateModal() {
  34. isModalReady.value = false;
  35. await nextTick();
  36. isModalReady.value = true;
  37. },
  38. });
  39. checkProps(extendedApi as ExtendedModalApi, {
  40. ...props,
  41. ...attrs,
  42. ...slots,
  43. });
  44. return () =>
  45. h(
  46. isModalReady.value ? connectedComponent : 'div',
  47. {
  48. ...props,
  49. ...attrs,
  50. },
  51. slots,
  52. );
  53. },
  54. {
  55. inheritAttrs: false,
  56. name: 'VbenParentModal',
  57. },
  58. );
  59. return [Modal, extendedApi as ExtendedModalApi] as const;
  60. }
  61. const injectData = inject<any>(USER_MODAL_INJECT_KEY, {});
  62. const mergedOptions = {
  63. ...injectData.options,
  64. ...options,
  65. } as ModalApiOptions;
  66. mergedOptions.onOpenChange = (isOpen: boolean) => {
  67. options.onOpenChange?.(isOpen);
  68. injectData.options?.onOpenChange?.(isOpen);
  69. };
  70. mergedOptions.onClosed = () => {
  71. options.onClosed?.();
  72. if (options.destroyOnClose) {
  73. injectData.reCreateModal?.();
  74. }
  75. };
  76. const api = new ModalApi(mergedOptions);
  77. const extendedApi: ExtendedModalApi = api as never;
  78. extendedApi.useStore = (selector) => {
  79. return useStore(api.store, selector);
  80. };
  81. const Modal = defineComponent(
  82. (props: ModalProps, { attrs, slots }) => {
  83. return () =>
  84. h(
  85. VbenModal,
  86. {
  87. ...props,
  88. ...attrs,
  89. modalApi: extendedApi,
  90. },
  91. slots,
  92. );
  93. },
  94. {
  95. inheritAttrs: false,
  96. name: 'VbenModal',
  97. },
  98. );
  99. injectData.extendApi?.(extendedApi);
  100. return [Modal, extendedApi] as const;
  101. }
  102. async function checkProps(api: ExtendedModalApi, attrs: Record<string, any>) {
  103. if (!attrs || Object.keys(attrs).length === 0) {
  104. return;
  105. }
  106. await nextTick();
  107. const state = api?.store?.state;
  108. if (!state) {
  109. return;
  110. }
  111. const stateKeys = new Set(Object.keys(state));
  112. for (const attr of Object.keys(attrs)) {
  113. if (stateKeys.has(attr) && !['class'].includes(attr)) {
  114. // connectedComponent存在时,不要传入Modal的props,会造成复杂度提升,如果你需要修改Modal的props,请使用 useModal 或者api
  115. console.warn(
  116. `[Vben Modal]: When 'connectedComponent' exists, do not set props or slots '${attr}', which will increase complexity. If you need to modify the props of Modal, please use useVbenModal or api.`,
  117. );
  118. }
  119. }
  120. }