refactor: drawer (#4725)

* refactor(drawer): use compositionAPI

* refactor(drawer): update

* refactor: update

* chore: update

* docs: update

* fix: remove transitionStr

* chore: use useConfigInject

* refactor: drawer #4708

Co-authored-by: ajuner <106791576@qq.com>
pull/4738/head
tangjinzhou 2021-10-03 13:30:50 +08:00 committed by GitHub
parent 4fe3c7a919
commit a5a3411c28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 2286 additions and 1565 deletions

View File

@ -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 ? <Teleport to={container.value}>{slots.default?.()}</Teleport> : null;
};
},
});

View File

@ -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 ? <Teleport to={container} v-slots={slots}></Teleport> : null;
};
},
});

View File

@ -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 = (
<Portal
getContainer={this.getDomContainer}
ref={this.savePortal}
v-slots={{ default: () => this.$slots.default?.(childProps) }}
></Portal>
);
}
return portal;
},
});

View File

@ -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<HTMLElement>();
const componentRef = ref();
const rafId = ref<number>();
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 = (
<Portal
getContainer={getContainer}
ref={componentRef}
v-slots={{ default: () => slots.default?.(childProps) }}
></Portal>
);
}
return portal;
};
},
});

View File

@ -0,0 +1,12 @@
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Portal',
inheritAttrs: false,
props: ['getContainer'],
setup(_props, { slots }) {
return () => {
return slots.default?.();
};
},
});

View File

@ -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', () => {

View File

@ -13,211 +13,207 @@ exports[`RangePicker customize separator 1`] = `
exports[`RangePicker show month panel according to value 1`] = `
<div data-v-app="">
<!--teleport start-->
<!--teleport end-->
<div>
<!---->
<div class="ant-picker-dropdown ant-picker-dropdown-range" style="opacity: 0; pointer-events: none;">
<div class="ant-picker-range-wrapper ant-picker-date-range-wrapper" style="min-width: 0px;">
<div class="ant-picker-range-arrow" style="left: 0px;"></div>
<div class="ant-picker-panel-container" style="margin-left: 0px;">
<div class="ant-picker-panels">
<div tabindex="0" class="ant-picker-panel ant-picker-panel-has-range ant-picker-panel-focused">
<div class="ant-picker-datetime-panel">
<div class="ant-picker-date-panel">
<div class="ant-picker-header"><button type="button" tabindex="-1" class="ant-picker-header-super-prev-btn">«</button><button type="button" tabindex="-1" class="ant-picker-header-prev-btn"></button>
<div class="ant-picker-header-view"><button type="button" tabindex="-1" class="ant-picker-month-btn">Jan</button><button type="button" tabindex="-1" class="ant-picker-year-btn">2000</button></div><button type="button" tabindex="-1" class="ant-picker-header-next-btn"></button><button type="button" tabindex="-1" class="ant-picker-header-super-next-btn">»</button>
</div>
<div class="ant-picker-body">
<table class="ant-picker-content">
<thead>
<tr>
<th>Su</th>
<th>Mo</th>
<th>Tu</th>
<th>We</th>
<th>Th</th>
<th>Fr</th>
<th>Sa</th>
</tr>
</thead>
<tbody>
<tr>
<td title="1999-12-26" class="ant-picker-cell">
<div class="ant-picker-cell-inner">26</div>
</td>
<td title="1999-12-27" class="ant-picker-cell">
<div class="ant-picker-cell-inner">27</div>
</td>
<td title="1999-12-28" class="ant-picker-cell">
<div class="ant-picker-cell-inner">28</div>
</td>
<td title="1999-12-29" class="ant-picker-cell">
<div class="ant-picker-cell-inner">29</div>
</td>
<td title="1999-12-30" class="ant-picker-cell">
<div class="ant-picker-cell-inner">30</div>
</td>
<td title="1999-12-31" class="ant-picker-cell ant-picker-cell-end">
<div class="ant-picker-cell-inner">31</div>
</td>
<td title="2000-01-01" class="ant-picker-cell ant-picker-cell-start ant-picker-cell-in-view ant-picker-cell-range-start ant-picker-cell-range-end ant-picker-cell-selected">
<div class="ant-picker-cell-inner">1</div>
</td>
</tr>
<tr>
<td title="2000-01-02" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">2</div>
</td>
<td title="2000-01-03" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">3</div>
</td>
<td title="2000-01-04" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">4</div>
</td>
<td title="2000-01-05" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">5</div>
</td>
<td title="2000-01-06" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">6</div>
</td>
<td title="2000-01-07" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">7</div>
</td>
<td title="2000-01-08" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">8</div>
</td>
</tr>
<tr>
<td title="2000-01-09" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">9</div>
</td>
<td title="2000-01-10" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">10</div>
</td>
<td title="2000-01-11" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">11</div>
</td>
<td title="2000-01-12" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">12</div>
</td>
<td title="2000-01-13" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">13</div>
</td>
<td title="2000-01-14" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">14</div>
</td>
<td title="2000-01-15" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">15</div>
</td>
</tr>
<tr>
<td title="2000-01-16" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">16</div>
</td>
<td title="2000-01-17" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">17</div>
</td>
<td title="2000-01-18" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">18</div>
</td>
<td title="2000-01-19" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">19</div>
</td>
<td title="2000-01-20" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">20</div>
</td>
<td title="2000-01-21" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">21</div>
</td>
<td title="2000-01-22" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">22</div>
</td>
</tr>
<tr>
<td title="2000-01-23" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">23</div>
</td>
<td title="2000-01-24" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">24</div>
</td>
<td title="2000-01-25" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">25</div>
</td>
<td title="2000-01-26" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">26</div>
</td>
<td title="2000-01-27" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">27</div>
</td>
<td title="2000-01-28" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">28</div>
</td>
<td title="2000-01-29" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">29</div>
</td>
</tr>
<tr>
<td title="2000-01-30" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">30</div>
</td>
<td title="2000-01-31" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">31</div>
</td>
<td title="2000-02-01" class="ant-picker-cell ant-picker-cell-start">
<div class="ant-picker-cell-inner">1</div>
</td>
<td title="2000-02-02" class="ant-picker-cell">
<div class="ant-picker-cell-inner">2</div>
</td>
<td title="2000-02-03" class="ant-picker-cell">
<div class="ant-picker-cell-inner">3</div>
</td>
<td title="2000-02-04" class="ant-picker-cell">
<div class="ant-picker-cell-inner">4</div>
</td>
<td title="2000-02-05" class="ant-picker-cell">
<div class="ant-picker-cell-inner">5</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="ant-picker-time-panel">
<div class="ant-picker-header">
<!---->
<!---->
<div class="ant-picker-header-view">2000/01/01</div>
<!---->
<!---->
</div>
<div class="ant-picker-content"></div>
</div>
</div>
<!---->
</div>
</div>
<div class="ant-picker-footer">
<!---->
<ul class="ant-picker-ranges">
<!---->
<li class="ant-picker-ok"><button class="ant-btn ant-btn-primary ant-btn-sm" type="button">
<!----><span>Ok</span>
</button></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="ant-picker ant-picker-range">
<div class="ant-picker-input ant-picker-input-active"><input readonly="" placeholder="Start date" size="12" autocomplete="off"></div>
<div class="ant-picker-range-separator"><span aria-label="to" class="ant-picker-separator"><span role="img" aria-label="swap-right" class="anticon anticon-swap-right"><svg focusable="false" class="" data-icon="swap-right" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="0 0 1024 1024"><path d="M873.1 596.2l-164-208A32 32 0 00684 376h-64.8c-6.7 0-10.4 7.7-6.3 13l144.3 183H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h695.9c26.8 0 41.7-30.8 25.2-51.8z"></path></svg></span></span></div>
<div class="ant-picker-input"><input readonly="" placeholder="End date" size="12" autocomplete="off"></div>
<div class="ant-picker-active-bar" style="left: 0px; width: 0px; position: absolute;"></div><span class="ant-picker-suffix"><span role="img" aria-label="calendar" class="anticon anticon-calendar"><svg focusable="false" class="" data-icon="calendar" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"></path></svg></span></span>
<div style="position: absolute; top: 0px; left: 0px; width: 100%;">
<div>
<!---->
<div class="ant-picker-dropdown ant-picker-dropdown-range" style="opacity: 0; pointer-events: none;">
<div class="ant-picker-range-wrapper ant-picker-date-range-wrapper" style="min-width: 0px;">
<div class="ant-picker-range-arrow" style="left: 0px;"></div>
<div class="ant-picker-panel-container" style="margin-left: 0px;">
<div class="ant-picker-panels">
<div tabindex="0" class="ant-picker-panel ant-picker-panel-has-range ant-picker-panel-focused">
<div class="ant-picker-datetime-panel">
<div class="ant-picker-date-panel">
<div class="ant-picker-header"><button type="button" tabindex="-1" class="ant-picker-header-super-prev-btn">«</button><button type="button" tabindex="-1" class="ant-picker-header-prev-btn"></button>
<div class="ant-picker-header-view"><button type="button" tabindex="-1" class="ant-picker-month-btn">Jan</button><button type="button" tabindex="-1" class="ant-picker-year-btn">2000</button></div><button type="button" tabindex="-1" class="ant-picker-header-next-btn"></button><button type="button" tabindex="-1" class="ant-picker-header-super-next-btn">»</button>
</div>
<div class="ant-picker-body">
<table class="ant-picker-content">
<thead>
<tr>
<th>Su</th>
<th>Mo</th>
<th>Tu</th>
<th>We</th>
<th>Th</th>
<th>Fr</th>
<th>Sa</th>
</tr>
</thead>
<tbody>
<tr>
<td title="1999-12-26" class="ant-picker-cell">
<div class="ant-picker-cell-inner">26</div>
</td>
<td title="1999-12-27" class="ant-picker-cell">
<div class="ant-picker-cell-inner">27</div>
</td>
<td title="1999-12-28" class="ant-picker-cell">
<div class="ant-picker-cell-inner">28</div>
</td>
<td title="1999-12-29" class="ant-picker-cell">
<div class="ant-picker-cell-inner">29</div>
</td>
<td title="1999-12-30" class="ant-picker-cell">
<div class="ant-picker-cell-inner">30</div>
</td>
<td title="1999-12-31" class="ant-picker-cell ant-picker-cell-end">
<div class="ant-picker-cell-inner">31</div>
</td>
<td title="2000-01-01" class="ant-picker-cell ant-picker-cell-start ant-picker-cell-in-view ant-picker-cell-range-start ant-picker-cell-range-end ant-picker-cell-selected">
<div class="ant-picker-cell-inner">1</div>
</td>
</tr>
<tr>
<td title="2000-01-02" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">2</div>
</td>
<td title="2000-01-03" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">3</div>
</td>
<td title="2000-01-04" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">4</div>
</td>
<td title="2000-01-05" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">5</div>
</td>
<td title="2000-01-06" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">6</div>
</td>
<td title="2000-01-07" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">7</div>
</td>
<td title="2000-01-08" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">8</div>
</td>
</tr>
<tr>
<td title="2000-01-09" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">9</div>
</td>
<td title="2000-01-10" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">10</div>
</td>
<td title="2000-01-11" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">11</div>
</td>
<td title="2000-01-12" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">12</div>
</td>
<td title="2000-01-13" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">13</div>
</td>
<td title="2000-01-14" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">14</div>
</td>
<td title="2000-01-15" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">15</div>
</td>
</tr>
<tr>
<td title="2000-01-16" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">16</div>
</td>
<td title="2000-01-17" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">17</div>
</td>
<td title="2000-01-18" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">18</div>
</td>
<td title="2000-01-19" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">19</div>
</td>
<td title="2000-01-20" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">20</div>
</td>
<td title="2000-01-21" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">21</div>
</td>
<td title="2000-01-22" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">22</div>
</td>
</tr>
<tr>
<td title="2000-01-23" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">23</div>
</td>
<td title="2000-01-24" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">24</div>
</td>
<td title="2000-01-25" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">25</div>
</td>
<td title="2000-01-26" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">26</div>
</td>
<td title="2000-01-27" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">27</div>
</td>
<td title="2000-01-28" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">28</div>
</td>
<td title="2000-01-29" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">29</div>
</td>
</tr>
<tr>
<td title="2000-01-30" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">30</div>
</td>
<td title="2000-01-31" class="ant-picker-cell ant-picker-cell-in-view">
<div class="ant-picker-cell-inner">31</div>
</td>
<td title="2000-02-01" class="ant-picker-cell ant-picker-cell-start">
<div class="ant-picker-cell-inner">1</div>
</td>
<td title="2000-02-02" class="ant-picker-cell">
<div class="ant-picker-cell-inner">2</div>
</td>
<td title="2000-02-03" class="ant-picker-cell">
<div class="ant-picker-cell-inner">3</div>
</td>
<td title="2000-02-04" class="ant-picker-cell">
<div class="ant-picker-cell-inner">4</div>
</td>
<td title="2000-02-05" class="ant-picker-cell">
<div class="ant-picker-cell-inner">5</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="ant-picker-time-panel">
<div class="ant-picker-header">
<!---->
<!---->
<div class="ant-picker-header-view">2000/01/01</div>
<!---->
<!---->
</div>
<div class="ant-picker-content"></div>
</div>
</div>
<!---->
</div>
</div>
<div class="ant-picker-footer">
<!---->
<ul class="ant-picker-ranges">
<!---->
<li class="ant-picker-ok"><button class="ant-btn ant-btn-primary ant-btn-sm" type="button">
<!----><span>Ok</span>
</button></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div><span class="ant-picker-clear"><span role="img" aria-label="close-circle" class="anticon anticon-close-circle"><svg focusable="false" class="" data-icon="close-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"></path></svg></span></span>
<div class="ant-picker-active-bar" style="left: 0px; width: 0px; position: absolute;"></div><span class="ant-picker-suffix"><span role="img" aria-label="calendar" class="anticon anticon-calendar"><svg focusable="false" class="" data-icon="calendar" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"></path></svg></span></span><span class="ant-picker-clear"><span role="img" aria-label="close-circle" class="anticon anticon-close-circle"><svg focusable="false" class="" data-icon="close-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"></path></svg></span></span>
</div>
</div>
`;

View File

@ -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,
};

View File

@ -38,14 +38,14 @@ const MultiDrawer = {
width: 520,
visible: this.visible,
getContainer: false,
wrapClassName: 'test_drawer',
class: 'test_drawer',
placement: this.placement,
onClose: this.onClose,
};
const childrenDrawerProps = {
title: 'Two-level Drawer',
width: 320,
wrapClassName: '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('.ant-drawer.test_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('.ant-drawer.test_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('.ant-drawer.test_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);

View File

@ -2,15 +2,19 @@
exports[`Drawer class is test_drawer 1`] = `
<div class="">
<div class="ant-drawer ant-drawer-right test_drawer" tabindex="-1">
<div tabindex="-1" class="ant-drawer ant-drawer-right test_drawer">
<div class="ant-drawer-mask"></div>
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 256px;">
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 378px;">
<div class="ant-drawer-content">
<div class="ant-drawer-wrapper-body">
<div class="ant-drawer-header-no-title">
<!----><button aria-label="Close" class="ant-drawer-close"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
<div class="ant-drawer-header ant-drawer-header-close-only">
<div class="ant-drawer-header-title"><button aria-label="Close" class="ant-drawer-close"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
<!---->
</div>
<!---->
</div>
<div class="ant-drawer-body">Here is content of Drawer</div>
<!---->
</div>
</div>
<!---->
@ -21,13 +25,14 @@ exports[`Drawer class is test_drawer 1`] = `
exports[`Drawer closable is false 1`] = `
<div class="">
<div class="ant-drawer ant-drawer-right" tabindex="-1">
<div tabindex="-1" class="ant-drawer ant-drawer-right">
<div class="ant-drawer-mask"></div>
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 256px;">
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 378px;">
<div class="ant-drawer-content">
<div class="ant-drawer-wrapper-body">
<!---->
<div class="ant-drawer-body">Here is content of Drawer</div>
<!---->
</div>
</div>
<!---->
@ -38,13 +43,14 @@ exports[`Drawer closable is false 1`] = `
exports[`Drawer destroyOnClose is true 1`] = `
<div class="">
<div class="ant-drawer ant-drawer-right" tabindex="-1">
<div tabindex="-1" class="ant-drawer ant-drawer-right">
<div class="ant-drawer-mask"></div>
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 256px;">
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 378px;">
<div class="ant-drawer-content">
<div class="ant-drawer-wrapper-body" style="opacity: 0; transition: opacity .3s;">
<div class="ant-drawer-wrapper-body">
<!---->
<div class="ant-drawer-body">Here is content of Drawer</div>
<!---->
</div>
</div>
<!---->
@ -55,15 +61,19 @@ exports[`Drawer destroyOnClose is true 1`] = `
exports[`Drawer have a title 1`] = `
<div class="">
<div class="ant-drawer ant-drawer-right" tabindex="-1">
<div tabindex="-1" class="ant-drawer ant-drawer-right">
<div class="ant-drawer-mask"></div>
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 256px;">
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 378px;">
<div class="ant-drawer-content">
<div class="ant-drawer-wrapper-body">
<div class="ant-drawer-header">
<div class="ant-drawer-title">Test Title</div><button aria-label="Close" class="ant-drawer-close"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
<div class="ant-drawer-header-title"><button aria-label="Close" class="ant-drawer-close"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
<div class="ant-drawer-title">Test Title</div>
</div>
<!---->
</div>
<div class="ant-drawer-body">Here is content of Drawer</div>
<!---->
</div>
</div>
<!---->
@ -74,15 +84,19 @@ exports[`Drawer have a title 1`] = `
exports[`Drawer render correctly 1`] = `
<div class="">
<div class="ant-drawer ant-drawer-right" tabindex="-1">
<div tabindex="-1" class="ant-drawer ant-drawer-right">
<div class="ant-drawer-mask"></div>
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 400px;">
<div class="ant-drawer-content">
<div class="ant-drawer-wrapper-body">
<div class="ant-drawer-header-no-title">
<!----><button aria-label="Close" class="ant-drawer-close"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
<div class="ant-drawer-header ant-drawer-header-close-only">
<div class="ant-drawer-header-title"><button aria-label="Close" class="ant-drawer-close"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
<!---->
</div>
<!---->
</div>
<div class="ant-drawer-body">Here is content of Drawer</div>
<!---->
</div>
</div>
<!---->
@ -93,15 +107,19 @@ exports[`Drawer render correctly 1`] = `
exports[`Drawer render top drawer 1`] = `
<div class="">
<div class="ant-drawer ant-drawer-top" tabindex="-1">
<div tabindex="-1" class="ant-drawer ant-drawer-top">
<div class="ant-drawer-mask"></div>
<div class="ant-drawer-content-wrapper" style="transform: translateY(-100%); height: 400px;">
<div class="ant-drawer-content">
<div class="ant-drawer-wrapper-body">
<div class="ant-drawer-header-no-title">
<!----><button aria-label="Close" class="ant-drawer-close"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
<div class="ant-drawer-header ant-drawer-header-close-only">
<div class="ant-drawer-header-title"><button aria-label="Close" class="ant-drawer-close"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
<!---->
</div>
<!---->
</div>
<div class="ant-drawer-body">Here is content of Drawer</div>
<!---->
</div>
</div>
<!---->

