PortalWrapper.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import { createVNode as _createVNode, resolveDirective as _resolveDirective } from "vue";
  2. import PropTypes from './vue-types';
  3. import Portal from './Portal';
  4. import { defineComponent, shallowRef, watch, onMounted, onBeforeUnmount, onUpdated, nextTick, computed } from 'vue';
  5. import canUseDom from './canUseDom';
  6. import raf from './raf';
  7. import { booleanType } from './type';
  8. import useScrollLocker from './hooks/useScrollLocker';
  9. let openCount = 0;
  10. const supportDom = canUseDom();
  11. /** @private Test usage only */
  12. export function getOpenCount() {
  13. return process.env.NODE_ENV === 'test' ? openCount : 0;
  14. }
  15. const getParent = getContainer => {
  16. if (!supportDom) {
  17. return null;
  18. }
  19. if (getContainer) {
  20. if (typeof getContainer === 'string') {
  21. return document.querySelectorAll(getContainer)[0];
  22. }
  23. if (typeof getContainer === 'function') {
  24. return getContainer();
  25. }
  26. if (typeof getContainer === 'object' && getContainer instanceof window.HTMLElement) {
  27. return getContainer;
  28. }
  29. }
  30. return document.body;
  31. };
  32. export default defineComponent({
  33. compatConfig: {
  34. MODE: 3
  35. },
  36. name: 'PortalWrapper',
  37. inheritAttrs: false,
  38. props: {
  39. wrapperClassName: String,
  40. forceRender: {
  41. type: Boolean,
  42. default: undefined
  43. },
  44. getContainer: PropTypes.any,
  45. visible: {
  46. type: Boolean,
  47. default: undefined
  48. },
  49. autoLock: booleanType(),
  50. didUpdate: Function
  51. },
  52. setup(props, _ref) {
  53. let {
  54. slots
  55. } = _ref;
  56. const container = shallowRef();
  57. const componentRef = shallowRef();
  58. const rafId = shallowRef();
  59. const triggerUpdate = shallowRef(1);
  60. const defaultContainer = canUseDom() && document.createElement('div');
  61. const removeCurrentContainer = () => {
  62. var _a, _b;
  63. // Portal will remove from `parentNode`.
  64. // Let's handle this again to avoid refactor issue.
  65. if (container.value === defaultContainer) {
  66. (_b = (_a = container.value) === null || _a === void 0 ? void 0 : _a.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(container.value);
  67. }
  68. container.value = null;
  69. };
  70. let parent = null;
  71. const attachToParent = function () {
  72. let force = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  73. if (force || container.value && !container.value.parentNode) {
  74. parent = getParent(props.getContainer);
  75. if (parent) {
  76. parent.appendChild(container.value);
  77. return true;
  78. }
  79. return false;
  80. }
  81. return true;
  82. };
  83. const getContainer = () => {
  84. if (!supportDom) {
  85. return null;
  86. }
  87. if (!container.value) {
  88. container.value = defaultContainer;
  89. attachToParent(true);
  90. }
  91. setWrapperClassName();
  92. return container.value;
  93. };
  94. const setWrapperClassName = () => {
  95. const {
  96. wrapperClassName
  97. } = props;
  98. if (container.value && wrapperClassName && wrapperClassName !== container.value.className) {
  99. container.value.className = wrapperClassName;
  100. }
  101. };
  102. onUpdated(() => {
  103. setWrapperClassName();
  104. attachToParent();
  105. });
  106. useScrollLocker(computed(() => {
  107. return props.autoLock && props.visible && canUseDom() && (container.value === document.body || container.value === defaultContainer);
  108. }));
  109. onMounted(() => {
  110. let init = false;
  111. watch([() => props.visible, () => props.getContainer], (_ref2, _ref3) => {
  112. let [visible, getContainer] = _ref2;
  113. let [prevVisible, prevGetContainer] = _ref3;
  114. // Update count
  115. if (supportDom) {
  116. parent = getParent(props.getContainer);
  117. if (parent === document.body) {
  118. if (visible && !prevVisible) {
  119. openCount += 1;
  120. } else if (init) {
  121. openCount -= 1;
  122. }
  123. }
  124. }
  125. if (init) {
  126. // Clean up container if needed
  127. const getContainerIsFunc = typeof getContainer === 'function' && typeof prevGetContainer === 'function';
  128. if (getContainerIsFunc ? getContainer.toString() !== prevGetContainer.toString() : getContainer !== prevGetContainer) {
  129. removeCurrentContainer();
  130. }
  131. }
  132. init = true;
  133. }, {
  134. immediate: true,
  135. flush: 'post'
  136. });
  137. nextTick(() => {
  138. if (!attachToParent()) {
  139. rafId.value = raf(() => {
  140. triggerUpdate.value += 1;
  141. });
  142. }
  143. });
  144. });
  145. onBeforeUnmount(() => {
  146. const {
  147. visible
  148. } = props;
  149. if (supportDom && parent === document.body) {
  150. // 离开时不会 render, 导到离开时数值不变,改用 func 。。
  151. openCount = visible && openCount ? openCount - 1 : openCount;
  152. }
  153. removeCurrentContainer();
  154. raf.cancel(rafId.value);
  155. });
  156. return () => {
  157. const {
  158. forceRender,
  159. visible
  160. } = props;
  161. let portal = null;
  162. const childProps = {
  163. getOpenCount: () => openCount,
  164. getContainer
  165. };
  166. if (triggerUpdate.value && (forceRender || visible || componentRef.value)) {
  167. portal = _createVNode(Portal, {
  168. "getContainer": getContainer,
  169. "ref": componentRef,
  170. "didUpdate": props.didUpdate
  171. }, {
  172. default: () => {
  173. var _a;
  174. return (_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots, childProps);
  175. }
  176. });
  177. }
  178. return portal;
  179. };
  180. }
  181. });