Skip to content

Commit 529f81c

Browse files
committed
refactor: portal
1 parent 8d050f1 commit 529f81c

File tree

19 files changed

+1038
-200
lines changed

19 files changed

+1038
-200
lines changed

antdv-demo

components/_util/Portal.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import PropTypes from './vue-types';
2+
import { cloneElement } from './vnode';
3+
4+
export default {
5+
name: 'Portal',
6+
props: {
7+
getContainer: PropTypes.func.isRequired,
8+
children: PropTypes.any.isRequired,
9+
didUpdate: PropTypes.func,
10+
},
11+
mounted() {
12+
this.createContainer();
13+
},
14+
updated() {
15+
const { didUpdate } = this.$props;
16+
if (didUpdate) {
17+
this.$nextTick(() => {
18+
didUpdate(this.$props);
19+
});
20+
}
21+
},
22+
23+
beforeDestroy() {
24+
this.removeContainer();
25+
},
26+
methods: {
27+
createContainer() {
28+
this._container = this.$props.getContainer();
29+
this.$forceUpdate();
30+
},
31+
32+
removeContainer() {
33+
if (this._container) {
34+
this._container.parentNode.removeChild(this._container);
35+
}
36+
},
37+
},
38+
39+
render() {
40+
if (this._container) {
41+
return cloneElement(this.$props.children, {
42+
directives: [
43+
{
44+
name: 'ant-portal',
45+
value: this._container,
46+
},
47+
],
48+
});
49+
}
50+
return null;
51+
},
52+
};

components/_util/PortalWrapper.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import PropTypes from './vue-types';
2+
import switchScrollingEffect from './switchScrollingEffect';
3+
import setStyle from './setStyle';
4+
import Portal from './Portal';
5+
6+
let openCount = 0;
7+
const windowIsUndefined = !(
8+
typeof window !== 'undefined' &&
9+
window.document &&
10+
window.document.createElement
11+
);
12+
// https://github.com/ant-design/ant-design/issues/19340
13+
// https://github.com/ant-design/ant-design/issues/19332
14+
let cacheOverflow = {};
15+
16+
export default {
17+
name: 'PortalWrapper',
18+
props: {
19+
wrapperClassName: PropTypes.string,
20+
forceRender: PropTypes.bool,
21+
getContainer: PropTypes.any,
22+
children: PropTypes.func,
23+
visible: PropTypes.bool,
24+
},
25+
data() {
26+
const { visible } = this.$props;
27+
openCount = visible ? openCount + 1 : openCount;
28+
return {};
29+
},
30+
updated() {
31+
this.setWrapperClassName();
32+
},
33+
watch: {
34+
visible(val) {
35+
openCount = val ? openCount + 1 : openCount - 1;
36+
},
37+
getContainer(getContainer, prevGetContainer) {
38+
const getContainerIsFunc =
39+
typeof getContainer === 'function' && typeof prevGetContainer === 'function';
40+
if (
41+
getContainerIsFunc
42+
? getContainer.toString() !== prevGetContainer.toString()
43+
: getContainer !== prevGetContainer
44+
) {
45+
this.removeCurrentContainer(false);
46+
}
47+
},
48+
},
49+
beforeDestroy() {
50+
const { visible } = this.$props;
51+
// 离开时不会 render, 导到离开时数值不变,改用 func 。。
52+
openCount = visible && openCount ? openCount - 1 : openCount;
53+
this.removeCurrentContainer(visible);
54+
},
55+
methods: {
56+
getParent() {
57+
const { getContainer } = this.$props;
58+
if (getContainer) {
59+
if (typeof getContainer === 'string') {
60+
return document.querySelectorAll(getContainer)[0];
61+
}
62+
if (typeof getContainer === 'function') {
63+
return getContainer();
64+
}
65+
if (typeof getContainer === 'object' && getContainer instanceof window.HTMLElement) {
66+
return getContainer;
67+
}
68+
}
69+
return document.body;
70+
},
71+
72+
getDomContainer() {
73+
if (windowIsUndefined) {
74+
return null;
75+
}
76+
if (!this.container) {
77+
this.container = document.createElement('div');
78+
const parent = this.getParent();
79+
if (parent) {
80+
parent.appendChild(this.container);
81+
}
82+
}
83+
this.setWrapperClassName();
84+
return this.container;
85+
},
86+
87+
setWrapperClassName() {
88+
const { wrapperClassName } = this.$props;
89+
if (this.container && wrapperClassName && wrapperClassName !== this.container.className) {
90+
this.container.className = wrapperClassName;
91+
}
92+
},
93+
94+
savePortal(c) {
95+
// Warning: don't rename _component
96+
// https://github.com/react-component/util/pull/65#discussion_r352407916
97+
this._component = c;
98+
},
99+
100+
removeCurrentContainer() {
101+
this.container = null;
102+
this._component = null;
103+
},
104+
105+
/**
106+
* Enhance ./switchScrollingEffect
107+
* 1. Simulate document body scroll bar with
108+
* 2. Record body has overflow style and recover when all of PortalWrapper invisible
109+
* 3. Disable body scroll when PortalWrapper has open
110+
*
111+
* @memberof PortalWrapper
112+
*/
113+
switchScrollingEffect() {
114+
if (openCount === 1 && !Object.keys(cacheOverflow).length) {
115+
switchScrollingEffect();
116+
// Must be set after switchScrollingEffect
117+
cacheOverflow = setStyle({
118+
overflow: 'hidden',
119+
overflowX: 'hidden',
120+
overflowY: 'hidden',
121+
});
122+
} else if (!openCount) {
123+
setStyle(cacheOverflow);
124+
cacheOverflow = {};
125+
switchScrollingEffect(true);
126+
}
127+
},
128+
},
129+
130+
render() {
131+
const { children, forceRender, visible } = this.$props;
132+
let portal = null;
133+
const childProps = {
134+
getOpenCount: () => openCount,
135+
getContainer: this.getDomContainer,
136+
switchScrollingEffect: this.switchScrollingEffect,
137+
};
138+
if (forceRender || visible || this._component) {
139+
portal = (
140+
<Portal
141+
getContainer={this.getDomContainer}
142+
children={children(childProps)}
143+
{...{
144+
directives: [
145+
{
146+
name: 'ant-ref',
147+
value: this.savePortal,
148+
},
149+
],
150+
}}
151+
></Portal>
152+
);
153+
}
154+
return portal;
155+
},
156+
};