View File

@ -5,15 +5,19 @@ exports[`Drawer render correctly 1`] = `
<!----><span>open</span>
</button>
<div class="">
<div class="ant-drawer ant-drawer-right" tabindex="-1">
<div tabindex="-1" class="ant-drawer ant-drawer-right">
<div class="ant-drawer-mask"></div>
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 256px;">
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 378px;">
<div class="ant-drawer-content">
<div class="ant-drawer-wrapper-body">
<div class="ant-drawer-header-no-title">
<!----><button aria-label="Close" class="ant-drawer-close"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
<div class="ant-drawer-header ant-drawer-header-close-only">
<div class="ant-drawer-header-title"><button aria-label="Close" class="ant-drawer-close"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
<!---->
</div>
<!---->
</div>
<div class="ant-drawer-body">Here is content of Drawer</div>
<!---->
</div>
</div>
<!---->

View File

@ -7,6 +7,13 @@ exports[`renders ./components/drawer/demo/basic.vue correctly 1`] = `
<!---->
`;
exports[`renders ./components/drawer/demo/extra.vue correctly 1`] = `
<div class="ant-radio-group ant-radio-group-outline ant-radio-group-default" style="margin-right: 8px;"><label class="ant-radio-wrapper"><span class="ant-radio"><input type="radio" class="ant-radio-input" value="top"><span class="ant-radio-inner"></span></span><span>top</span></label><label class="ant-radio-wrapper"><span class="ant-radio"><input type="radio" class="ant-radio-input" value="right"><span class="ant-radio-inner"></span></span><span>right</span></label><label class="ant-radio-wrapper"><span class="ant-radio"><input type="radio" class="ant-radio-input" value="bottom"><span class="ant-radio-inner"></span></span><span>bottom</span></label><label class="ant-radio-wrapper ant-radio-wrapper-checked"><span class="ant-radio ant-radio-checked"><input type="radio" class="ant-radio-input" value="left"><span class="ant-radio-inner"></span></span><span>left</span></label></div><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open</span>
</button>
<!---->
`;
exports[`renders ./components/drawer/demo/form-in-drawer.vue correctly 1`] = `
<button class="ant-btn ant-btn-primary" type="button">
<!----><span role="img" aria-label="plus" class="anticon anticon-plus"><svg focusable="false" class="" data-icon="plus" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><defs><style></style></defs><path d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"></path><path d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"></path></svg></span><span>New account</span>
@ -33,18 +40,22 @@ exports[`renders ./components/drawer/demo/render-in-current.vue correctly 1`] =
<!----><span>Open</span>
</button></div>
<div class="">
<div class="ant-drawer ant-drawer-right" style="position: absolute;" tabindex="-1">
<div tabindex="-1" class="ant-drawer ant-drawer-right" style="position: absolute;">
<div class="ant-drawer-mask"></div>
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 256px;">
<div class="ant-drawer-content-wrapper" style="transform: translateX(100%); width: 378px;">
<div class="ant-drawer-content">
<div class="ant-drawer-wrapper-body">
<div class="ant-drawer-header">
<div class="ant-drawer-title">Basic Drawer</div>
<div class="ant-drawer-header-title">
<!---->
<div class="ant-drawer-title">Basic Drawer</div>
</div>
<!---->
</div>
<div class="ant-drawer-body">
<p>Some contents...</p>
</div>
<!---->
</div>
</div>
<!---->
@ -54,6 +65,15 @@ exports[`renders ./components/drawer/demo/render-in-current.vue correctly 1`] =
</div>
`;
exports[`renders ./components/drawer/demo/size.vue correctly 1`] = `
<button style="margin-right: 8px;" class="ant-btn ant-btn-primary" type="button">
<!----><span>Open Default Size (378px)</span>
</button><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open Large Size (736px)</span>
</button>
<!---->
`;
exports[`renders ./components/drawer/demo/user-profile.vue correctly 1`] = `
<div class="ant-list ant-list-split ant-list-bordered">
<!---->

View File

@ -20,10 +20,11 @@ Basic drawer.
<a-button type="primary" @click="showDrawer">Open</a-button>
<a-drawer
v-model:visible="visible"
class="custom-class"
style="color: red"
title="Basic Drawer"
placement="right"
:closable="false"
:after-visible-change="afterVisibleChange"
@after-visible-change="afterVisibleChange"
>
<p>Some contents...</p>
<p>Some contents...</p>

View File

@ -0,0 +1,65 @@
<docs>
---
order: 2
title:
zh-CN: 额外操作
en-US: Extra Actions
---
## zh-CN
Ant Design 规范中操作按钮建议放在抽屉的右上角可以使用 extra 属性来实现
## en-US
Extra actions should be placed at corner of drawer in Ant Design, you can using `extra` prop for that.
</docs>
<template>
<a-radio-group v-model:value="placement" style="margin-right: 8px">
<a-radio value="top">top</a-radio>
<a-radio value="right">right</a-radio>
<a-radio value="bottom">bottom</a-radio>
<a-radio value="left">left</a-radio>
</a-radio-group>
<a-button type="primary" @click="showDrawer">Open</a-button>
<a-drawer
:width="500"
title="Basic Drawer"
:placement="placement"
:visible="visible"
@close="onClose"
>
<template #extra>
<a-button style="margin-right: 8px" @click="onClose">Cancel</a-button>
<a-button type="primary" @click="onClose">Submit</a-button>
</template>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</a-drawer>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const placement = ref<string>('left');
const visible = ref<boolean>(false);
const showDrawer = () => {
visible.value = true;
};
const onClose = () => {
visible.value = false;
};
return {
placement,
visible,
showDrawer,
onClose,
};
},
});
</script>

View File

@ -1,6 +1,6 @@
<docs>
---
order: 3
order: 4
title:
zh-CN: 抽屉表单
en-US: Submit form in drawer
@ -26,6 +26,7 @@ Use form in drawer with submit button.
:width="720"
:visible="visible"
:body-style="{ paddingBottom: '80px' }"
:footer-style="{ textAlign: 'right' }"
@close="onClose"
>
<a-form :model="form" :rules="rules" layout="vertical">
@ -96,22 +97,10 @@ Use form in drawer with submit button.
</a-col>
</a-row>
</a-form>
<div
:style="{
position: 'absolute',
right: 0,
bottom: 0,
width: '100%',
borderTop: '1px solid #e9e9e9',
padding: '10px 16px',
background: '#fff',
textAlign: 'right',
zIndex: 1,
}"
>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">Cancel</a-button>
<a-button type="primary" @click="onClose">Submit</a-button>
</div>
</template>
</a-drawer>
</template>
<script lang="ts">

View File

