ant-design-vue/components/_util/PortalWrapper.tsx
Zev Zhu 53dc5daaaf
feat: add static-style-extract (#6713)
* docs: add ant-design-vue nuxt module

* feat: add static-style-extract

* docs: fix import defineProps and defineEmits

* feat: add static-style extract test

* fix: add ignore component

* feat:  calendar select support info.source param (#6697)

* docs: add ant-design-vue nuxt module

* feat: calendar select support info.source param

* docs: synchronous config-provider demo (#6706)

* revert: #6706

* feat: node error

* fix: Handle the output of styles in Server Side Rendering mode

* fix: fix value required error

* chore: change tests dirname

* fix: auchor warning

* chore: add tests

* chore: generate tests snap

* docs: add ssr docs

* docs: synchronous config-provider demo (#6706)

* revert: #6706

* docs: add extract ssr doc

* fix: Removing canUseDom does not have any effect.

* fix: remove range picker in picker map

* fix: watch source can only be a getter/effect function style warning

* fix: styleContext props maybe ref

* fix: remove black list component

* feat: add compChildNameMap

---------

Co-authored-by: selicens <1244620067@qq.com>
Co-authored-by: tangjinzhou <415800467@qq.com>
2023-08-06 12:28:22 +08:00

194 lines
5.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import PropTypes from './vue-types';
import Portal from './Portal';
import {
defineComponent,
shallowRef,
watch,
onMounted,
onBeforeUnmount,
onUpdated,
getCurrentInstance,
nextTick,
computed,
} from 'vue';
import canUseDom from './canUseDom';
import raf from './raf';
import { booleanType } from './type';
import useScrollLocker from './hooks/useScrollLocker';
let openCount = 0;
const supportDom = canUseDom();
/** @private Test usage only */
export function getOpenCount() {
return process.env.NODE_ENV === 'test' ? openCount : 0;
}
const getParent = (getContainer: GetContainer) => {
if (!supportDom) {
return null;
}
if (getContainer) {
if (typeof getContainer === 'string') {
return document.querySelectorAll(getContainer)[0] as HTMLElement;
}
if (typeof getContainer === 'function') {
return getContainer();
}
if (typeof getContainer === 'object' && getContainer instanceof window.HTMLElement) {
return getContainer;
}
}
return document.body;
};
export type GetContainer = string | HTMLElement | (() => HTMLElement);
export default defineComponent({
compatConfig: { MODE: 3 },
name: 'PortalWrapper',
inheritAttrs: false,
props: {
wrapperClassName: String,
forceRender: { type: Boolean, default: undefined },
getContainer: PropTypes.any,
visible: { type: Boolean, default: undefined },
autoLock: booleanType(),
didUpdate: Function,
},
setup(props, { slots }) {
const container = shallowRef<HTMLElement>();
const componentRef = shallowRef();
const rafId = shallowRef<number>();
const removeCurrentContainer = () => {
// Portal will remove from `parentNode`.
// Let's handle this again to avoid refactor issue.
container.value?.parentNode?.removeChild(container.value);
container.value = null;
};
let parent: HTMLElement = null;
const attachToParent = (force = false) => {
if (force || (container.value && !container.value.parentNode)) {
parent = getParent(props.getContainer);
if (parent) {
parent.appendChild(container.value);
return true;
}
return false;
}
return true;
};
// attachToParent();
const defaultContainer = canUseDom() && document.createElement('div');
const getContainer = () => {
if (!supportDom) {
return null;
}
if (!container.value) {
container.value = defaultContainer;
attachToParent(true);
}
setWrapperClassName();
return container.value;
};
const setWrapperClassName = () => {
const { wrapperClassName } = props;
if (container.value && wrapperClassName && wrapperClassName !== container.value.className) {
container.value.className = wrapperClassName;
}
};
onUpdated(() => {
setWrapperClassName();
attachToParent();
});
const instance = getCurrentInstance();
useScrollLocker(
computed(() => {
return (
props.autoLock &&
props.visible &&
canUseDom() &&
(container.value === document.body || container.value === defaultContainer)
);
}),
);
onMounted(() => {
let init = false;
watch(
[() => props.visible, () => props.getContainer],
([visible, getContainer], [prevVisible, prevGetContainer]) => {
// Update count
if (supportDom) {
parent = getParent(props.getContainer);
if (parent === document.body) {
if (visible && !prevVisible) {
openCount += 1;
} else if (init) {
openCount -= 1;
}
}
}
if (init) {
// Clean up container if needed
const getContainerIsFunc =
typeof getContainer === 'function' && typeof prevGetContainer === 'function';
if (
getContainerIsFunc
? getContainer.toString() !== prevGetContainer.toString()
: getContainer !== prevGetContainer
) {
removeCurrentContainer();
}
}
init = true;
},
{ immediate: true, flush: 'post' },
);
nextTick(() => {
if (!attachToParent()) {
rafId.value = raf(() => {
instance.update();
});
}
});
});
onBeforeUnmount(() => {
const { visible } = props;
if (supportDom && parent === document.body) {
// 离开时不会 render 导到离开时数值不变,改用 func 。。
openCount = visible && openCount ? openCount - 1 : openCount;
}
removeCurrentContainer();
raf.cancel(rafId.value);
});
return () => {
const { forceRender, visible } = props;
let portal = null;
const childProps = {
getOpenCount: () => openCount,
getContainer,
};
if (forceRender || visible || componentRef.value) {
portal = (
<Portal
getContainer={getContainer}
ref={componentRef}
didUpdate={props.didUpdate}
v-slots={{ default: () => slots.default?.(childProps) }}
></Portal>
);
}
return portal;
};
},
});