components/_util/antDirective.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import ref from 'vue-ref';
22
import { antInput } from './antInputDirective';
33
import { antDecorator } from './FormDecoratorDirective';
4+
import { antPortal } from './portalDirective';
45

56
export default {
67
install: Vue => {
78
Vue.use(ref, { name: 'ant-ref' });
89
antInput(Vue);
910
antDecorator(Vue);
11+
antPortal(Vue);
1012
},
1113
};

components/_util/portalDirective.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export function antPortal(Vue) {
2+
return Vue.directive('ant-portal', {
3+
inserted(el, binding) {
4+
const { value } = binding;
5+
const parentNode = typeof value === 'function' ? value(el) : value;
6+
if (parentNode !== el.parentNode) {
7+
parentNode.appendChild(el);
8+
}
9+
},
10+
componentUpdated(el, binding) {
11+
const { value } = binding;
12+
const parentNode = typeof value === 'function' ? value(el) : value;
13+
if (parentNode !== el.parentNode) {
14+
parentNode.appendChild(el);
15+
}
16+
},
17+
});
18+
}
19+
20+
export default {
21+
install: Vue => {
22+
antPortal(Vue);
23+
},
24+
};

components/_util/setStyle.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Easy to set element style, return previous style
3+
* IE browser compatible(IE browser doesn't merge overflow style, need to set it separately)
4+
* https://github.com/ant-design/ant-design/issues/19393
5+
*
6+
*/
7+
function setStyle(style, options = {}) {
8+
const { element = document.body } = options;
9+
const oldStyle = {};
10+
11+
const styleKeys = Object.keys(style);
12+
13+
// IE browser compatible
14+
styleKeys.forEach(key => {
15+
oldStyle[key] = element.style[key];
16+
});
17+
18+
styleKeys.forEach(key => {
19+
element.style[key] = style[key];
20+
});
21+
22+
return oldStyle;
23+
}
24+
25+
export default setStyle;

components/_util/vnode.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ export function cloneElement(n, nodeProps = {}, deep) {
129129
node.componentOptions.children = children;
130130
}
131131
} else {
132+
if (children) {
133+
node.children = children;
134+
}
132135
node.data.on = { ...(node.data.on || {}), ...on };
133136
}
134137