@ -1,20 +1,24 @@
<template>
<demo-sort>
<basic />
<extra />
<placement />
<render-in-current />
<form-in-drawer />
<user-profile />
<multi-level-drawer />
<user-profile />
<size />
</demo-sort>
</template>
<script lang="ts">
import Basic from './basic.vue';
import Extra from './extra.vue';
import Placement from './placement.vue';
import UserProfile from './user-profile.vue';
import MultiLevelDrawer from './multi-level-drawer.vue';
import FormInDrawer from './form-in-drawer.vue';
import RenderInCurrent from './render-in-current.vue';
import Size from './size.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
@ -25,11 +29,13 @@ export default defineComponent({
US,
components: {
Basic,
Extra,
Placement,
UserProfile,
MultiLevelDrawer,
FormInDrawer,
RenderInCurrent,
Size,
},
setup() {
return {};

View File

@ -16,41 +16,48 @@ Open a new drawer on top of an existing drawer to handle multi branch tasks.
</docs>
<docs>
---
order: 6
title:
zh-CN: 多层抽屉
en-US: Multi-level drawer
---
## zh-CN
在抽屉内打开新的抽屉用以解决多分支任务的复杂状况
## en-US
Open a new drawer on top of an existing drawer to handle multi branch tasks.
</docs>
<template>
<a-button type="primary" @click="showDrawer">Open</a-button>
<a-drawer
v-model:visible="visible"
title="Multi-level drawer"
width="520"
:closable="false"
:visible="visible"
:footer-style="{ textAlign: 'right' }"
@close="onClose"
>
<a-button type="primary" @click="showChildrenDrawer">Two-level drawer</a-button>
<a-drawer
v-model:visible="childrenDrawer"
title="Two-level Drawer"
width="320"
:closable="false"
:visible="childrenDrawer"
@close="onChildrenDrawerClose"
>
<a-button type="primary" @click="showChildrenDrawer">This is two-level drawer</a-button>
</a-drawer>
<div
:style="{
position: 'absolute',
bottom: 0,
width: '100%',
borderTop: '1px solid #e8e8e8',
padding: '10px 16px',
textAlign: 'right',
left: 0,
background: '#fff',
borderRadius: '0 0 4px 4px',
}"
>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">Cancel</a-button>
<a-button type="primary" @click="onClose">Submit</a-button>
</div>
</template>
</a-drawer>
</template>
<script lang="ts">
@ -71,16 +78,12 @@ export default defineComponent({
const showChildrenDrawer = () => {
childrenDrawer.value = true;
};
const onChildrenDrawerClose = () => {
childrenDrawer.value = false;
};
return {
visible,
childrenDrawer,
showDrawer,
onClose,
showChildrenDrawer,
onChildrenDrawerClose,
};
},
});

View File

@ -1,6 +1,6 @@
<docs>
---
order: 2
order: 3
title:
zh-CN: 渲染在当前 DOM
en-US: Render in current dom
@ -40,7 +40,7 @@ Render in current dom. custom container, check getContainer.
:closable="false"
:visible="visible"
:get-container="false"
:wrap-style="{ position: 'absolute' }"
:style="{ position: 'absolute' }"
@close="onClose"
>
<p>Some contents...</p>
@ -48,10 +48,10 @@ Render in current dom. custom container, check getContainer.
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const visible = ref(true);
const visible = ref(false);
const afterVisibleChange = (bool: boolean) => {
console.log('visible', bool);
@ -65,10 +65,6 @@ export default defineComponent({
visible.value = false;
};
onMounted(() => {
visible.value = false;
});
return {
visible,
afterVisibleChange,

View File

@ -0,0 +1,57 @@
<docs>
---
order: 7
title:
zh-CN: 预设宽度
en-US: Presetted size
---
## zh-CN
抽屉的默认宽度为 `378px`另外还提供一个大号抽屉 `736px`可以用 size 属性来设置
## en-US
The default width (or height) of Drawer is `378px`, and there is a presetted large size `736px`.
</docs>
<template>
<a-button type="primary" style="margin-right: 8px" @click="showDrawer('default')">
Open Default Size (378px)
</a-button>
<a-button type="primary" @click="showDrawer('large')">Open Large Size (736px)</a-button>
<a-drawer title="Basic Drawer" :size="size" :visible="visible" @close="onClose">
<template #extra>
<a-button style="margin-right: 8px" @click="onClose">Cancel</a-button>
<a-button type="primary" @click="onClose">Submit</a-button>
</template>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</a-drawer>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const visible = ref<boolean>(false);
const size = ref<string>('default');
const showDrawer = (val: string) => {
size.value = val;
visible.value = true;
};
const onClose = () => {
visible.value = false;
};
return {
visible,
size,
showDrawer,
onClose,
};
},
});
</script>

View File

@ -1,6 +1,6 @@
<docs>
---
order: 4
order: 6
title:
zh-CN: 信息预览抽屉
en-US: Preview drawer

View File

@ -15,34 +15,41 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
- Processing subtasks. When subtasks are too heavy for a Popover and we still want to keep the subtasks in the context of the main task, Drawer comes very handy.
- When the same Form is needed in multiple places.
## API
| Property | Description | Type | Default | Version |
| Props | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| closable | Whether a close (x) button is visible on top right of the Drawer dialog or not. | boolean | true | |
| destroyOnClose | Whether to unmount child components on closing drawer or not. | boolean | false | |
| getContainer | Return the mounted node for Drawer. | HTMLElement \| `() => HTMLElement` \| Selectors | 'body' | |
| mask | Whether to show mask or not. | Boolean | true | |
| maskClosable | Clicking on the mask (area outside the Drawer) to close the Drawer or not. | boolean | true | |
| maskStyle | Style for Drawer's mask element. | object | {} | |
| title | The title for Drawer. | string\|slot | - | |
| visible(v-model) | Whether the Drawer dialog is visible or not. | boolean | false | |
| wrapClassName | The class name of the container of the Drawer dialog. | string | - | |
| wrapStyle | Style of wrapper element which **contains mask** compare to `drawerStyle` | object | - | |
| autoFocus | Whether Drawer should get focused after open | boolean | true | 3.0.0 |
| bodyStyle | Style of the drawer content part | CSSProperties | - | |
| class | The class name of the container of the Drawer dialog | string | - | |
| closable | Whether a close (x) button is visible on top right of the Drawer dialog or not | boolean | true | |
| closeIcon | Custom close icon | VNode \| slot | <CloseOutlined /> | 3.0.0 |
| contentWrapperStyle | Style of the drawer wrapper of content part | CSSProperties | 3.0.0 |
| destroyOnClose | Whether to unmount child components on closing drawer or not | boolean | false | |
| drawerStyle | Style of the popup layer element | object | - | |
| headerStyle | Style of the drawer header part | object | - | |
| bodyStyle | Style of the drawer content part | object | - | |
| width | Width of the Drawer dialog. | string\|number | 256 | |
| height | placement is `top` or `bottom`, height of the Drawer dialog. | string\|number | - | |
| zIndex | The `z-index` of the Drawer. | Number | 1000 | |
| placement | The placement of the Drawer. | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | |
| handle | After setting, the drawer is directly mounted on the DOM, and you can control the drawer to open or close through this `handle`. | VNode \| slot | - | |
| afterVisibleChange | Callback after the animation ends when switching drawers. | function(visible) | - | |
| keyboard | Whether support press esc to close | Boolean | true | |
| extra | Extra actions area at corner | VNode \| slot | - | 3.0.0 |
| footer | The footer for Drawer | VNode \| slot | - | 3.0.0 |
| footerStyle | Style of the drawer footer part | CSSProperties | - | 3.0.0 |
| forceRender | Prerender Drawer component forcely | boolean | false | 3.0.0 |
| getContainer | Return the mounted node for Drawer | HTMLElement \| `() => HTMLElement` \| Selectors | 'body' | |
| headerStyle | Style of the drawer header part | CSSProperties | - | 3.0.0 |
| height | Placement is `top` or `bottom`, height of the Drawer dialog | string \| number | 378 | |
| keyboard | Whether support press esc to close | boolean | true | |
| mask | Whether to show mask or not | Boolean | true | |
| maskClosable | Clicking on the mask (area outside the Drawer) to close the Drawer or not | boolean | true | |
| maskStyle | Style for Drawer's mask element | CSSProperties | {} | |
| 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 | 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 | |
| zIndex | The `z-index` of the Drawer | Number | 1000 | |
## Methods
| Name | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| afterVisibleChange | Callback after the animation ends when switching drawers. | function(visible) | - | |
| close | Specify a callback that will be called when a user clicks mask, close button or Cancel button. | function(e) | - | |

View File

@ -1,189 +1,280 @@
import type { CSSProperties } from 'vue';
import { inject, provide, nextTick, defineComponent } from 'vue';
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
import {
inject,
nextTick,
defineComponent,
ref,
onMounted,
provide,
onUnmounted,
watch,
computed,
} from 'vue';
import { getPropsSlot, initDefaultProps } from '../_util/props-util';
import classnames from '../_util/classNames';
import VcDrawer from '../vc-drawer/src';
import VcDrawer from '../vc-drawer';
import PropTypes from '../_util/vue-types';
import BaseMixin from '../_util/BaseMixin';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import { getComponent, getOptionProps } from '../_util/props-util';
import { defaultConfigProvider } from '../config-provider';
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');
type placementType = typeof PlacementTypes[number];
export type placementType = typeof PlacementTypes[number];
const SizeTypes = tuple('default', 'large');
export type sizeType = typeof SizeTypes[number];
export interface PushState {
distance: string | number;
}
const defaultPushState: PushState = { distance: 180 };
const drawerProps = () => ({
autofocus: PropTypes.looseBool,
closable: PropTypes.looseBool,
closeIcon: PropTypes.VNodeChild,
destroyOnClose: PropTypes.looseBool,
forceRender: PropTypes.looseBool,
getContainer: PropTypes.any,
maskClosable: PropTypes.looseBool,
mask: PropTypes.looseBool,
maskStyle: 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<sizeType>,
},
drawerStyle: PropTypes.object,
headerStyle: PropTypes.object,
bodyStyle: PropTypes.object,
contentWrapperStyle: PropTypes.object,
title: PropTypes.VNodeChild,
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<PushState> }]),
placement: PropTypes.oneOf(PlacementTypes),
keyboard: PropTypes.looseBool,
extra: PropTypes.VNodeChild,
footer: PropTypes.VNodeChild,
footerStyle: PropTypes.object,
level: PropTypes.any,
levelMove: PropTypes.any,
handle: PropTypes.VNodeChild,
/** @deprecated Use `@afterVisibleChange` instead */
afterVisibleChange: PropTypes.func,
});
export type DrawerProps = Partial<ExtractPropTypes<typeof drawerProps>>;
const Drawer = defineComponent({
name: 'ADrawer',
mixins: [BaseMixin],
inheritAttrs: false,
props: {
closable: PropTypes.looseBool.def(true),
destroyOnClose: PropTypes.looseBool,
getContainer: PropTypes.any,
maskClosable: PropTypes.looseBool.def(true),
mask: PropTypes.looseBool.def(true),
maskStyle: PropTypes.object,
wrapStyle: PropTypes.object,
bodyStyle: PropTypes.object,
headerStyle: PropTypes.object,
drawerStyle: PropTypes.object,
title: PropTypes.VNodeChild,
visible: PropTypes.looseBool,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).def(256),
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).def(256),
zIndex: PropTypes.number,
prefixCls: PropTypes.string,
placement: PropTypes.oneOf(PlacementTypes).def('right'),
level: PropTypes.any.def(null),
wrapClassName: PropTypes.string, // not use class like react, vue will add class to root dom
handle: PropTypes.VNodeChild,
afterVisibleChange: PropTypes.func,
keyboard: PropTypes.looseBool.def(true),
onClose: PropTypes.func,
'onUpdate:visible': PropTypes.func,
},
setup(props) {
const configProvider = inject('configProvider', defaultConfigProvider);
return {
configProvider,
destroyClose: false,
preVisible: props.visible,
parentDrawer: inject('parentDrawer', null),
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 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;
};
},
data() {
return {
sPush: false,
};
},
beforeCreate() {
provide('parentDrawer', this);
},
mounted() {
// fix: delete drawer in child and re-render, no push started.
// <Drawer>{show && <Drawer />}</Drawer>
const { visible } = this;
if (visible && this.parentDrawer) {
this.parentDrawer.push();
}
},
updated() {
nextTick(() => {
if (this.preVisible !== this.visible && this.parentDrawer) {
if (this.visible) {
this.parentDrawer.push();
} else {
this.parentDrawer.pull();
}
}
this.preVisible = this.visible;
});
},
beforeUnmount() {
// unmount drawer in child, clear push.
if (this.parentDrawer) {
this.parentDrawer.pull();
}
},
methods: {
domFocus() {
if (this.$refs.vcDrawer) {
(this.$refs.vcDrawer as any).domFocus();
}
},
close(e: Event) {
this.$emit('update:visible', false);
this.$emit('close', e);
},
// onMaskClick(e) {
// if (!this.maskClosable) {
// return;
// }
// this.close(e);
// },
push() {
this.setState({
sPush: true,
const setPull = () => {
sPush.value = false;
nextTick(() => {
domFocus();
});
},
pull() {
this.setState(
{
sPush: false,
},
() => {
this.domFocus();
},
);
},
onDestroyTransitionEnd() {
const isDestroyOnClose = this.getDestroyOnClose();
};
provide('parentDrawerOpts', {
setPush,
setPull,
});
onMounted(() => {
const { visible } = props;
if (visible && parentDrawerOpts) {
parentDrawerOpts.setPush();
}
});
onUnmounted(() => {
if (parentDrawerOpts) {
parentDrawerOpts.setPull();
}
});
watch(
() => props.visible,
visible => {
if (parentDrawerOpts) {
if (visible) {
parentDrawerOpts.setPush();
} else {
parentDrawerOpts.setPull();
}
}
},
{ flush: 'post' },
);
const domFocus = () => {
vcDrawer.value?.domFocus?.();
};
const close = (e: Event) => {
emit('update:visible', false);
emit('close', e);
};
const afterVisibleChange = (visible: boolean) => {
props.afterVisibleChange?.(visible);
emit('afterVisibleChange', visible);
};
const destroyOnClose = computed(() => props.destroyOnClose && !props.visible);
const onDestroyTransitionEnd = () => {
const isDestroyOnClose = destroyOnClose.value;
if (!isDestroyOnClose) {
return;
}
if (!this.visible) {
this.destroyClose = true;
(this as any).$forceUpdate();
if (!props.visible) {
destroyClose.value = true;
}
},
};
const pushTransform = computed(() => {
const { push, placement } = props;
let distance: number | string;
if (typeof push === 'boolean') {
distance = push ? defaultPushState.distance : 0;
} else {
distance = push!.distance;
}
distance = parseFloat(String(distance || 0));
getDestroyOnClose() {
return this.destroyOnClose && !this.visible;
},
// get drawar push width or height
getPushTransform(placement?: placementType) {
if (placement === 'left' || placement === 'right') {
return `translateX(${placement === 'left' ? 180 : -180}px)`;
return `translateX(${placement === 'left' ? distance : -distance}px)`;
}
if (placement === 'top' || placement === 'bottom') {
return `translateY(${placement === 'top' ? 180 : -180}px)`;
return `translateY(${placement === 'top' ? distance : -distance}px)`;
}
},
getRcDrawerStyle() {
const { zIndex, placement, wrapStyle } = this.$props;
const { sPush: push } = this.$data;
return null;
});
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: push ? this.getPushTransform(placement) : undefined,
transform: sPush.value ? pushTransform.value : undefined,
...val,
...wrapStyle,
...style,
};
},
renderHeader(prefixCls: string) {
const { closable, headerStyle } = this.$props;
const title = getComponent(this, 'title');
});
const renderHeader = (prefixCls: string) => {
const { closable, headerStyle } = props;
const extra = getPropsSlot(slots, props, 'extra');
const title = getPropsSlot(slots, props, 'title');
if (!title && !closable) {
return null;
}
const headerClassName = title ? `${prefixCls}-header` : `${prefixCls}-header-no-title`;
return (
<div class={headerClassName} style={headerStyle}>
{title && <div class={`${prefixCls}-title`}>{title}</div>}
{closable ? this.renderCloseIcon(prefixCls) : null}
<div
class={classnames(`${prefixCls}-header`, {
[`${prefixCls}-header-close-only`]: closable && !title && !extra,
})}
style={headerStyle}
>
<div class={`${prefixCls}-header-title`}>
{renderCloseIcon(prefixCls)}
{title && <div class={`${prefixCls}-title`}>{title}</div>}
</div>
{extra && <div class={`${prefixCls}-extra`}>{extra}</div>}
</div>
);
},
renderCloseIcon(prefixCls: string) {
const { closable } = this;
};
const renderCloseIcon = (prefixCls: string) => {
const { closable } = props;
const $closeIcon = props.closeIcon ? slots.closeIcon?.() : props.closeIcon;
return (
closable && (
<button key="closer" onClick={this.close} aria-label="Close" class={`${prefixCls}-close`}>
<CloseOutlined />
<button key="closer" onClick={close} aria-label="Close" class={`${prefixCls}-close`}>
{$closeIcon === undefined ? <CloseOutlined></CloseOutlined> : null}
</button>
)
);
},
// render drawer body dom
renderBody(prefixCls: string) {
if (this.destroyClose && !this.visible) {
};
const renderBody = (prefixCls: string) => {
if (destroyClose.value && !props.visible) {
return null;
}
this.destroyClose = false;
const { bodyStyle, drawerStyle } = this.$props;
destroyClose.value = false;
const { bodyStyle, drawerStyle } = props;
const containerStyle: CSSProperties = {};
const isDestroyOnClose = this.getDestroyOnClose();
const isDestroyOnClose = destroyOnClose.value;
if (isDestroyOnClose) {
// Increase the opacity transition, delete children after closing.
containerStyle.opacity = 0;
@ -194,74 +285,85 @@ const Drawer = defineComponent({
<div
class={`${prefixCls}-wrapper-body`}
style={{ ...containerStyle, ...drawerStyle }}
onTransitionend={this.onDestroyTransitionEnd}
onTransitionend={onDestroyTransitionEnd}
>
{this.renderHeader(prefixCls)}
{renderHeader(prefixCls)}
<div key="body" class={`${prefixCls}-body`} style={bodyStyle}>
{this.$slots.default?.()}
{slots.default?.()}
</div>
{renderFooter(prefixCls)}
</div>
);
},
},
render() {
const props: any = getOptionProps(this);
const {
prefixCls: customizePrefixCls,
width,
height,
visible,
placement,
wrapClassName,
mask,
...rest
} = props;
const haveMask = mask ? '' : 'no-mask';
const offsetStyle: CSSProperties = {};
if (placement === 'left' || placement === 'right') {
offsetStyle.width = typeof width === 'number' ? `${width}px` : width;
} else {
offsetStyle.height = typeof height === 'number' ? `${height}px` : height;
}
const handler = getComponent(this, 'handle') || false;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('drawer', customizePrefixCls);
const { class: className } = this.$attrs;
const vcDrawerProps: any = {
...this.$attrs,
...omit(rest, [
'closable',
'destroyOnClose',
'drawerStyle',
'headerStyle',
'bodyStyle',
'title',
'push',
'visible',
'getPopupContainer',
'rootPrefixCls',
'getPrefixCls',
'renderEmpty',
'csp',
'pageHeader',
'autoInsertSpaceInButton',
]),
onClose: this.close,
handler,
...offsetStyle,
prefixCls,
open: visible,
showMask: mask,
placement,
class: classnames({
[className as string]: !!className,
[wrapClassName]: !!wrapClassName,
[haveMask]: !!haveMask,
}),
wrapStyle: this.getRcDrawerStyle(),
ref: 'vcDrawer',
};
return <VcDrawer {...vcDrawerProps}>{this.renderBody(prefixCls)}</VcDrawer>;
const renderFooter = (prefixCls: string) => {
const footer = getPropsSlot(slots, props, 'footer');
if (!footer) {
return null;
}
const footerClassName = `${prefixCls}-footer`;
return (
<div class={footerClassName} style={props.footerStyle}>
{footer}
</div>
);
};
return () => {
const {
width,
height,
visible,
placement,
mask,
wrapClassName,
class: className,
...rest
} = props;
const val = mask ? offsetStyle.value : {};
const haveMask = mask ? '' : 'no-mask';
const vcDrawerProps: any = {
...attrs,
...omit(rest, [
'size',
'closeIcon',
'closable',
'destroyOnClose',
'drawerStyle',
'headerStyle',
'bodyStyle',
'title',
'push',
'wrapStyle',
]),
...val,
onClose: close,
afterVisibleChange,
handler: false,
prefixCls: prefixCls.value,
open: visible,
showMask: mask,
placement,
class: classnames({
[className]: className,
[wrapClassName]: !!wrapClassName,
[haveMask]: !!haveMask,
}),
style: drawerStyle.value,
ref: vcDrawer,
};
return (
<VcDrawer
{...vcDrawerProps}
v-slots={{
handler: props.handle ? () => props.handle : slots.handle,
default: () => renderBody(prefixCls.value),
}}
></VcDrawer>
);
};
},
});

View File

@ -6,33 +6,50 @@ subtitle: 抽屉
cover: https://gw.alipayobjects.com/zos/alicdn/7z8NJQhFb/Drawer.svg
---
屏幕边缘滑出的浮层面板。
## 何时使用
抽屉从父窗体边缘滑入,覆盖住部分父窗体内容。用户在抽屉内操作时不必离开当前任务,操作完成后,可以平滑地回到原任务。
- 当需要一个附加的面板来控制父窗体内容,这个面板在需要时呼出。比如,控制界面展示样式,往界面中添加内容。
- 当需要在当前任务流中插入临时任务,创建或预览附加内容。比如展示协议条款,创建子对象。
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| autoFocus | 抽屉展开后是否将焦点切换至其 Dom 节点 | boolean | true | 3.0.0 |
| bodyStyle | 可用于设置 Drawer 内容部分的样式 | CSSProperties | - | |
| class | 对话框外层容器的类名 | string | - | |
| closable | 是否显示右上角的关闭按钮 | boolean | true | |
| closeIcon | 自定义关闭图标 | VNode \| slot | <CloseOutlined /> | 3.0.0 |
| contentWrapperStyle | 可用于设置 Drawer 包裹内容部分的样式 | CSSProperties | 3.0.0 |
| destroyOnClose | 关闭时销毁 Drawer 里的子元素 | boolean | false | |
| drawerStyle | 用于设置 Drawer 弹出层的样式 | object | - | |
| extra | 抽屉右上角的操作区域 | VNode \| slot | - | 3.0.0 |
| footer | 抽屉的页脚 | VNode \| slot | - | 3.0.0 |
| footerStyle | 抽屉页脚部件的样式 | CSSProperties | - | 3.0.0 |
| forceRender | 预渲染 Drawer 内元素 | boolean | false | 3.0.0 |
| getContainer | 指定 Drawer 挂载的 HTML 节点 | HTMLElement \| `() => HTMLElement` \| Selectors | 'body' | |
| maskClosable | 点击蒙层是否允许关闭 | boolean | true | |
| headerStyle | 用于设置 Drawer 头部的样式 | CSSProperties | - | 3.0.0 |
| height | 高度, 在 `placement``top``bottom` 时使用 | string \| number | 378 | |
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
| mask | 是否展示遮罩 | Boolean | true | |
| maskStyle | 遮罩样式 | object | {} | |
| maskClosable | 点击蒙层是否允许关闭 | boolean | true | |
| maskStyle | 遮罩样式 | CSSProperties | {} | |
| 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 | 可用于设置 Drawer 最外层容器的样式,和 `drawerStyle` 的区别是作用节点包括 `mask` | CSSProperties | - | |
| title | 标题 | string \| slot | - | |
| visible(v-model) | Drawer 是否可见 | boolean | - | |
| wrapClassName | 对话框外层容器的类名 | string | - | |
| wrapStyle | 可用于设置 Drawer 最外层容器的样式,和 `drawerStyle` 的区别是作用节点包括 `mask` | object | - | |
| drawerStyle | 用于设置 Drawer 弹出层的样式 | object | - | |
| headerStyle | 用于设置 Drawer 头部的样式 | object | - | |
| bodyStyle | 可用于设置 Drawer 内容部分的样式 | object | - | |
| width | 宽度 | string \| number | 256 | |
| height | 高度, 在 `placement``top``bottom` 时使用 | string \| number | 256 | |
| width | 宽度 | string \| number | 378 | |
| zIndex | 设置 Drawer 的 `z-index` | Number | 1000 | |
| placement | 抽屉的方向 | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | |
| handle | 设置后抽屉直接挂载到 DOM 上,你可以通过该 handle 控制抽屉打开关闭 | VNode \| slot | - | |
| afterVisibleChange | 切换抽屉时动画结束后的回调 | function(visible) | 无 | |
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
## 方法
| 名称 | 描述 | 类型 | 默认值 | 版本 |
| ----- | ------------------------------------ | ----------- | ------ | ---- |
| close | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | 无 | |
| 名称 | 描述 | 类型 | 默认值 | 版本 |
| ------------------ | ------------------------------------ | ----------------- | ------ | ---- |
| afterVisibleChange | 切换抽屉时动画结束后的回调 | function(visible) | 无 | |
| close | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | 无 | |

View File

@ -1,12 +1,11 @@
@import '../../style/themes/index';
// Preserve the typo for compatibility
// https://github.com/ant-design/ant-design/issues/14628
@dawer-prefix-cls: ~'@{ant-prefix}-drawer';
@drawer-prefix-cls: @dawer-prefix-cls;
@drawer-prefix-cls: ~'@{ant-prefix}-drawer';
@picker-prefix-cls: ~'@{ant-prefix}-picker';
.@{drawer-prefix-cls} {
@drawer-header-close-padding: ceil(((@drawer-header-close-size - @font-size-lg) / 2));
position: fixed;
z-index: @zindex-modal;
width: 0%;
@ -20,7 +19,10 @@
&-content-wrapper {
position: absolute;
width: 100%;
height: 100%;
}
.@{drawer-prefix-cls}-content {
width: 100%;
height: 100%;
@ -38,12 +40,17 @@
width: 100%;
transition: transform @animation-duration-slow @ease-base-out;
}
&.@{drawer-prefix-cls}-open.no-mask {
width: 0%;
}
}
&-left {
left: 0;
.@{drawer-prefix-cls} {
&-content-wrapper {
left: 0;
}
}
&.@{drawer-prefix-cls}-open {
.@{drawer-prefix-cls}-content-wrapper {
box-shadow: @shadow-1-right;
@ -84,9 +91,6 @@
height: 100%;
transition: transform @animation-duration-slow @ease-base-out;
}
&.@{drawer-prefix-cls}-open.no-mask {
height: 0%;
}
}
&-top {
@ -118,15 +122,12 @@
}
}
&.@{drawer-prefix-cls}-open {
.@{drawer-prefix-cls} {
&-mask {
height: 100%;
opacity: 1;
transition: none;
animation: antdDrawerFadeIn @animation-duration-slow @ease-base-out;
}
}
&.@{drawer-prefix-cls}-open .@{drawer-prefix-cls}-mask {
height: 100%;
opacity: 1;
transition: none;
animation: antdDrawerFadeIn @animation-duration-slow @ease-base-out;
pointer-events: auto;
}
&-title {
@ -147,19 +148,13 @@
}
&-close {
position: absolute;
top: 0;
right: 0;
z-index: @zindex-popup-close;
display: block;
width: 56px;
height: 56px;
padding: 0;
color: @text-color-secondary;
display: inline-block;
margin-right: 12px;
color: @modal-close-color;
font-weight: 700;
font-size: @font-size-lg;
font-style: normal;
line-height: 56px;
line-height: 1;
text-align: center;
text-transform: none;
text-decoration: none;
@ -179,27 +174,48 @@
&-header {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: @drawer-header-padding;
color: @text-color;
background: @drawer-bg;
border-bottom: @border-width-base @border-style-base @border-color-split;
border-radius: @border-radius-base @border-radius-base 0 0;
&-title {
display: flex;
align-items: center;
justify-content: space-between;
}
&-close-only {
padding-bottom: 0;
border: none;
}
}
&-header-no-title {
color: @text-color;
background: @drawer-bg;
&-wrapper-body {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
width: 100%;
height: 100%;
}
&-body {
flex-grow: 1;
padding: @drawer-body-padding;
overflow: auto;
font-size: @font-size-base;
line-height: @line-height-base;
word-wrap: break-word;
}
&-wrapper-body {
height: 100%;
overflow: auto;
&-footer {
flex-shrink: 0;
padding: @drawer-footer-padding-vertical @drawer-footer-padding-horizontal;
border-top: @border-width-base @border-style-base @border-color-split;
}
&-mask {
@ -212,12 +228,21 @@
opacity: 0;
filter: ~'alpha(opacity=45)';
transition: opacity @animation-duration-slow linear, height 0s ease @animation-duration-slow;
pointer-events: none;
}
&-open {
&-content {
box-shadow: @shadow-2;
}
}
// =================== Hook Components ===================
.@{picker-prefix-cls} {
&-clear {
background: @popover-background;
}
}
}
@keyframes antdDrawerFadeIn {

View File

@ -1,3 +1,6 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './drawer';
@import './rtl';
.popover-customize-bg(@drawer-prefix-cls, @popover-background);

View File

@ -0,0 +1,16 @@
@import '../../style/themes/index';
@drawer-prefix-cls: ~'@{ant-prefix}-drawer';
.@{drawer-prefix-cls} {
&-rtl {
direction: rtl;
}
&-close {
.@{drawer-prefix-cls}-rtl & {
margin-right: 0;
margin-left: 12px;
}
}
}

View File

@ -458,7 +458,7 @@ exports[`renders ./components/menu/demo/vertical.vue correctly 1`] = `
<li class="ant-menu-submenu ant-menu-submenu-vertical" role="none" data-submenu-id="sub2">
<!--teleport start-->
<!--teleport end-->
<div class="ant-menu-submenu-title" tabindex="-1" data-menu-id="sub2" aria-expanded="false" aria-haspopup="true" aria-controls="sub_menu_23_$$_sub2-popup" aria-disabled="false"><span role="img" aria-label="setting" class="anticon anticon-setting ant-menu-item-icon"><svg focusable="false" class="" data-icon="setting" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"></path></svg></span><span class="ant-menu-title-content">Navigation Four</span><i class="ant-menu-submenu-arrow"></i></div>
<div class="ant-menu-submenu-title" tabindex="-1" data-menu-id="sub2" aria-expanded="false" aria-haspopup="true" aria-controls="sub_menu_24_$$_sub2-popup" aria-disabled="false"><span role="img" aria-label="setting" class="anticon anticon-setting ant-menu-item-icon"><svg focusable="false" class="" data-icon="setting" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"></path></svg></span><span class="ant-menu-title-content">Navigation Four</span><i class="ant-menu-submenu-arrow"></i></div>
<!---->
</li>
<!---->

View File

@ -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: {

View File

@ -2,88 +2,73 @@
exports[`Modal render correctly 1`] = `
<div>
<div>
<div>
<div class="ant-modal-root">
<div class="ant-modal-mask"></div>
<div tabindex="-1" class="ant-modal-wrap " role="dialog">
<div role="document" style="width: 520px;" class="ant-modal">
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
<div class="ant-modal-content"><button type="button" aria-label="Close" class="ant-modal-close"><span class="ant-modal-close-x"><span role="img" aria-label="close" class="anticon anticon-close ant-modal-close-icon"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></span></button>
<!---->
<div class="ant-modal-body">Here is content of Modal</div>
<div class="ant-modal-footer">
<div><button class="ant-btn" type="button">
<!----><span>Cancel</span>
</button><button class="ant-btn ant-btn-primary" type="button">
<!----><span>OK</span>
</button></div>
</div>
</div>
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
<div></div>
<div class="ant-modal-root">
<div class="ant-modal-mask"></div>
<div tabindex="-1" class="ant-modal-wrap " role="dialog">
<div role="document" style="width: 520px;" class="ant-modal">
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
<div class="ant-modal-content"><button type="button" aria-label="Close" class="ant-modal-close"><span class="ant-modal-close-x"><span role="img" aria-label="close" class="anticon anticon-close ant-modal-close-icon"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></span></button>
<!---->
<div class="ant-modal-body">Here is content of Modal</div>
<div class="ant-modal-footer">
<div><button class="ant-btn" type="button">
<!----><span>Cancel</span>
</button><button class="ant-btn ant-btn-primary" type="button">
<!----><span>OK</span>
</button></div>
</div>
</div>
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
</div>
</div>
</div>
<!--teleport start-->
<!--teleport end-->
</div>
`;
exports[`Modal render correctly 2`] = `
<div>
<div>
<div>
<div class="ant-modal-root">
<div class="ant-modal-mask"></div>
<div tabindex="-1" class="ant-modal-wrap " role="dialog">
<div role="document" style="width: 520px;" class="ant-modal">
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
<div class="ant-modal-content"><button type="button" aria-label="Close" class="ant-modal-close"><span class="ant-modal-close-x"><span role="img" aria-label="close" class="anticon anticon-close ant-modal-close-icon"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></span></button>
<!---->
<div class="ant-modal-body">Here is content of Modal</div>
<div class="ant-modal-footer">
<div><button class="ant-btn" type="button">
<!----><span>Cancel</span>
</button><button class="ant-btn ant-btn-primary" type="button">
<!----><span>OK</span>
</button></div>
</div>
</div>
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
<div></div>
<div class="ant-modal-root">
<div class="ant-modal-mask"></div>
<div tabindex="-1" class="ant-modal-wrap " role="dialog">
<div role="document" style="width: 520px;" class="ant-modal">
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
<div class="ant-modal-content"><button type="button" aria-label="Close" class="ant-modal-close"><span class="ant-modal-close-x"><span role="img" aria-label="close" class="anticon anticon-close ant-modal-close-icon"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></span></button>
<!---->
<div class="ant-modal-body">Here is content of Modal</div>
<div class="ant-modal-footer">
<div><button class="ant-btn" type="button">
<!----><span>Cancel</span>
</button><button class="ant-btn ant-btn-primary" type="button">
<!----><span>OK</span>
</button></div>
</div>
</div>
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
</div>
</div>
</div>
<!--teleport start-->
<!--teleport end-->
</div>
`;
exports[`Modal render without footer 1`] = `
<div>
<div>
<div>
<div class="ant-modal-root">
<div class="ant-modal-mask"></div>
<div tabindex="-1" class="ant-modal-wrap " role="dialog">
<div role="document" style="width: 520px;" class="ant-modal">
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
<div class="ant-modal-content"><button type="button" aria-label="Close" class="ant-modal-close"><span class="ant-modal-close-x"><span role="img" aria-label="close" class="anticon anticon-close ant-modal-close-icon"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></span></button>
<!---->
<div class="ant-modal-body">Here is content of Modal</div>
<!---->
</div>
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
</div>
<div></div>
<div class="ant-modal-root">
<div class="ant-modal-mask"></div>
<div tabindex="-1" class="ant-modal-wrap " role="dialog">
<div role="document" style="width: 520px;" class="ant-modal">
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
<div class="ant-modal-content"><button type="button" aria-label="Close" class="ant-modal-close"><span class="ant-modal-close-x"><span role="img" aria-label="close" class="anticon anticon-close ant-modal-close-icon"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></span></button>
<!---->
<div class="ant-modal-body">Here is content of Modal</div>
<!---->
</div>
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
</div>
</div>
</div>
<!--teleport start-->
<!--teleport end-->
</div>
`;

View File

@ -1,6 +1,7 @@
import Modal from '..';
import { sleep } from '../../../tests/utils';
const { confirm } = Modal;
jest.mock('../../_util/Portal');
describe('Modal.confirm triggers callbacks correctly', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
@ -8,6 +9,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
afterEach(() => {
errorSpy.mockReset();
document.body.innerHTML = '';
Modal.destroyAll();
});
afterAll(() => {
@ -19,11 +21,14 @@ describe('Modal.confirm triggers callbacks correctly', () => {
}
function open(args) {
jest.useFakeTimers();
confirm({
title: 'Want to delete these items?',
content: 'some descriptions',
...args,
});
jest.runAllTimers();
jest.useRealTimers();
}
it('trigger onCancel once when click on cancel button', async () => {

View File

@ -0,0 +1,178 @@
// customize dark components background in popover containers(like Modal, Drawer, Card, Popover, Popconfirm, Notification, ...)
// for dark theme
.popover-customize-bg(@containerClass, @background: @popover-background, @prefix: @ant-prefix)
when
(@theme = dark) {
@picker-prefix-cls: ~'@{prefix}-picker';
@slider-prefix-cls: ~'@{prefix}-slider';
@anchor-prefix-cls: ~'@{prefix}-anchor';
@collapse-prefix-cls: ~'@{prefix}-collapse';
@tab-prefix-cls: ~'@{prefix}-tabs';
@timeline-prefix-cls: ~'@{prefix}-timeline';
@tree-prefix-cls: ~'@{prefix}-tree';
@card-prefix-cls: ~'@{prefix}-card';
@badge-prefix-cls: ~'@{prefix}-badge';
@transfer-prefix-cls: ~'@{prefix}-transfer';
@calendar-prefix-cls: ~'@{prefix}-picker-calendar';
@calendar-picker-prefix-cls: ~'@{prefix}-picker';
@table-prefix-cls: ~'@{prefix}-table';
@popover-border: @border-width-base @border-style-base @popover-customize-border-color;
.@{containerClass} {
.@{picker-prefix-cls}-clear,
.@{slider-prefix-cls}-handle,
.@{anchor-prefix-cls}-wrapper,
.@{collapse-prefix-cls}-content,
.@{timeline-prefix-cls}-item-head,
.@{card-prefix-cls} {
background-color: @background;
}
.@{transfer-prefix-cls} {
&-list {
&-header {
background: @background;
border-bottom: @popover-border;
}
&-content-item:not(.@{transfer-prefix-cls}-list-content-item-disabled):hover {
background-color: @item-hover-bg;
}
}
}
tr.@{table-prefix-cls}-expanded-row {
&,
&:hover {
> td {
background: #272727;
}
}
}
.@{table-prefix-cls}.@{table-prefix-cls}-small {
thead {
> tr {
> th {
background-color: @background;
border-bottom: @popover-border;
}
}
}
}
.@{table-prefix-cls} {
background-color: @background;
.@{table-prefix-cls}-row-expand-icon {
border: @popover-border;
}
tfoot {
> tr {
> th,
> td {
border-bottom: @popover-border;
}
}
}
thead {
> tr {
> th {
background-color: #272727;
border-bottom: @popover-border;
}
}
}
tbody {
> tr {
> td {
border-bottom: @popover-border;
&.@{table-prefix-cls}-cell-fix-left,
&.@{table-prefix-cls}-cell-fix-right {
background-color: @background;
}
}
&.@{table-prefix-cls}-row:hover {
> td {
background: @table-header-sort-active-bg;
}
}
}
}
&.@{table-prefix-cls}-bordered {
.@{table-prefix-cls}-title {
border: @popover-border;
}
// ============================= Cell =============================
thead > tr > th,
tbody > tr > td,
tfoot > tr > th,
tfoot > tr > td {
border-right: @popover-border;
}
// Fixed right should provides additional border
.@{table-prefix-cls}-cell-fix-right-first::after {
border-right: @popover-border;
}
// ============================ Header ============================
table > {
thead {
> tr:not(:last-child) > th {
border-bottom: @border-width-base @border-style-base @border-color-split;
}
}
}
// =========================== Content ============================
.@{table-prefix-cls}-container {
border: @popover-border;
}
// ========================== Expandable ==========================
.@{table-prefix-cls}-expanded-row-fixed {
&::after {
border-right: @popover-border;
}
}
.@{table-prefix-cls}-footer {
border: @popover-border;
}
}
.@{table-prefix-cls}-filter-trigger-container-open {
background-color: #525252;
}
}
.@{calendar-prefix-cls}-full {
background-color: @background;
.@{calendar-picker-prefix-cls}-panel {
background-color: @background;
.@{calendar-prefix-cls}-date {
border-top: 2px solid @popover-customize-border-color;
}
}
}
.@{tab-prefix-cls} {
&.@{tab-prefix-cls}-card .@{tab-prefix-cls}-card-bar .@{tab-prefix-cls}-tab-active {
background-color: @background;
border-bottom: @border-width-base solid @background;
}
}
.@{badge-prefix-cls} {
&-count {
box-shadow: 0 0 0 1px @background;
}
}
.@{tree-prefix-cls} {
&-show-line {
.@{tree-prefix-cls}-switcher {
background: @background;
}
}
}
}
}

View File

@ -8,5 +8,6 @@
@import 'reset';
@import 'operation-unit';
@import 'typography';
@import 'customize';
@import 'box';
@import 'modal-mask';

View File

@ -494,6 +494,9 @@
@modal-footer-bg: transparent;
@modal-footer-border-color-split: @border-color-split;
@modal-mask-bg: fade(@black, 45%);
@modal-close-color: @text-color-secondary;
@modal-footer-padding-vertical: 10px;
@modal-footer-padding-horizontal: 16px;
// Progress
// --
@ -876,9 +879,12 @@
// Drawer
// ---
@drawer-header-padding: 16px 24px;
@drawer-body-padding: 24px;
@drawer-header-padding: @padding-md @padding-lg;
@drawer-body-padding: @padding-lg;
@drawer-bg: @component-background;
@drawer-footer-padding-vertical: @modal-footer-padding-vertical;
@drawer-footer-padding-horizontal: @modal-footer-padding-horizontal;
@drawer-header-close-size: 56px;
// Timeline
// ---

View File

@ -1,12 +1,13 @@
import { mount } from '@vue/test-utils';
import { asyncExpect, sleep } from '../../../tests/utils';
import Table from '..';
jest.mock('../../_util/Portal');
describe('Table.rowSelection', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
afterEach(() => {
errorSpy.mockReset();
document.body.innerHTML = '';
});
afterAll(() => {
@ -582,8 +583,9 @@ describe('Table.rowSelection', () => {
});
// Check Lucy
clickFilter([0, 1]);
clickFilter([0]);
await sleep();
clickFilter([1]);
await asyncExpect(() => {
expect(wrapper.findAll('tbody tr').length).toBe(1);
});

View File

@ -3,25 +3,27 @@
@drawer: drawer;
.@{drawer} {
position: fixed;
top: 0;
z-index: 9999;
> * {
transition: transform @duration @ease-in-out-circ, opacity @duration @ease-in-out-circ,
box-shaow @duration @ease-in-out-circ;
transition: width 0s ease @duration, height 0s ease @duration, transform @duration @ease-in-out-circ;
>* {
transition: transform @duration @ease-in-out-circ, opacity @duration @ease-in-out-circ, box-shadow @duration @ease-in-out-circ;
}
&.@{drawer}-open {
transition: transform @duration @ease-in-out-circ;
}
& &-mask {
background: #000;
opacity: 0;
width: 0;
width: 100%;
height: 0;
position: fixed;
position: absolute;
top: 0;
transition: opacity @duration @ease-in-out-circ, width 0s ease @duration,
left: 0;
transition: opacity @duration @ease-in-out-circ,
height 0s ease @duration;
display: block !important;
}
&-content-wrapper {
position: fixed;
position: absolute;
background: #fff;
}
&-content {
@ -77,41 +79,53 @@
}
&.@{drawer}-open {
width: 100%;
&.no-mask {
width: 0%;
}
}
}
&-left {
top: 0;
left: 0;
.@{drawer} {
&-handle {
right: -40px;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
box-shadow: 2px 0 8px rgba(0, 0, 0, .15);
border-radius: 0 4px 4px 0;
}
}
&.@{drawer}-open {
.@{drawer} {
&-content-wrapper {
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
box-shadow: 2px 0 8px rgba(0, 0, 0, .15);
}
}
}
}
&-right {
top: 0;
right: 0;
.@{drawer} {
&-content-wrapper {
right: 0;
}
&-handle {
left: -40px;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
box-shadow: -2px 0 8px rgba(0, 0, 0, .15);
border-radius: 4px 0 0 4px;
}
}
&.@{drawer}-open {
& .@{drawer} {
&-content-wrapper {
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
box-shadow: -2px 0 8px rgba(0, 0, 0, .15);
}
}
&.no-mask {
// https://github.com/ant-design/ant-design/issues/18607
right: 1px;
transform: translateX(1px);
}
}
}
&-top,
@ -122,60 +136,74 @@
.@{drawer}-content {
width: 100%;
}
.@{drawer}-content {
height: 100%;
}
&.@{drawer}-open {
height: 100%;
&.no-mask {
height: 0%;
}
}
.@{drawer} {
&-handle {
left: 50%;
margin-left: -20px;
}
}
&.@{drawer}-open {
height: 100%;
}
}
&-top {
top: 0;
left: 0;
.@{drawer} {
&-handle {
top: auto;
bottom: -40px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
border-radius: 0 0 4px 4px;
}
}
&.@{drawer}-open {
.@{drawer} {
&-wrapper {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
&-content-wrapper {
box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
}
}
}
}
&-bottom {
bottom: 0;
left: 0;
.@{drawer} {
&-content-wrapper {
bottom: 0;
}
&-handle {
top: -40px;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);
box-shadow: 0 -2px 8px rgba(0, 0, 0, .15);
border-radius: 4px 4px 0 0;
}
}
&.@{drawer}-open {
.@{drawer} {
&-content-wrapper {
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);
box-shadow: 0 -2px 8px rgba(0, 0, 0, .15);
}
}
&.no-mask {
// https://github.com/ant-design/ant-design/issues/18607
bottom: 1px;
transform: translateY(1px);
}
}
}
&.@{drawer}-open {
.@{drawer} {
&-mask {
opacity: 0.3;
width: 100%;
opacity: .3;
height: 100%;
animation: fadeIn 0.3s @ease-in-out-circ;
transition: none;
transition: opacity 0.3s @ease-in-out-circ;
}
&-handle {
&-icon {
@ -190,13 +218,4 @@
}
}
}
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 0.3;
}
}
}

View File

@ -0,0 +1,4 @@
// base rc-drawer 4.4.2
import Drawer from './src/DrawerWrapper';
export default Drawer;

View File

@ -1,620 +0,0 @@
import classnames from '../../_util/classNames';
import { Teleport, nextTick, defineComponent } from 'vue';
import BaseMixin from '../../_util/BaseMixin';
import { initDefaultProps, getSlot } from '../../_util/props-util';
import getScrollBarSize from '../../_util/getScrollBarSize';
import { IDrawerProps } from './IDrawerPropTypes';
import KeyCode from '../../_util/KeyCode';
import {
dataToArray,
transitionEnd,
transitionStr,
addEventListener,
removeEventListener,
transformArguments,
isNumeric,
} from './utils';
import supportsPassive from '../../_util/supportsPassive';
import { cloneElement } from '../../_util/vnode';
function noop() {}
const currentDrawer = {};
const windowIsUndefined = !(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
);
const Drawer = defineComponent({
name: 'Drawer',
mixins: [BaseMixin],
inheritAttrs: false,
props: initDefaultProps(IDrawerProps, {
prefixCls: 'drawer',
placement: 'left',
getContainer: 'body',
level: 'all',
duration: '.3s',
ease: 'cubic-bezier(0.78, 0.14, 0.15, 0.86)',
firstEnter: false, // .
showMask: true,
handler: true,
maskStyle: {},
wrapperClassName: '',
}),
data() {
this.levelDom = [];
this.contentDom = null;
this.maskDom = null;
this.handlerdom = null;
this.mousePos = null;
this.sFirstEnter = this.firstEnter;
this.timeout = null;
this.children = null;
this.dom = null;
this.drawerId = Number(
(Date.now() + Math.random()).toString().replace('.', Math.round(Math.random() * 9)),
).toString(16);
const open = this.open !== undefined ? this.open : !!this.defaultOpen;
currentDrawer[this.drawerId] = open;
this.orignalOpen = this.open;
this.preProps = { ...this.$props };
return {
sOpen: open,
isOpenChange: undefined,
passive: undefined,
container: undefined,
};
},
watch: {
open(val) {
if (val !== undefined && val !== this.preProps.open) {
this.isOpenChange = true;
// dom ;
if (!this.container) {
this.getDefault(this.$props);
}
this.setState({
sOpen: open,
});
}
this.preProps.open = val;
if (val) {
setTimeout(() => {
this.domFocus();
});
}
},
placement(val) {
if (val !== this.preProps.placement) {
// test bug, dom
this.contentDom = null;
}
this.preProps.placement = val;
},
level(val) {
if (this.preProps.level !== val) {
this.getParentAndLevelDom(this.$props);
}
this.preProps.level = val;
},
},
mounted() {
nextTick(() => {
if (!windowIsUndefined) {
this.passive = supportsPassive ? { passive: false } : false;
}
const open = this.getOpen();
if (this.handler || open || this.sFirstEnter) {
this.getDefault(this.$props);
if (open) {
this.isOpenChange = true;
nextTick(() => {
this.domFocus();
});
}
this.$forceUpdate();
}
});
},
updated() {
nextTick(() => {
// dom
if (!this.sFirstEnter && this.container) {
this.$forceUpdate();
this.sFirstEnter = true;
}
});
},
beforeUnmount() {
delete currentDrawer[this.drawerId];
delete this.isOpenChange;
if (this.container) {
if (this.sOpen) {
this.setLevelDomTransform(false, true);
}
document.body.style.overflow = '';
}
this.sFirstEnter = false;
clearTimeout(this.timeout);
},
methods: {
domFocus() {
if (this.dom) {
this.dom.focus();
}
},
onKeyDown(e) {
if (e.keyCode === KeyCode.ESC) {
e.stopPropagation();
this.__emit('close', e);
}
},
onMaskTouchEnd(e) {
this.__emit('close', e);
this.onTouchEnd(e, true);
},
onIconTouchEnd(e) {
this.__emit('handleClick', e);
this.onTouchEnd(e);
},
onTouchEnd(e, close) {
if (this.open !== undefined) {
return;
}
const open = close || this.sOpen;
this.isOpenChange = true;
this.setState({
sOpen: !open,
});
},
onWrapperTransitionEnd(e) {
if (e.target === this.contentWrapper && e.propertyName.match(/transform$/)) {
const open = this.getOpen();
this.dom.style.transition = '';
if (!open && this.getCurrentDrawerSome()) {
document.body.style.overflowX = '';
if (this.maskDom) {
this.maskDom.style.left = '';
this.maskDom.style.width = '';
}
}
if (this.afterVisibleChange) {
this.afterVisibleChange(!!open);
}
}
},
getDefault(props) {
this.getParentAndLevelDom(props);
if (props.getContainer || props.parent) {
this.container = this.defaultGetContainer();
}
},
getCurrentDrawerSome() {
return !Object.keys(currentDrawer).some(key => currentDrawer[key]);
},
getSelfContainer() {
return this.container;
},
getParentAndLevelDom(props) {
if (windowIsUndefined) {
return;
}
const { level, getContainer } = props;
this.levelDom = [];
if (getContainer) {
if (typeof getContainer === 'string') {
const dom = document.querySelectorAll(getContainer)[0];
this.parent = dom;
}
if (typeof getContainer === 'function') {
this.parent = getContainer();
}
if (typeof getContainer === 'object' && getContainer instanceof window.HTMLElement) {
this.parent = getContainer;
}
}
if (!getContainer && this.container) {
this.parent = this.container.parentNode;
}
if (level === 'all') {
const children = Array.prototype.slice.call(this.parent.children);
children.forEach(child => {
if (
child.nodeName !== 'SCRIPT' &&
child.nodeName !== 'STYLE' &&
child.nodeName !== 'LINK' &&
child !== this.container
) {
this.levelDom.push(child);
}
});
} else if (level) {
dataToArray(level).forEach(key => {
document.querySelectorAll(key).forEach(item => {
this.levelDom.push(item);
});
});
}
},
setLevelDomTransform(open, openTransition, placementName, value) {
const { placement, levelMove, duration, ease, getContainer } = this.$props;
if (!windowIsUndefined) {
this.levelDom.forEach(dom => {
if (dom && (this.isOpenChange || openTransition)) {
/* eslint no-param-reassign: "error" */
dom.style.transition = `transform ${duration} ${ease}`;
addEventListener(dom, transitionEnd, this.trnasitionEnd);
let levelValue = open ? value : 0;
if (levelMove) {
const $levelMove = transformArguments(levelMove, { target: dom, open });
levelValue = open ? $levelMove[0] : $levelMove[1] || 0;
}
const $value = typeof levelValue === 'number' ? `${levelValue}px` : levelValue;
const placementPos =
placement === 'left' || placement === 'top' ? $value : `-${$value}`;
dom.style.transform = levelValue ? `${placementName}(${placementPos})` : '';
dom.style.msTransform = levelValue ? `${placementName}(${placementPos})` : '';
}
});
// body
if (getContainer === 'body') {
const eventArray = ['touchstart'];
const domArray = [document.body, this.maskDom, this.handlerdom, this.contentDom];
const right =
document.body.scrollHeight >
(window.innerHeight || document.documentElement.clientHeight) &&
window.innerWidth > document.body.offsetWidth
? getScrollBarSize(1)
: 0;
let widthTransition = `width ${duration} ${ease}`;
const trannsformTransition = `transform ${duration} ${ease}`;
if (open && document.body.style.overflow !== 'hidden') {
document.body.style.overflow = 'hidden';
if (right) {
document.body.style.position = 'relative';
document.body.style.width = `calc(100% - ${right}px)`;
clearTimeout(this.timeout);
if (this.dom) {
this.dom.style.transition = 'none';
switch (placement) {
case 'right':
this.dom.style.transform = `translateX(-${right}px)`;
this.dom.style.msTransform = `translateX(-${right}px)`;
break;
case 'top':
case 'bottom':
this.dom.style.width = `calc(100% - ${right}px)`;
this.dom.style.transform = 'translateZ(0)';
break;
default:
break;
}
this.timeout = setTimeout(() => {
this.dom.style.transition = `${trannsformTransition},${widthTransition}`;
this.dom.style.width = '';
this.dom.style.transform = '';
this.dom.style.msTransform = '';
});
}
}
//
domArray.forEach((item, i) => {
if (!item) {
return;
}
addEventListener(
item,
eventArray[i] || 'touchmove',
i ? this.removeMoveHandler : this.removeStartHandler,
this.passive,
);
});
} else if (this.getCurrentDrawerSome()) {
document.body.style.overflow = '';
if ((this.isOpenChange || openTransition) && right) {
document.body.style.position = '';
document.body.style.width = '';
if (transitionStr) {
document.body.style.overflowX = 'hidden';
}
if (placement === 'right' && this.maskDom) {
this.maskDom.style.left = `-${right}px`;
this.maskDom.style.width = `calc(100% + ${right}px)`;
}
clearTimeout(this.timeout);
if (this.dom) {
this.dom.style.transition = 'none';
let heightTransition;
switch (placement) {
case 'right': {
this.dom.style.transform = `translateX(${right}px)`;
this.dom.style.msTransform = `translateX(${right}px)`;
this.dom.style.width = '100%';
widthTransition = `width 0s ${ease} ${duration}`;
break;
}
case 'top':
case 'bottom': {
this.dom.style.width = `calc(100% + ${right}px)`;
this.dom.style.height = '100%';
this.dom.style.transform = 'translateZ(0)';
heightTransition = `height 0s ${ease} ${duration}`;
break;
}
default:
break;
}
this.timeout = setTimeout(() => {
this.dom.style.transition = `${trannsformTransition},${
heightTransition ? `${heightTransition},` : ''
}${widthTransition}`;
this.dom.style.transform = '';
this.dom.style.msTransform = '';
this.dom.style.width = '';
this.dom.style.height = '';
});
}
}
domArray.forEach((item, i) => {
if (!item) {
return;
}
removeEventListener(
item,
eventArray[i] || 'touchmove',
i ? this.removeMoveHandler : this.removeStartHandler,
this.passive,
);
});
}
}
}
const { onChange } = this.$attrs;
if (onChange && this.isOpenChange && this.sFirstEnter) {
onChange(open);
this.isOpenChange = false;
}
},
getChildToRender(open) {
const {
prefixCls,
placement,
handler,
showMask,
maskStyle,
width,
height,
wrapStyle,
keyboard,
maskClosable,
} = this.$props;
const { class: cls, style, ...restAttrs } = this.$attrs;
const children = getSlot(this);
const wrapperClassname = classnames(prefixCls, {
[`${prefixCls}-${placement}`]: true,
[`${prefixCls}-open`]: open,
'no-mask': !showMask,
[cls]: cls,
});
const isOpenChange = this.isOpenChange;
const isHorizontal = placement === 'left' || placement === 'right';
const placementName = `translate${isHorizontal ? 'X' : 'Y'}`;
//
// const defaultValue = !this.contentDom || !level ? '100%' : `${value}px`;
const placementPos = placement === 'left' || placement === 'top' ? '-100%' : '100%';
const transform = open ? '' : `${placementName}(${placementPos})`;
if (isOpenChange === undefined || isOpenChange) {
const contentValue = this.contentDom
? this.contentDom.getBoundingClientRect()[isHorizontal ? 'width' : 'height']
: 0;
const value = (isHorizontal ? width : height) || contentValue;
this.setLevelDomTransform(open, false, placementName, value);
}
let handlerChildren;
if (handler !== false) {
const handlerDefalut = (
<div class="drawer-handle" onClick={() => {}}>
<i class="drawer-handle-icon" />
</div>
);
const { handler: handlerSlot } = this;
const handlerSlotVnode = handlerSlot || handlerDefalut;
const handleIconClick = handlerSlotVnode.props && handlerSlotVnode.props.onClick;
handlerChildren = cloneElement(handlerSlotVnode, {
onClick: e => {
handleIconClick && handleIconClick(e);
this.onIconTouchEnd(e);
},
ref: c => {
this.handlerdom = c;
},
});
}
const domContProps = {
...restAttrs,
class: wrapperClassname,
onTransitionend: this.onWrapperTransitionEnd,
onKeydown: open && keyboard ? this.onKeyDown : noop,
style: { ...wrapStyle, ...style },
};
//
const touchEvents = {
[supportsPassive ? 'onTouchstartPassive' : 'onTouchstart']: open
? this.removeStartHandler
: noop,
[supportsPassive ? 'onTouchmovePassive' : 'onTouchmove']: open
? this.removeMoveHandler
: noop,
};
return (
<div
ref={c => {
this.dom = c;
}}
{...domContProps}
tabindex={-1}
>
{showMask && (
<div
key={open} // 第二次渲染时虚拟DOM没有改变没有出发dom更新使用key强制更新 https://github.com/vueComponent/ant-design-vue/issues/2407
class={`${prefixCls}-mask`}
onClick={maskClosable ? this.onMaskTouchEnd : noop}
style={maskStyle}
ref={c => {
this.maskDom = c;
}}
/>
)}
<div
class={`${prefixCls}-content-wrapper`}
style={{
transform,
msTransform: transform,
width: isNumeric(width) ? `${width}px` : width,
height: isNumeric(height) ? `${height}px` : height,
}}
ref={c => {
this.contentWrapper = c;
}}
>
<div
class={`${prefixCls}-content`}
ref={c => {
this.contentDom = c;
}}
{...touchEvents}
>
{children}
</div>
{handlerChildren}
</div>
</div>
);
},
getOpen() {
return this.open !== undefined ? this.open : this.sOpen;
},
getTouchParentScroll(root, currentTarget, differX, differY) {
if (!currentTarget || currentTarget === document) {
return false;
}
// root drawer-content overflow, root parent
if (currentTarget === root.parentNode) {
return true;
}
const isY = Math.max(Math.abs(differX), Math.abs(differY)) === Math.abs(differY);
const isX = Math.max(Math.abs(differX), Math.abs(differY)) === Math.abs(differX);
const scrollY = currentTarget.scrollHeight - currentTarget.clientHeight;
const scrollX = currentTarget.scrollWidth - currentTarget.clientWidth;
/**
* <div style="height: 300px">
* <div style="height: 900px"></div>
* </div>
* 在没设定 overflow: auto scroll currentTarget 里获取不到 scrollTop scrollLeft,
* 预先用 scrollTo 来滚动如果取出的值跟滚动前取出不同 currnetTarget 被设定了 overflow; 否则就是上面这种
*/
const t = currentTarget.scrollTop;
const l = currentTarget.scrollLeft;
if (currentTarget.scrollTo) {
currentTarget.scrollTo(currentTarget.scrollLeft + 1, currentTarget.scrollTop + 1);
}
const currentT = currentTarget.scrollTop;
const currentL = currentTarget.scrollLeft;
if (currentTarget.scrollTo) {
currentTarget.scrollTo(currentTarget.scrollLeft - 1, currentTarget.scrollTop - 1);
}
if (
(isY &&
(!scrollY ||
!(currentT - t) ||
(scrollY &&
((currentTarget.scrollTop >= scrollY && differY < 0) ||
(currentTarget.scrollTop <= 0 && differY > 0))))) ||
(isX &&
(!scrollX ||
!(currentL - l) ||
(scrollX &&
((currentTarget.scrollLeft >= scrollX && differX < 0) ||
(currentTarget.scrollLeft <= 0 && differX > 0)))))
) {
return this.getTouchParentScroll(root, currentTarget.parentNode, differX, differY);
}
return false;
},
removeStartHandler(e) {
if (e.touches.length > 1) {
return;
}
this.startPos = {
x: e.touches[0].clientX,
y: e.touches[0].clientY,
};
},
removeMoveHandler(e) {
if (e.changedTouches.length > 1) {
return;
}
const currentTarget = e.currentTarget;
const differX = e.changedTouches[0].clientX - this.startPos.x;
const differY = e.changedTouches[0].clientY - this.startPos.y;
if (
currentTarget === this.maskDom ||
currentTarget === this.handlerdom ||
(currentTarget === this.contentDom &&
this.getTouchParentScroll(currentTarget, e.target, differX, differY))
) {
e.preventDefault();
}
},
trnasitionEnd(e) {
removeEventListener(e.target, transitionEnd, this.trnasitionEnd);
e.target.style.transition = '';
},
defaultGetContainer() {
if (windowIsUndefined) {
return null;
}
const container = document.createElement('div');
this.parent.appendChild(container);
if (this.wrapperClassName) {
container.className = this.wrapperClassName;
}
return container;
},
},
render() {
const { getContainer, wrapperClassName, handler, forceRender } = this.$props;
const open = this.getOpen();
let portal = null;
currentDrawer[this.drawerId] = open ? this.container : open;
const children = this.getChildToRender(this.sFirstEnter ? open : false);
if (!getContainer) {
return (
<div
class={wrapperClassName}
ref={c => {
this.container = c;
}}
>
{children}
</div>
);
}
if (!this.container || (!open && !this.sFirstEnter)) {
return null;
}
// handler
const $forceRender = !!handler || forceRender;
if ($forceRender || open || this.dom) {
portal = <Teleport to={this.getSelfContainer()}>{children}</Teleport>;
}
return portal;
},
});
export default Drawer;

View File

@ -0,0 +1,505 @@
import {
defineComponent,
reactive,
onMounted,
computed,
onUnmounted,
nextTick,
watch,
ref,
} from 'vue';
import classnames from '../../_util/classNames';
import getScrollBarSize from '../../_util/getScrollBarSize';
import KeyCode from '../../_util/KeyCode';
import omit from '../../_util/omit';
import supportsPassive from '../../_util/supportsPassive';
import { drawerChildProps } from './IDrawerPropTypes';
import {
addEventListener,
dataToArray,
getTouchParentScroll,
isNumeric,
removeEventListener,
transformArguments,
transitionEndFun,
windowIsUndefined,
} from './utils';
const currentDrawer: Record<string, boolean> = {};
export interface scrollLockOptions {
container: HTMLElement;
}
const DrawerChild = defineComponent({
inheritAttrs: false,
props: drawerChildProps(),
emits: ['close', 'handleClick', 'change'],
setup(props, { emit, slots }) {
const state = reactive({
startPos: {
x: null,
y: null,
},
});
let timeout;
const contentWrapper = ref<HTMLElement>();
const dom = ref<HTMLElement>();
const maskDom = ref<HTMLElement>();
const handlerDom = ref<HTMLElement>();
const contentDom = ref<HTMLElement>();
let levelDom = [];
const drawerId = `drawer_id_${Number(
(Date.now() + Math.random())
.toString()
.replace('.', Math.round(Math.random() * 9).toString()),
).toString(16)}`;
const passive = !windowIsUndefined && supportsPassive ? { passive: false } : false;
onMounted(() => {
nextTick(() => {
const { open, getContainer, showMask, autofocus } = props;
const container = getContainer?.();
getLevelDom(props);
if (open) {
if (container && container.parentNode === document.body) {
currentDrawer[drawerId] = open;
}
// level;
openLevelTransition();
nextTick(() => {
if (autofocus) {
domFocus();
}
});
if (showMask) {
props.scrollLocker?.lock();
}
}
});
});
watch(
() => props.level,
() => {
getLevelDom(props);
},
{ flush: 'post' },
);
watch(
() => props.open,
() => {
const { open, getContainer, scrollLocker, showMask, autofocus } = props;
const container = getContainer?.();
if (container && container.parentNode === document.body) {
currentDrawer[drawerId] = !!open;
}
openLevelTransition();
if (open) {
if (autofocus) {
domFocus();
}
if (showMask) {
scrollLocker?.lock();
}
} else {
scrollLocker?.unLock();
}
},
{ flush: 'post' },
);
onUnmounted(() => {
const { open } = props;
delete currentDrawer[drawerId];
if (open) {
setLevelTransform(false);
document.body.style.touchAction = '';
}
props.scrollLocker?.unLock();
});
watch(
() => props.placement,
val => {
if (val) {
// test bug, dom
contentDom.value = null;
}
},
);
const domFocus = () => {
dom.value?.focus?.();
};
const removeStartHandler = (e: TouchEvent) => {
if (e.touches.length > 1) {
return;
}
state.startPos = {
x: e.touches[0].clientX,
y: e.touches[0].clientY,
};
};
const removeMoveHandler = (e: TouchEvent) => {
if (e.changedTouches.length > 1) {
return;
}
const currentTarget = e.currentTarget as HTMLElement;
const differX = e.changedTouches[0].clientX - state.startPos.x;
const differY = e.changedTouches[0].clientY - state.startPos.y;
if (
(currentTarget === maskDom.value ||
currentTarget === handlerDom.value ||
(currentTarget === contentDom.value &&
getTouchParentScroll(currentTarget, e.target as HTMLElement, differX, differY))) &&
e.cancelable
) {
e.preventDefault();
}
};
const transitionEnd = (e: TransitionEvent) => {
const dom: HTMLElement = e.target as HTMLElement;
removeEventListener(dom, transitionEndFun, transitionEnd);
dom.style.transition = '';
};
const onClose = (e: Event) => {
emit('close', e);
};
const onKeyDown = (e: KeyboardEvent) => {
if (e.keyCode === KeyCode.ESC) {
e.stopPropagation();
onClose(e);
}
};
const onWrapperTransitionEnd = (e: TransitionEvent) => {
const { open, afterVisibleChange } = props;
if (e.target === contentWrapper.value && e.propertyName.match(/transform$/)) {
dom.value.style.transition = '';
if (!open && getCurrentDrawerSome()) {
document.body.style.overflowX = '';
if (maskDom.value) {
maskDom.value.style.left = '';
maskDom.value.style.width = '';
}
}
if (afterVisibleChange) {
afterVisibleChange(!!open);
}
}
};
const horizontalBoolAndPlacementName = computed(() => {
const { placement } = props;
const isHorizontal = placement === 'left' || placement === 'right';
const placementName = `translate${isHorizontal ? 'X' : 'Y'}`;
return {
isHorizontal,
placementName,
};
});
const openLevelTransition = () => {
const { open, width, height } = props;
const { isHorizontal, placementName } = horizontalBoolAndPlacementName.value;
const contentValue = contentDom.value
? contentDom.value.getBoundingClientRect()[isHorizontal ? 'width' : 'height']
: 0;
const value = (isHorizontal ? width : height) || contentValue;
setLevelAndScrolling(open, placementName, value);
};
const setLevelTransform = (
open?: boolean,
placementName?: string,
value?: string | number,
right?: number,
) => {
const { placement, levelMove, duration, ease, showMask } = props;
// router
levelDom.forEach(dom => {
dom.style.transition = `transform ${duration} ${ease}`;
addEventListener(dom, transitionEndFun, transitionEnd);
let levelValue = open ? value : 0;
if (levelMove) {
const $levelMove = transformArguments(levelMove, { target: dom, open });
levelValue = open ? $levelMove[0] : $levelMove[1] || 0;
}
const $value = typeof levelValue === 'number' ? `${levelValue}px` : levelValue;
let placementPos = placement === 'left' || placement === 'top' ? $value : `-${$value}`;
placementPos =
showMask && placement === 'right' && right
? `calc(${placementPos} + ${right}px)`
: placementPos;
dom.style.transform = levelValue ? `${placementName}(${placementPos})` : '';
});
};
const setLevelAndScrolling = (
open?: boolean,
placementName?: string,
value?: string | number,
) => {
if (!windowIsUndefined) {
const right =
document.body.scrollHeight >
(window.innerHeight || document.documentElement.clientHeight) &&
window.innerWidth > document.body.offsetWidth
? getScrollBarSize(true)
: 0;
setLevelTransform(open, placementName, value, right);
toggleScrollingToDrawerAndBody(right);
}
emit('change', open);
};
const toggleScrollingToDrawerAndBody = (right: number) => {
const { getContainer, showMask, open } = props;
const container = getContainer?.();
// body
if (container && container.parentNode === document.body && showMask) {
const eventArray = ['touchstart'];
const domArray = [document.body, maskDom.value, handlerDom.value, contentDom.value];
if (open && document.body.style.overflow !== 'hidden') {
if (right) {
addScrollingEffect(right);
}
document.body.style.touchAction = 'none';
//
domArray.forEach((item, i) => {
if (!item) {
return;
}
addEventListener(
item,
eventArray[i] || 'touchmove',
i ? removeMoveHandler : removeStartHandler,
passive,
);
});
} else if (getCurrentDrawerSome()) {
document.body.style.touchAction = '';
if (right) {
remScrollingEffect(right);
}
//
domArray.forEach((item, i) => {
if (!item) {
return;
}
removeEventListener(
item,
eventArray[i] || 'touchmove',
i ? removeMoveHandler : removeStartHandler,
passive,
);
});
}
}
};
const addScrollingEffect = (right: number) => {
const { placement, duration, ease } = props;
const widthTransition = `width ${duration} ${ease}`;
const transformTransition = `transform ${duration} ${ease}`;
dom.value.style.transition = 'none';
switch (placement) {
case 'right':
dom.value.style.transform = `translateX(-${right}px)`;
break;
case 'top':
case 'bottom':
dom.value.style.width = `calc(100% - ${right}px)`;
dom.value.style.transform = 'translateZ(0)';
break;
default:
break;
}
clearTimeout(timeout);
timeout = setTimeout(() => {
if (dom.value) {
dom.value.style.transition = `${transformTransition},${widthTransition}`;
dom.value.style.width = '';
dom.value.style.transform = '';
}
});
};
const remScrollingEffect = (right: number) => {
const { placement, duration, ease } = props;
dom.value.style.transition = 'none';
let heightTransition: string;
let widthTransition = `width ${duration} ${ease}`;
const transformTransition = `transform ${duration} ${ease}`;
switch (placement) {
case 'left': {
dom.value.style.width = '100%';
widthTransition = `width 0s ${ease} ${duration}`;
break;
}
case 'right': {
dom.value.style.transform = `translateX(${right}px)`;
dom.value.style.width = '100%';
widthTransition = `width 0s ${ease} ${duration}`;
if (maskDom.value) {
maskDom.value.style.left = `-${right}px`;
maskDom.value.style.width = `calc(100% + ${right}px)`;
}
break;
}
case 'top':
case 'bottom': {
dom.value.style.width = `calc(100% + ${right}px)`;
dom.value.style.height = '100%';
dom.value.style.transform = 'translateZ(0)';
heightTransition = `height 0s ${ease} ${duration}`;
break;
}
default:
break;
}
clearTimeout(timeout);
timeout = setTimeout(() => {
if (dom.value) {
dom.value.style.transition = `${transformTransition},${
heightTransition ? `${heightTransition},` : ''
}${widthTransition}`;
dom.value.style.transform = '';
dom.value.style.width = '';
dom.value.style.height = '';
}
});
};
const getCurrentDrawerSome = () => !Object.keys(currentDrawer).some(key => currentDrawer[key]);
const getLevelDom = ({ level, getContainer }) => {
if (windowIsUndefined) {
return;
}
const container = getContainer?.();
const parent = container ? (container.parentNode as HTMLElement) : null;
levelDom = [];
if (level === 'all') {
const children: HTMLElement[] = parent ? Array.prototype.slice.call(parent.children) : [];
children.forEach((child: HTMLElement) => {
if (
child.nodeName !== 'SCRIPT' &&
child.nodeName !== 'STYLE' &&
child.nodeName !== 'LINK' &&
child !== container
) {
levelDom.push(child);
}
});
} else if (level) {
dataToArray(level).forEach(key => {
document.querySelectorAll(key).forEach(item => {
levelDom.push(item);
});
});
}
};
const onHandleClick = e => {
emit('handleClick', e);
};
const canOpen = ref(false);
watch(dom, () => {
nextTick(() => {
canOpen.value = true;
});
});
return () => {
const {
width,
height,
open: $open,
prefixCls,
placement,
level,
levelMove,
ease,
duration,
getContainer,
onChange,
afterVisibleChange,
showMask,
maskClosable,
maskStyle,
keyboard,
getOpenCount,
scrollLocker,
contentWrapperStyle,
style,
class: className,
...otherProps
} = props;
//
const open = $open && canOpen.value;
const wrapperClassName = classnames(prefixCls, {
[`${prefixCls}-${placement}`]: true,
[`${prefixCls}-open`]: open,
[className]: !!className,
'no-mask': !showMask,
});
const { placementName } = horizontalBoolAndPlacementName.value;
//
// const defaultValue = !this.contentDom || !level ? '100%' : `${value}px`;
const placementPos = placement === 'left' || placement === 'top' ? '-100%' : '100%';
const transform = open ? '' : `${placementName}(${placementPos})`;
return (
<div
{...omit(otherProps, ['switchScrollingEffect', 'autofocus'])}
tabindex={-1}
class={wrapperClassName}
style={style}
ref={dom}
onKeydown={open && keyboard ? onKeyDown : undefined}
onTransitionend={onWrapperTransitionEnd}
>
{showMask && (
<div
class={`${prefixCls}-mask`}
onClick={maskClosable ? onClose : undefined}
style={maskStyle}
ref={maskDom}
/>
)}
<div
class={`${prefixCls}-content-wrapper`}
style={{
transform,
msTransform: transform,
width: isNumeric(width) ? `${width}px` : width,
height: isNumeric(height) ? `${height}px` : height,
...contentWrapperStyle,
}}
ref={contentWrapper}
>
<div class={`${prefixCls}-content`} ref={contentDom}>
{slots.default?.()}
</div>
{slots.handler ? (
<div onClick={onHandleClick} ref={handlerDom}>
{slots.handler?.()}
</div>
) : null}
</div>
</div>
);
};
},
});
export default DrawerChild;

View File

@ -0,0 +1,91 @@
import Child from './DrawerChild';
import { initDefaultProps } from '../../_util/props-util';
import { defineComponent, ref } from 'vue';
import { drawerProps } from './IDrawerPropTypes';
import PortalWrapper from '../../_util/PortalWrapper';
const DrawerWrapper = defineComponent({
inheritAttrs: false,
props: initDefaultProps(drawerProps(), {
prefixCls: 'drawer',
placement: 'left',
getContainer: 'body',
level: 'all',
duration: '.3s',
ease: 'cubic-bezier(0.78, 0.14, 0.15, 0.86)',
afterVisibleChange: () => {},
showMask: true,
maskClosable: true,
maskStyle: {},
wrapperClassName: '',
keyboard: true,
forceRender: false,
autofocus: true,
}),
emits: ['handleClick', 'close'],
slots: ['handler'],
setup(props, { emit, slots }) {
const dom = ref<HTMLElement>(null);
const onHandleClick = (e: MouseEvent | KeyboardEvent) => {
emit('handleClick', e);
};
const onClose = (e: MouseEvent | KeyboardEvent) => {
emit('close', e);
};
return () => {
const { afterVisibleChange, getContainer, wrapperClassName, forceRender, ...otherProps } =
props;
let portal = null;
if (!getContainer) {
return (
<div class={wrapperClassName} ref={dom}>
<Child
v-slots={slots}
{...otherProps}
open={props.open}
getContainer={() => dom.value}
onClose={onClose}
onHandleClick={onHandleClick}
></Child>
</div>
);
}
// handler
const $forceRender = !!slots.handler || forceRender;
if ($forceRender || props.open || dom.value) {
portal = (
<PortalWrapper
visible={props.open}
forceRender={$forceRender}
getContainer={getContainer}
wrapperClassName={wrapperClassName}
v-slots={{
default: ({ visible, afterClose, ...rest }) => (
<Child
ref={dom}
v-slots={slots}
{...otherProps}
{...rest}
open={visible !== undefined ? visible : props.open}
afterVisibleChange={
afterClose !== undefined ? afterClose : props.afterVisibleChange
}
onClose={onClose}
onHandleClick={onHandleClick}
/>
),
}}
></PortalWrapper>
);
}
return portal;
};
},
});
export default DrawerWrapper;

View File

@ -1,44 +0,0 @@
import PropTypes from '../../_util/vue-types';
const IProps = {
width: PropTypes.any,
height: PropTypes.any,
defaultOpen: PropTypes.looseBool,
firstEnter: PropTypes.looseBool,
open: PropTypes.looseBool,
prefixCls: PropTypes.string,
placement: PropTypes.string,
level: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
levelMove: PropTypes.oneOfType([PropTypes.number, PropTypes.func, PropTypes.array]),
ease: PropTypes.string,
duration: PropTypes.string,
handler: PropTypes.any,
showMask: PropTypes.looseBool,
maskStyle: PropTypes.object,
className: PropTypes.string,
wrapStyle: PropTypes.object,
maskClosable: PropTypes.looseBool,
afterVisibleChange: PropTypes.func,
keyboard: PropTypes.looseBool,
};
const IDrawerProps = {
...IProps,
wrapperClassName: PropTypes.string,
forceRender: PropTypes.looseBool,
getContainer: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
PropTypes.object,
PropTypes.looseBool,
]),
};
const IDrawerChildProps = {
...IProps,
getContainer: PropTypes.func,
getOpenCount: PropTypes.func,
switchScrollingEffect: PropTypes.func,
};
export { IDrawerProps, IDrawerChildProps };

View File

@ -0,0 +1,53 @@
import PropTypes from '../../_util/vue-types';
import type { PropType } from 'vue';
export type IPlacement = 'left' | 'top' | 'right' | 'bottom';
type ILevelMove = number | [number, number];
const props = () => ({
prefixCls: PropTypes.string,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
style: PropTypes.style,
class: PropTypes.string,
placement: {
type: String as PropType<IPlacement>,
},
wrapperClassName: PropTypes.string,
level: { type: [String, Array] as PropType<string | string[]> },
levelMove: {
type: [Number, Function, Array] as PropType<
ILevelMove | ((e: { target: HTMLElement; open: boolean }) => ILevelMove)
>,
},
duration: PropTypes.string,
ease: PropTypes.string,
showMask: PropTypes.looseBool,
maskClosable: PropTypes.looseBool,
maskStyle: PropTypes.style,
afterVisibleChange: PropTypes.func,
keyboard: PropTypes.looseBool,
contentWrapperStyle: PropTypes.style,
autofocus: PropTypes.looseBool,
open: PropTypes.looseBool,
});
const drawerProps = () => ({
...props(),
forceRender: PropTypes.looseBool,
getContainer: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
PropTypes.object,
PropTypes.looseBool,
]),
});
const drawerChildProps = () => ({
...props(),
getContainer: PropTypes.func,
getOpenCount: PropTypes.func,
scrollLocker: PropTypes.any,
switchScrollingEffect: PropTypes.func,
});
export { drawerProps, drawerChildProps };

View File

@ -1,5 +0,0 @@
// base in 1.7.7
// export this package's api
import Drawer from './Drawer';
export default Drawer;

View File

@ -1,47 +1,54 @@
export function dataToArray(vars) {
export function dataToArray(vars: any) {
if (Array.isArray(vars)) {
return vars;
}
return [vars];
}
const transitionEndObject = {
const transitionEndObject: Record<string, string> = {
transition: 'transitionend',
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd otransitionend',
};
export const transitionStr = Object.keys(transitionEndObject).filter(key => {
export const transitionStr: string = Object.keys(transitionEndObject).filter(key => {
if (typeof document === 'undefined') {
return false;
}
const html = document.getElementsByTagName('html')[0];
return key in (html ? html.style : {});
})[0];
export const transitionEnd = transitionEndObject[transitionStr];
export const transitionEndFun: string = transitionEndObject[transitionStr];
export function addEventListener(target, eventType, callback, options) {
export function addEventListener(
target: HTMLElement,
eventType: string,
callback: (e: TouchEvent | Event) => void,
options?: any,
) {
if (target.addEventListener) {
target.addEventListener(eventType, callback, options);
} else if (target.attachEvent) {
target.attachEvent(`on${eventType}`, callback);
} else if ((target as any).attachEvent) {
// tslint:disable-line
(target as any).attachEvent(`on${eventType}`, callback); // tslint:disable-line
}
}
export function removeEventListener(target, eventType, callback, options) {
export function removeEventListener(
target: HTMLElement,
eventType: string,
callback: (e: TouchEvent | Event) => void,
options?: any,
) {
if (target.removeEventListener) {
target.removeEventListener(eventType, callback, options);
} else if (target.attachEvent) {
target.detachEvent(`on${eventType}`, callback);
} else if ((target as any).attachEvent) {
// tslint:disable-line
(target as any).detachEvent(`on${eventType}`, callback); // tslint:disable-line
}
}
export function transformArguments(arg, cb) {
let result;
if (typeof arg === 'function') {
result = arg(cb);
} else {
result = arg;
}
export function transformArguments(arg: any, cb: any) {
const result = typeof arg === 'function' ? arg(cb) : arg;
if (Array.isArray(result)) {
if (result.length === 2) {
return result;
@ -51,9 +58,8 @@ export function transformArguments(arg, cb) {
return [result];
}
export const isNumeric = value => {
return !isNaN(parseFloat(value)) && isFinite(value); // eslint-disable-line
};
export const isNumeric = (value: string | number | undefined) =>
!isNaN(parseFloat(value as string)) && isFinite(value as number);
export const windowIsUndefined = !(
typeof window !== 'undefined' &&
@ -61,7 +67,12 @@ export const windowIsUndefined = !(
window.document.createElement
);
export const getTouchParentScroll = (root, currentTarget, differX, differY) => {
export const getTouchParentScroll = (
root: HTMLElement,
currentTarget: HTMLElement | Document | null,
differX: number,
differY: number,
): boolean => {
if (!currentTarget || currentTarget === document || currentTarget instanceof Document) {
return false;
}
@ -92,10 +103,10 @@ export const getTouchParentScroll = (root, currentTarget, differX, differY) => {
(isX &&
(!x ||
(x &&
((currentTarget.scrollLeft >= scrollX && scrollX < 0) ||
(currentTarget.scrollLeft <= 0 && scrollX > 0)))))
((currentTarget.scrollLeft >= scrollX && differX < 0) ||
(currentTarget.scrollLeft <= 0 && differX > 0)))))
) {
return getTouchParentScroll(root, currentTarget.parentNode, differX, differY);
return getTouchParentScroll(root, currentTarget.parentNode as HTMLElement, differX, differY);
}
return false;
};

View File

@ -1,5 +1,5 @@
import type { Ref } from 'vue';
import { nextTick, onBeforeUnmount, ref, watch } from 'vue';
import { nextTick, onBeforeUnmount, ref, watch, onMounted } from 'vue';
import raf from '../../_util/raf';
/**
@ -62,29 +62,32 @@ export default (
},
{ immediate: true, flush: 'post' },
);
// Go next status
watch(
status,
() => {
switch (status.value) {
case 'measure':
doMeasure();
break;
default:
}
onMounted(() => {
// Go next status
watch(
status,
() => {
switch (status.value) {
case 'measure':
doMeasure();
break;
default:
}
if (status.value) {
nextTick(() => {
const index = StatusQueue.indexOf(status.value);
const nextStatus = StatusQueue[index + 1];
if (nextStatus && index !== -1) {
setStatus(nextStatus);
}
});
}
},
{ immediate: true, flush: 'post' },
);
});
if (status.value) {
nextTick(() => {
const index = StatusQueue.indexOf(status.value);
const nextStatus = StatusQueue[index + 1];
if (nextStatus && index !== -1) {
setStatus(nextStatus);
}
});
}
},
{ immediate: true, flush: 'post' },
);
onBeforeUnmount(() => {
destroyRef.value = true;
cancelRaf();

View File

@ -0,0 +1,130 @@
import type { CSSProperties } from '@vue/runtime-dom';
import getScrollBarSize from '../../_util/getScrollBarSize';
import setStyle from '../../_util/setStyle';
export interface scrollLockOptions {
container: HTMLElement;
}
interface Ilocks {
target: typeof uuid;
options: scrollLockOptions;
}
let locks: Ilocks[] = [];
const scrollingEffectClassName = 'ant-scrolling-effect';
const scrollingEffectClassNameReg = new RegExp(`${scrollingEffectClassName}`, 'g');
let uuid = 0;
// https://github.com/ant-design/ant-design/issues/19340
// https://github.com/ant-design/ant-design/issues/19332
const cacheStyle = new Map<Element, CSSProperties>();
export default class ScrollLocker {
private lockTarget: typeof uuid;
private options: scrollLockOptions;
constructor(options?: scrollLockOptions) {
// eslint-disable-next-line no-plusplus
this.lockTarget = uuid++;
this.options = options;
}
getContainer = (): HTMLElement | undefined => {
return this.options?.container;
};
// if options change...
reLock = (options?: scrollLockOptions) => {
const findLock = locks.find(({ target }) => target === this.lockTarget);
if (findLock) {
this.unLock();
}
this.options = options;
if (findLock) {
findLock.options = options;
this.lock();
}
};
lock = () => {
// If lockTarget exist return
if (locks.some(({ target }) => target === this.lockTarget)) {
return;
}
// If same container effect, return
if (locks.some(({ options }) => options?.container === this.options?.container)) {
locks = [...locks, { target: this.lockTarget, options: this.options }];
return;
}
let scrollBarSize = 0;
const container = this.options?.container || document.body;
if (
(container === document.body &&
window.innerWidth - document.documentElement.clientWidth > 0) ||
container.scrollHeight > container.clientHeight
) {
scrollBarSize = getScrollBarSize();
}
const containerClassName = container.className;
if (
locks.filter(({ options }) => options?.container === this.options?.container).length === 0
) {
cacheStyle.set(
container,
setStyle(
{
width: scrollBarSize !== 0 ? `calc(100% - ${scrollBarSize}px)` : undefined,
overflow: 'hidden',
overflowX: 'hidden',
overflowY: 'hidden',
},
{
element: container,
},
),
);
}
// https://github.com/ant-design/ant-design/issues/19729
if (!scrollingEffectClassNameReg.test(containerClassName)) {
const addClassName = `${containerClassName} ${scrollingEffectClassName}`;
container.className = addClassName.trim();
}
locks = [...locks, { target: this.lockTarget, options: this.options }];
};
unLock = () => {
const findLock = locks.find(({ target }) => target === this.lockTarget);
locks = locks.filter(({ target }) => target !== this.lockTarget);
if (
!findLock ||
locks.some(({ options }) => options?.container === findLock.options?.container)
) {
return;
}
// Remove Effect
const container = this.options?.container || document.body;
const containerClassName = container.className;
if (!scrollingEffectClassNameReg.test(containerClassName)) return;
setStyle(cacheStyle.get(container), { element: container });
cacheStyle.delete(container);
container.className = container.className.replace(scrollingEffectClassNameReg, '').trim();
};
}