diff --git a/components/_util/Portal.jsx b/components/_util/Portal.jsx deleted file mode 100644 index 782b3cf9e..000000000 --- a/components/_util/Portal.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import PropTypes from './vue-types'; -import { - defineComponent, - nextTick, - onBeforeUnmount, - onMounted, - onUpdated, - ref, - Teleport, -} from 'vue'; - -export default defineComponent({ - name: 'Portal', - inheritAttrs: false, - props: { - getContainer: PropTypes.func.isRequired, - didUpdate: PropTypes.func, - }, - setup(props, { slots }) { - const container = ref(); - onMounted(() => { - container.value = props.getContainer(); - }); - onUpdated(() => { - nextTick(() => { - props.nextTick?.(props); - }); - }); - onBeforeUnmount(() => { - if (container.value && container.value.parentNode) { - container.value.parentNode.removeChild(container.value); - } - }); - return () => { - return container.value ? {slots.default?.()} : null; - }; - }, -}); diff --git a/components/_util/Portal.tsx b/components/_util/Portal.tsx new file mode 100644 index 000000000..c856175b9 --- /dev/null +++ b/components/_util/Portal.tsx @@ -0,0 +1,29 @@ +import PropTypes from './vue-types'; +import { defineComponent, nextTick, onBeforeUnmount, onUpdated, Teleport } from 'vue'; + +export default defineComponent({ + name: 'Portal', + inheritAttrs: false, + props: { + getContainer: PropTypes.func.isRequired, + didUpdate: PropTypes.func, + }, + setup(props, { slots }) { + // getContainer 不会改变,不用响应式 + const container = props.getContainer(); + + onUpdated(() => { + nextTick(() => { + props.didUpdate?.(props); + }); + }); + onBeforeUnmount(() => { + if (container && container.parentNode) { + container.parentNode.removeChild(container); + } + }); + return () => { + return container ? : null; + }; + }, +}); diff --git a/components/_util/PortalWrapper.jsx b/components/_util/PortalWrapper.jsx deleted file mode 100644 index 7cf359071..000000000 --- a/components/_util/PortalWrapper.jsx +++ /dev/null @@ -1,150 +0,0 @@ -import PropTypes from './vue-types'; -import switchScrollingEffect from './switchScrollingEffect'; -import setStyle from './setStyle'; -import Portal from './Portal'; -import { defineComponent } from 'vue'; - -let openCount = 0; -const windowIsUndefined = !( - typeof window !== 'undefined' && - window.document && - window.document.createElement -); -// https://github.com/ant-design/ant-design/issues/19340 -// https://github.com/ant-design/ant-design/issues/19332 -let cacheOverflow = {}; - -export default defineComponent({ - name: 'PortalWrapper', - props: { - wrapperClassName: PropTypes.string, - forceRender: PropTypes.looseBool, - getContainer: PropTypes.any, - visible: PropTypes.looseBool, - }, - data() { - this._component = null; - const { visible } = this.$props; - openCount = visible ? openCount + 1 : openCount; - return {}; - }, - watch: { - visible(val) { - openCount = val ? openCount + 1 : openCount - 1; - }, - getContainer(getContainer, prevGetContainer) { - const getContainerIsFunc = - typeof getContainer === 'function' && typeof prevGetContainer === 'function'; - if ( - getContainerIsFunc - ? getContainer.toString() !== prevGetContainer.toString() - : getContainer !== prevGetContainer - ) { - this.removeCurrentContainer(false); - } - }, - }, - updated() { - this.setWrapperClassName(); - }, - beforeUnmount() { - const { visible } = this.$props; - // 离开时不会 render, 导到离开时数值不变,改用 func 。。 - openCount = visible && openCount ? openCount - 1 : openCount; - this.removeCurrentContainer(visible); - }, - methods: { - getParent() { - const { getContainer } = this.$props; - if (getContainer) { - if (typeof getContainer === 'string') { - return document.querySelectorAll(getContainer)[0]; - } - if (typeof getContainer === 'function') { - return getContainer(); - } - if (typeof getContainer === 'object' && getContainer instanceof window.HTMLElement) { - return getContainer; - } - } - return document.body; - }, - - getDomContainer() { - if (windowIsUndefined) { - return null; - } - if (!this.container) { - this.container = document.createElement('div'); - const parent = this.getParent(); - if (parent) { - parent.appendChild(this.container); - } - } - this.setWrapperClassName(); - return this.container; - }, - - setWrapperClassName() { - const { wrapperClassName } = this.$props; - if (this.container && wrapperClassName && wrapperClassName !== this.container.className) { - this.container.className = wrapperClassName; - } - }, - - savePortal(c) { - // Warning: don't rename _component - // https://github.com/react-component/util/pull/65#discussion_r352407916 - this._component = c; - }, - - removeCurrentContainer() { - this.container = null; - this._component = null; - }, - - /** - * Enhance ./switchScrollingEffect - * 1. Simulate document body scroll bar with - * 2. Record body has overflow style and recover when all of PortalWrapper invisible - * 3. Disable body scroll when PortalWrapper has open - * - * @memberof PortalWrapper - */ - switchScrollingEffect() { - if (openCount === 1 && !Object.keys(cacheOverflow).length) { - switchScrollingEffect(); - // Must be set after switchScrollingEffect - cacheOverflow = setStyle({ - overflow: 'hidden', - overflowX: 'hidden', - overflowY: 'hidden', - }); - } else if (!openCount) { - setStyle(cacheOverflow); - cacheOverflow = {}; - switchScrollingEffect(true); - } - }, - }, - - render() { - const { forceRender, visible } = this.$props; - let portal = null; - const childProps = { - getOpenCount: () => openCount, - getContainer: this.getDomContainer, - switchScrollingEffect: this.switchScrollingEffect, - }; - if (forceRender || visible || this._component) { - portal = ( - this.$slots.default?.(childProps) }} - > - ); - } - return portal; - }, -}); diff --git a/components/_util/PortalWrapper.tsx b/components/_util/PortalWrapper.tsx new file mode 100644 index 000000000..cd7c8f24f --- /dev/null +++ b/components/_util/PortalWrapper.tsx @@ -0,0 +1,217 @@ +import PropTypes from './vue-types'; +import switchScrollingEffect from './switchScrollingEffect'; +import setStyle from './setStyle'; +import Portal from './Portal'; +import { + defineComponent, + ref, + watch, + onMounted, + onBeforeUnmount, + onUpdated, + getCurrentInstance, +} from 'vue'; +import canUseDom from './canUseDom'; +import ScrollLocker from '../vc-util/Dom/scrollLocker'; +import wrapperRaf from './raf'; +import { nextTick } from 'process'; + +let openCount = 0; +const supportDom = canUseDom(); + +/** @private Test usage only */ +export function getOpenCount() { + return process.env.NODE_ENV === 'test' ? openCount : 0; +} + +// https://github.com/ant-design/ant-design/issues/19340 +// https://github.com/ant-design/ant-design/issues/19332 +let cacheOverflow = {}; + +const getParent = (getContainer: GetContainer) => { + if (!supportDom) { + return null; + } + if (getContainer) { + if (typeof getContainer === 'string') { + return document.querySelectorAll(getContainer)[0]; + } + 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({ + name: 'PortalWrapper', + inheritAttrs: false, + props: { + wrapperClassName: PropTypes.string, + forceRender: PropTypes.looseBool, + getContainer: PropTypes.any, + visible: PropTypes.looseBool, + }, + + setup(props, { slots }) { + const container = ref(); + const componentRef = ref(); + const rafId = ref(); + const scrollLocker = new ScrollLocker({ + container: getParent(props.getContainer) as HTMLElement, + }); + + const removeCurrentContainer = () => { + // Portal will remove from `parentNode`. + // Let's handle this again to avoid refactor issue. + container.value?.parentNode?.removeChild(container.value); + }; + const attachToParent = (force = false) => { + if (force || (container.value && !container.value.parentNode)) { + const parent = getParent(props.getContainer); + if (parent) { + parent.appendChild(container.value); + return true; + } + + return false; + } + + return true; + }; + // attachToParent(); + + const getContainer = () => { + if (!supportDom) { + return null; + } + if (!container.value) { + container.value = document.createElement('div'); + 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(); + }); + /** + * Enhance ./switchScrollingEffect + * 1. Simulate document body scroll bar with + * 2. Record body has overflow style and recover when all of PortalWrapper invisible + * 3. Disable body scroll when PortalWrapper has open + * + * @memberof PortalWrapper + */ + const switchScrolling = () => { + if (openCount === 1 && !Object.keys(cacheOverflow).length) { + switchScrollingEffect(); + // Must be set after switchScrollingEffect + cacheOverflow = setStyle({ + overflow: 'hidden', + overflowX: 'hidden', + overflowY: 'hidden', + }); + } else if (!openCount) { + setStyle(cacheOverflow); + cacheOverflow = {}; + switchScrollingEffect(true); + } + }; + const instance = getCurrentInstance(); + onMounted(() => { + let init = false; + watch( + [() => props.visible, () => props.getContainer], + ([visible, getContainer], [prevVisible, prevGetContainer]) => { + // Update count + if (supportDom && getParent(props.getContainer) === 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(); + } + // updateScrollLocker + if ( + visible && + visible !== prevVisible && + supportDom && + getParent(getContainer) !== scrollLocker.getContainer() + ) { + scrollLocker.reLock({ + container: getParent(getContainer) as HTMLElement, + }); + } + } + init = true; + }, + { immediate: true, flush: 'post' }, + ); + + nextTick(() => { + if (!attachToParent()) { + rafId.value = wrapperRaf(() => { + instance.update(); + }); + } + }); + }); + + onBeforeUnmount(() => { + const { visible, getContainer } = props; + if (supportDom && getParent(getContainer) === document.body) { + // 离开时不会 render, 导到离开时数值不变,改用 func 。。 + openCount = visible && openCount ? openCount - 1 : openCount; + } + removeCurrentContainer(); + wrapperRaf.cancel(rafId.value); + }); + + return () => { + const { forceRender, visible } = props; + let portal = null; + const childProps = { + getOpenCount: () => openCount, + getContainer, + switchScrollingEffect: switchScrolling, + scrollLocker, + }; + + if (forceRender || visible || componentRef.value) { + portal = ( + slots.default?.(childProps) }} + > + ); + } + return portal; + }; + }, +}); diff --git a/components/_util/__mocks__/Portal.tsx b/components/_util/__mocks__/Portal.tsx new file mode 100644 index 000000000..0043608ff --- /dev/null +++ b/components/_util/__mocks__/Portal.tsx @@ -0,0 +1,12 @@ +import { defineComponent } from 'vue'; + +export default defineComponent({ + name: 'Portal', + inheritAttrs: false, + props: ['getContainer'], + setup(_props, { slots }) { + return () => { + return slots.default?.(); + }; + }, +}); diff --git a/components/date-picker/__tests__/RangePicker.test.js b/components/date-picker/__tests__/RangePicker.test.js index 85ee3b6be..218489cea 100644 --- a/components/date-picker/__tests__/RangePicker.test.js +++ b/components/date-picker/__tests__/RangePicker.test.js @@ -4,7 +4,7 @@ import dayjs from 'dayjs'; import DatePicker from '../'; import { openPicker, selectCell, closePicker } from './utils'; import focusTest from '../../../tests/shared/focusTest'; - +jest.mock('../../_util/Portal'); const { RangePicker } = DatePicker; describe('RangePicker', () => { diff --git a/components/date-picker/__tests__/__snapshots__/RangePicker.test.js.snap b/components/date-picker/__tests__/__snapshots__/RangePicker.test.js.snap index 9b3c31943..abdff27cb 100644 --- a/components/date-picker/__tests__/__snapshots__/RangePicker.test.js.snap +++ b/components/date-picker/__tests__/__snapshots__/RangePicker.test.js.snap @@ -13,211 +13,207 @@ exports[`RangePicker customize separator 1`] = ` exports[`RangePicker show month panel according to value 1`] = `
- - +
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SuMoTuWeThFrSa
+
26
+
+
27
+
+
28
+
+
29
+
+
30
+
+
31
+
+
1
+
+
2
+
+
3
+
+
4
+
+
5
+
+
6
+
+
7
+
+
8
+
+
9
+
+
10
+
+
11
+
+
12
+
+
13
+
+
14
+
+
15
+
+
16
+
+
17
+
+
18
+
+
19
+
+
20
+
+
21
+
+
22
+
+
23
+
+
24
+
+
25
+
+
26
+
+
27
+
+
28
+
+
29
+
+
30
+
+
31
+
+
1
+
+
2
+
+
3
+
+
4
+
+
5
+
+
+
+
+
+ + +
2000/01/01
+ + +
+
+
+
+ +
+
+ +
+
+
+
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SuMoTuWeThFrSa
-
26
-
-
27
-
-
28
-
-
29
-
-
30
-
-
31
-
-
1
-
-
2
-
-
3
-
-
4
-
-
5
-
-
6
-
-
7
-
-
8
-
-
9
-
-
10
-
-
11
-
-
12
-
-
13
-
-
14
-
-
15
-
-
16
-
-
17
-
-
18
-
-
19
-
-
20
-
-
21
-
-
22
-
-
23
-
-
24
-
-
25
-
-
26
-
-
27
-
-
28
-
-
29
-
-
30
-
-
31
-
-
1
-
-
2
-
-
3
-
-
4
-
-
5
-
-
-
-
-
- - -
2000/01/01
- - -
-
-
-
- -
-
- -
-
-
-
-
+
`; diff --git a/components/drawer/__tests__/Drawer.test.js b/components/drawer/__tests__/Drawer.test.js index 9081fd9bd..107fef83a 100644 --- a/components/drawer/__tests__/Drawer.test.js +++ b/components/drawer/__tests__/Drawer.test.js @@ -16,7 +16,7 @@ const DrawerCom = { type: Boolean, default: false, }, - wrapClassName: { + class: { type: String, default: '', }, @@ -106,6 +106,7 @@ describe('Drawer', () => { destroyOnClose: true, closable: false, getContainer: false, + visible: true, }, slots: { default: () => 'Here is content of Drawer', @@ -121,7 +122,8 @@ describe('Drawer', () => { it('class is test_drawer', async () => { const props = { props: { - wrapClassName: 'test_drawer', + class: 'test_drawer', + visible: true, }, sync: false, }; diff --git a/components/drawer/__tests__/MultiDrawer.test.js b/components/drawer/__tests__/MultiDrawer.test.js index 1a56b9b7a..44d921c40 100644 --- a/components/drawer/__tests__/MultiDrawer.test.js +++ b/components/drawer/__tests__/MultiDrawer.test.js @@ -38,14 +38,14 @@ const MultiDrawer = { width: 520, visible: this.visible, getContainer: false, - className: 'test_drawer', + class: 'test_drawer', placement: this.placement, onClose: this.onClose, }; const childrenDrawerProps = { title: 'Two-level Drawer', width: 320, - className: 'Two-level', + class: 'Two-level', visible: this.childrenDrawer, getContainer: false, placement: this.placement, @@ -112,7 +112,7 @@ describe('Drawer', () => { wrapper.find('#open_two_drawer').trigger('click'); }, 0); await asyncExpect(() => { - const translateX = wrapper.find('.test_drawer').find('.ant-drawer').element.style.transform; + const translateX = wrapper.find('.test_drawer').element.style.transform; expect(translateX).toEqual('translateX(-180px)'); expect(wrapper.find('#two_drawer_text').exists()).toBe(true); }, 1000); @@ -133,7 +133,7 @@ describe('Drawer', () => { wrapper.find('#open_two_drawer').trigger('click'); }, 0); await asyncExpect(() => { - const translateX = wrapper.find('.test_drawer').find('.ant-drawer').element.style.transform; + const translateX = wrapper.find('.test_drawer').element.style.transform; expect(translateX).toEqual('translateX(180px)'); expect(wrapper.find('#two_drawer_text').exists()).toBe(true); }, 1000); @@ -153,7 +153,7 @@ describe('Drawer', () => { wrapper.find('#open_two_drawer').trigger('click'); }, 0); await asyncExpect(() => { - const translateY = wrapper.find('.test_drawer').find('.ant-drawer').element.style.transform; + const translateY = wrapper.find('.test_drawer').element.style.transform; expect(translateY).toEqual('translateY(180px)'); expect(wrapper.find('#two_drawer_text').exists()).toBe(true); }, 1000); diff --git a/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap b/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap index 0e5591ff4..2ff76db38 100644 --- a/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap +++ b/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap @@ -1,8 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Drawer class is test_drawer 1`] = ` -
-
+
+
@@ -17,16 +17,17 @@ exports[`Drawer class is test_drawer 1`] = `
+
`; exports[`Drawer closable is false 1`] = ` -
-
+
+
-
+
@@ -34,33 +35,35 @@ exports[`Drawer closable is false 1`] = `
+
`; exports[`Drawer destroyOnClose is true 1`] = ` -
-
+
+
-
+
Here is content of Drawer
+
`; exports[`Drawer have a title 1`] = ` -
-
+
+
-
+
@@ -73,16 +76,17 @@ exports[`Drawer have a title 1`] = `
+
`; exports[`Drawer render correctly 1`] = ` -
-
+
+
-
+
@@ -95,16 +99,17 @@ exports[`Drawer render correctly 1`] = `
+
`; exports[`Drawer render top drawer 1`] = ` -
-
+
+
-
+
@@ -117,6 +122,7 @@ exports[`Drawer render top drawer 1`] = `
+
diff --git a/components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap b/components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap index 5336ced19..fda0dc75d 100644 --- a/components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap +++ b/components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap @@ -4,8 +4,8 @@ exports[`Drawer render correctly 1`] = `
-
-
+
+
@@ -20,6 +20,7 @@ exports[`Drawer render correctly 1`] = `
+
diff --git a/components/drawer/__tests__/__snapshots__/demo.test.js.snap b/components/drawer/__tests__/__snapshots__/demo.test.js.snap index cc47ae3b9..5c0146173 100644 --- a/components/drawer/__tests__/__snapshots__/demo.test.js.snap +++ b/components/drawer/__tests__/__snapshots__/demo.test.js.snap @@ -39,8 +39,8 @@ exports[`renders ./components/drawer/demo/render-in-current.vue correctly 1`] =
Render in this
-
-
+
+
@@ -58,6 +58,7 @@ exports[`renders ./components/drawer/demo/render-in-current.vue correctly 1`] =
+
diff --git a/components/drawer/demo/basic.vue b/components/drawer/demo/basic.vue index 7a203d6ce..998684be9 100644 --- a/components/drawer/demo/basic.vue +++ b/components/drawer/demo/basic.vue @@ -20,6 +20,8 @@ Basic drawer. Open | 3.0.0 | | contentWrapperStyle | Style of the drawer wrapper of content part | CSSProperties | 3.0.0 | @@ -41,7 +41,7 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr | placement | The placement of the Drawer | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | | | push | Nested drawers push behavior | boolean \| {distance: string \| number} | { distance: 180} | 3.0.0 | | size | presetted size of drawer, default `378px` and large `736px` | `default` \| `large` | `default` | 3.0.0 | -| style(old: wrapStyle) | Style of wrapper element which contains mask compare to drawerStyle | CSSProperties | - | 3.0.0 | +| style | Style of wrapper element which contains mask compare to drawerStyle | CSSProperties | - | | | title | The title for Drawer | string \| slot | - | | | visible(v-model) | Whether the Drawer dialog is visible or not | boolean | - | | | width | Width of the Drawer dialog | string \| number | 378 | | diff --git a/components/drawer/index.tsx b/components/drawer/index.tsx index 4382b37dc..7ade52905 100644 --- a/components/drawer/index.tsx +++ b/components/drawer/index.tsx @@ -6,11 +6,11 @@ import { ref, onMounted, provide, - onBeforeMount, - onUpdated, onUnmounted, + watch, + computed, } from 'vue'; -import { getPropsSlot } from '../_util/props-util'; +import { getPropsSlot, initDefaultProps } from '../_util/props-util'; import classnames from '../_util/classNames'; import VcDrawer from '../vc-drawer'; import PropTypes from '../_util/vue-types'; @@ -18,6 +18,7 @@ import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; import useConfigInject from '../_util/hooks/useConfigInject'; import { tuple, withInstall } from '../_util/type'; import omit from '../_util/omit'; +import devWarning from '../vc-util/devWarning'; const PlacementTypes = tuple('top', 'right', 'bottom', 'left'); export type placementType = typeof PlacementTypes[number]; @@ -31,17 +32,22 @@ export interface PushState { const defaultPushState: PushState = { distance: 180 }; -const drawerProps = { - autoFocus: PropTypes.looseBool, - closable: PropTypes.looseBool.def(true), - closeIcon: PropTypes.VNodeChild.def(), +const drawerProps = () => ({ + autofocus: PropTypes.looseBool, + closable: PropTypes.looseBool, + closeIcon: PropTypes.VNodeChild, destroyOnClose: PropTypes.looseBool, forceRender: PropTypes.looseBool, getContainer: PropTypes.any, - maskClosable: PropTypes.looseBool.def(true), - mask: PropTypes.looseBool.def(true), + maskClosable: PropTypes.looseBool, + mask: PropTypes.looseBool, maskStyle: PropTypes.object, - style: PropTypes.object, + /** @deprecated Use `style` instead */ + wrapStyle: PropTypes.style, + style: PropTypes.style, + class: PropTypes.any, + /** @deprecated Use `class` instead */ + wrapClassName: PropTypes.string, size: { type: String as PropType, }, @@ -50,44 +56,74 @@ const drawerProps = { bodyStyle: PropTypes.object, contentWrapperStyle: PropTypes.object, title: PropTypes.VNodeChild, - visible: PropTypes.looseBool, - className: PropTypes.string, + visible: PropTypes.looseBool.isRequired, width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), zIndex: PropTypes.number, prefixCls: PropTypes.string, - push: PropTypes.oneOfType([PropTypes.looseBool, { type: Object as PropType }]).def( - defaultPushState, - ), - placement: PropTypes.oneOf(PlacementTypes).def('right'), - keyboard: PropTypes.looseBool.def(true), + push: PropTypes.oneOfType([PropTypes.looseBool, { type: Object as PropType }]), + placement: PropTypes.oneOf(PlacementTypes), + keyboard: PropTypes.looseBool, extra: PropTypes.VNodeChild, footer: PropTypes.VNodeChild, footerStyle: PropTypes.object, - level: PropTypes.any.def(null), + level: PropTypes.any, levelMove: PropTypes.any, -}; + handle: PropTypes.VNodeChild, + /** @deprecated Use `@afterVisibleChange` instead */ + afterVisibleChange: PropTypes.func, +}); export type DrawerProps = Partial>; const Drawer = defineComponent({ name: 'ADrawer', inheritAttrs: false, - props: drawerProps, + props: initDefaultProps(drawerProps(), { + closable: true, + placement: 'right' as placementType, + maskClosable: true, + mask: true, + level: null, + keyboard: true, + push: defaultPushState, + }), + slots: ['closeIcon', 'title', 'extra', 'footer', 'handle'], emits: ['update:visible', 'close', 'afterVisibleChange'], setup(props, { emit, slots, attrs }) { const sPush = ref(false); - const preVisible = ref(props.visible); const destroyClose = ref(false); const vcDrawer = ref(null); const parentDrawerOpts = inject('parentDrawerOpts', null); const { prefixCls } = useConfigInject('drawer', props); + devWarning( + !props.afterVisibleChange, + 'Drawer', + '`afterVisibleChange` prop is deprecated, please use `@afterVisibleChange` event instead', + ); + devWarning( + props.wrapStyle === undefined, + 'Drawer', + '`wrapStyle` prop is deprecated, please use `style` instead', + ); + devWarning( + props.wrapClassName === undefined, + 'Drawer', + '`wrapClassName` prop is deprecated, please use `class` instead', + ); + const setPush = () => { + sPush.value = true; + }; - onBeforeMount(() => { - provide('parentDrawerOpts', { - setPush, - setPull, + const setPull = () => { + sPush.value = false; + nextTick(() => { + domFocus(); }); + }; + provide('parentDrawerOpts', { + setPush, + setPull, }); onMounted(() => { @@ -103,19 +139,19 @@ const Drawer = defineComponent({ } }); - onUpdated(() => { - const { visible } = props; - nextTick(() => { - if (preVisible.value !== visible && parentDrawerOpts) { + watch( + () => props.visible, + visible => { + if (parentDrawerOpts) { if (visible) { parentDrawerOpts.setPush(); } else { parentDrawerOpts.setPull(); } } - preVisible.value = visible; - }); - }); + }, + { flush: 'post' }, + ); const domFocus = () => { vcDrawer.value?.domFocus?.(); @@ -127,22 +163,12 @@ const Drawer = defineComponent({ }; const afterVisibleChange = (visible: boolean) => { + props.afterVisibleChange?.(visible); emit('afterVisibleChange', visible); }; - - const setPush = () => { - sPush.value = true; - }; - - const setPull = () => { - sPush.value = false; - nextTick(() => { - domFocus(); - }); - }; - + const destroyOnClose = computed(() => props.destroyOnClose && !props.visible); const onDestroyTransitionEnd = () => { - const isDestroyOnClose = getDestroyOnClose(); + const isDestroyOnClose = destroyOnClose.value; if (!isDestroyOnClose) { return; } @@ -151,12 +177,8 @@ const Drawer = defineComponent({ } }; - const getDestroyOnClose = () => { - return props.destroyOnClose && !props.visible; - }; - - const getPushTransform = (placement?: placementType) => { - const { push } = props; + const pushTransform = computed(() => { + const { push, placement } = props; let distance: number | string; if (typeof push === 'boolean') { distance = push ? defaultPushState.distance : 0; @@ -172,18 +194,39 @@ const Drawer = defineComponent({ if (placement === 'top' || placement === 'bottom') { return `translateY(${placement === 'top' ? distance : -distance}px)`; } - }; + return null; + }); - const getRcDrawerStyle = () => { - const { zIndex, placement, style, mask } = props; - const offsetStyle = mask ? {} : getOffsetStyle(); + const offsetStyle = computed(() => { + // https://github.com/ant-design/ant-design/issues/24287 + const { visible, mask, placement, size, width, height } = props; + if (!visible && !mask) { + return {}; + } + const val: CSSProperties = {}; + if (placement === 'left' || placement === 'right') { + const defaultWidth = size === 'large' ? 736 : 378; + val.width = typeof width === 'undefined' ? defaultWidth : width; + val.width = typeof val.width === 'string' ? val.width : `${val.width}px`; + } else { + const defaultHeight = size === 'large' ? 736 : 378; + val.height = typeof height === 'undefined' ? defaultHeight : height; + val.height = typeof val.height === 'string' ? val.height : `${val.height}px`; + } + return val; + }); + + const drawerStyle = computed(() => { + const { zIndex, wrapStyle, mask, style } = props; + const val = mask ? {} : offsetStyle.value; return { zIndex, - transform: sPush.value ? getPushTransform(placement) : undefined, - ...offsetStyle, + transform: sPush.value ? pushTransform.value : undefined, + ...val, + ...wrapStyle, ...style, }; - }; + }); const renderHeader = (prefixCls: string) => { const { closable, headerStyle } = props; @@ -211,11 +254,11 @@ const Drawer = defineComponent({ const renderCloseIcon = (prefixCls: string) => { const { closable } = props; - const $closeIcon = getPropsSlot(slots, props, 'closeIcon'); + const $closeIcon = props.closeIcon ? slots.closeIcon?.() : props.closeIcon; return ( closable && ( ) ); @@ -231,7 +274,7 @@ const Drawer = defineComponent({ const containerStyle: CSSProperties = {}; - const isDestroyOnClose = getDestroyOnClose(); + const isDestroyOnClose = destroyOnClose.value; if (isDestroyOnClose) { // Increase the opacity transition, delete children after closing. containerStyle.opacity = 0; @@ -267,27 +310,19 @@ const Drawer = defineComponent({ ); }; - const getOffsetStyle = () => { - // https://github.com/ant-design/ant-design/issues/24287 - const { visible, mask, placement, size, width, height } = props; - if (!visible && !mask) { - return {}; - } - const offsetStyle: CSSProperties = {}; - if (placement === 'left' || placement === 'right') { - const defaultWidth = size === 'large' ? 736 : 378; - offsetStyle.width = typeof width === 'undefined' ? defaultWidth : width; - } else { - const defaultHeight = size === 'large' ? 736 : 378; - offsetStyle.height = typeof height === 'undefined' ? defaultHeight : height; - } - return offsetStyle; - }; - return () => { - const { width, height, visible, placement, mask, className, ...rest } = props; + const { + width, + height, + visible, + placement, + mask, + wrapClassName, + class: className, + ...rest + } = props; - const offsetStyle = mask ? getOffsetStyle() : {}; + const val = mask ? offsetStyle.value : {}; const haveMask = mask ? '' : 'no-mask'; const vcDrawerProps: any = { ...attrs, @@ -301,8 +336,9 @@ const Drawer = defineComponent({ 'bodyStyle', 'title', 'push', + 'wrapStyle', ]), - ...offsetStyle, + ...val, onClose: close, afterVisibleChange, handler: false, @@ -310,14 +346,23 @@ const Drawer = defineComponent({ open: visible, showMask: mask, placement, - wrapperClassName: classnames({ - [className as string]: className, + class: classnames({ + [className]: className, + [wrapClassName]: !!wrapClassName, [haveMask]: !!haveMask, }), - style: getRcDrawerStyle(), + style: drawerStyle.value, ref: vcDrawer, }; - return {renderBody(prefixCls.value)}; + return ( + props.handle : slots.handle, + default: () => renderBody(prefixCls.value), + }} + > + ); }; }, }); diff --git a/components/drawer/index.zh-CN.md b/components/drawer/index.zh-CN.md index d6af45da2..e58b80d20 100644 --- a/components/drawer/index.zh-CN.md +++ b/components/drawer/index.zh-CN.md @@ -21,7 +21,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/7z8NJQhFb/Drawer.svg | --- | --- | --- | --- | --- | | autoFocus | 抽屉展开后是否将焦点切换至其 Dom 节点 | boolean | true | 3.0.0 | | bodyStyle | 可用于设置 Drawer 内容部分的样式 | CSSProperties | - | | -| className(原 wrapClassName) | 对话框外层容器的类名 | string | - | 3.0.0 | +| class | 对话框外层容器的类名 | string | - | | | closable | 是否显示右上角的关闭按钮 | boolean | true | | | closeIcon | 自定义关闭图标 | VNode \| slot | | 3.0.0 | | contentWrapperStyle | 可用于设置 Drawer 包裹内容部分的样式 | CSSProperties | 3.0.0 | @@ -41,7 +41,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/7z8NJQhFb/Drawer.svg | placement | 抽屉的方向 | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | | | push | 用于设置多层 Drawer 的推动行为 | boolean \| {distance: string \| number} | { distance: 180} | 3.0.0 | | size | 预设抽屉宽度(或高度),default `378px` 和 large `736px` | `default` \| `large` | `default` | 3.0.0 | -| style(原 wrapStyle) | 可用于设置 Drawer 最外层容器的样式,和 `drawerStyle` 的区别是作用节点包括 `mask` | CSSProperties | - | 3.0.0 | +| style | 可用于设置 Drawer 最外层容器的样式,和 `drawerStyle` 的区别是作用节点包括 `mask` | CSSProperties | - | | | title | 标题 | string \| slot | - | | | visible(v-model) | Drawer 是否可见 | boolean | - | | | width | 宽度 | string \| number | 378 | | diff --git a/components/drawer/style/drawer.less b/components/drawer/style/drawer.less index eab0002fd..e63531a49 100644 --- a/components/drawer/style/drawer.less +++ b/components/drawer/style/drawer.less @@ -1,4 +1,4 @@ -@import '../../style/themes/index.less'; +@import '../../style/themes/index'; @drawer-prefix-cls: ~'@{ant-prefix}-drawer'; @picker-prefix-cls: ~'@{ant-prefix}-picker'; @@ -237,6 +237,7 @@ } } + // =================== Hook Components =================== .@{picker-prefix-cls} { &-clear { background: @popover-background; diff --git a/components/drawer/style/index.less b/components/drawer/style/index.less index a36039cda..77efacf49 100644 --- a/components/drawer/style/index.less +++ b/components/drawer/style/index.less @@ -3,4 +3,4 @@ @import './drawer'; @import './rtl'; -// .popover-customize-bg(@drawer-prefix-cls, @popover-background); +.popover-customize-bg(@drawer-prefix-cls, @popover-background); diff --git a/components/drawer/style/index.ts b/components/drawer/style/index.tsx similarity index 100% rename from components/drawer/style/index.ts rename to components/drawer/style/index.tsx diff --git a/components/menu/__tests__/__snapshots__/demo.test.js.snap b/components/menu/__tests__/__snapshots__/demo.test.js.snap index e282bcbcc..2a1d0fe5d 100644 --- a/components/menu/__tests__/__snapshots__/demo.test.js.snap +++ b/components/menu/__tests__/__snapshots__/demo.test.js.snap @@ -458,7 +458,7 @@ exports[`renders ./components/menu/demo/vertical.vue correctly 1`] = `
  • - +
  • diff --git a/components/modal/__tests__/Modal.test.js b/components/modal/__tests__/Modal.test.js index 5415e8eaa..4d62ae7ca 100644 --- a/components/modal/__tests__/Modal.test.js +++ b/components/modal/__tests__/Modal.test.js @@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'; import Modal from '..'; import mountTest from '../../../tests/shared/mountTest'; import { asyncExpect } from '../../../tests/utils'; - +jest.mock('../../_util/Portal'); const ModalTester = { props: ['footer', 'visible'], methods: { diff --git a/components/modal/__tests__/__snapshots__/Modal.test.js.snap b/components/modal/__tests__/__snapshots__/Modal.test.js.snap index ede42e3f3..c8d0991ac 100644 --- a/components/modal/__tests__/__snapshots__/Modal.test.js.snap +++ b/components/modal/__tests__/__snapshots__/Modal.test.js.snap @@ -2,88 +2,73 @@ exports[`Modal render correctly 1`] = `
    -
    -
    -
    -
    -