components/drawer/__tests__/__snapshots__/Drawer.test.js.snap

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`Drawer class is test_drawer 1`] = `
4-
<div tabindex="-1" class="">
4+
<div class="">
55
<div tabindex="-1" class="ant-drawer ant-drawer-right test_drawer">
66
<div class="ant-drawer-mask"></div>
77
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 256px;">
@@ -19,7 +19,7 @@ exports[`Drawer class is test_drawer 1`] = `
1919
`;
2020

2121
exports[`Drawer closable is false 1`] = `
22-
<div tabindex="-1" class="">
22+
<div class="">
2323
<div tabindex="-1" class="ant-drawer ant-drawer-right">
2424
<div class="ant-drawer-mask"></div>
2525
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 256px;">
@@ -34,7 +34,7 @@ exports[`Drawer closable is false 1`] = `
3434
`;
3535

3636
exports[`Drawer destroyOnClose is true 1`] = `
37-
<div tabindex="-1" class="">
37+
<div class="">
3838
<div tabindex="-1" class="ant-drawer ant-drawer-right">
3939
<div class="ant-drawer-mask"></div>
4040
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 256px;">
@@ -49,7 +49,7 @@ exports[`Drawer destroyOnClose is true 1`] = `
4949
`;
5050

5151
exports[`Drawer have a title 1`] = `
52-
<div tabindex="-1" class="">
52+
<div class="">
5353
<div tabindex="-1" class="ant-drawer ant-drawer-right">
5454
<div class="ant-drawer-mask"></div>
5555
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 256px;">
@@ -69,7 +69,7 @@ exports[`Drawer have a title 1`] = `
6969
`;
7070

7171
exports[`Drawer render correctly 1`] = `
72-
<div tabindex="-1" class="">
72+
<div class="">
7373
<div tabindex="-1" class="ant-drawer ant-drawer-right">
7474
<div class="ant-drawer-mask"></div>
7575
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 400px;">
@@ -87,7 +87,7 @@ exports[`Drawer render correctly 1`] = `
8787
`;
8888

8989
exports[`Drawer render top drawer 1`] = `
90-
<div tabindex="-1" class="">
90+
<div class="">
9191
<div tabindex="-1" class="ant-drawer ant-drawer-top">
9292
<div class="ant-drawer-mask"></div>
9393
<div class="ant-drawer-content-wrapper" style="transform: translateY(-100%); height: 400px;">

components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
exports[`Drawer render correctly 1`] = `
44
<div><button type="button" class="ant-btn" ant-click-animating-without-extra-node="false"><span>open</span></button>
5-
<div tabindex="-1" class="">
5+
<div class="">
66
<div tabindex="-1" class="ant-drawer ant-drawer-right">
77
<div class="ant-drawer-mask"></div>
88
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 256px;">

components/drawer/__tests__/__snapshots__/demo.test.js.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ exports[`renders ./antdv-demo/docs/drawer/demo/render-in-current.md correctly 1`
4040
<div style="height: 200px; overflow: hidden; position: relative; border: 1px solid #ebedf0; border-radius: 2px; padding: 48px; text-align: center; background: rgb(250, 250, 250);">
4141
Render in this
4242
<div style="margin-top: 16px;"><button type="button" class="ant-btn ant-btn-primary"><span>Open</span></button></div>
43-
<div tabindex="-1" class="">
43+
<div class="">
4444
<div tabindex="-1" class="ant-drawer ant-drawer-right" style="position: absolute;">
4545
<div class="ant-drawer-mask"></div>
4646
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 256px;">

0 commit comments

Comments
 (0)