diff --git a/components/calendar/__tests__/index.test.js b/components/calendar/__tests__/index.test.js
index 54c04e1a5..50414e5ce 100644
--- a/components/calendar/__tests__/index.test.js
+++ b/components/calendar/__tests__/index.test.js
@@ -3,11 +3,21 @@ import { mount } from '@vue/test-utils';
import { asyncExpect } from '@/tests/utils';
import MockDate from 'mockdate';
import Calendar from '..';
+import Header from '../Header';
+import Select from '../../select';
+import Group from '../../radio/group';
+import Button from '../../radio/radioButton';
+import mountTest from '../../../tests/shared/mountTest';
+import { sleep } from '../../../tests/utils';
function $$(className) {
return document.body.querySelectorAll(className);
}
describe('Calendar', () => {
+ mountTest(Calendar);
+ beforeAll(() => {
+ document.body.innerHTML = '';
+ });
it('Calendar should be selectable', async () => {
const onSelect = jest.fn();
const wrapper = mount(
@@ -25,7 +35,7 @@ describe('Calendar', () => {
.trigger('click');
});
await asyncExpect(() => {
- expect(onSelect).toBeCalledWith(expect.anything());
+ expect(onSelect).toHaveBeenCalledWith(expect.anything());
const value = onSelect.mock.calls[0][0];
expect(Moment.isMoment(value)).toBe(true);
});
@@ -203,11 +213,11 @@ describe('Calendar', () => {
});
await asyncExpect(() => {
expect(wrapper.vm.sMode).toEqual(yearMode);
- wrapper.vm.setType('date');
+ wrapper.setProps({ mode: monthMode });
});
await asyncExpect(() => {
expect(wrapper.vm.sMode).toEqual(monthMode);
- expect(onPanelChangeStub).toHaveBeenCalledTimes(1);
+ expect(onPanelChangeStub).toHaveBeenCalledTimes(0);
});
});
@@ -226,4 +236,164 @@ describe('Calendar', () => {
MockDate.reset();
});
});
+
+ it('should trigger onPanelChange when click last month of date', () => {
+ const onPanelChange = jest.fn();
+ const date = new Moment('1990-09-03');
+ const wrapper = mount(Calendar, {
+ propsData: {
+ value: date,
+ },
+ listeners: {
+ panelChange: onPanelChange,
+ },
+ sync: false,
+ });
+ wrapper
+ .findAll('.ant-fullcalendar-cell')
+ .at(0)
+ .trigger('click');
+
+ expect(onPanelChange).toHaveBeenCalled();
+ expect(onPanelChange.mock.calls[0][0].month()).toEqual(date.month() - 1);
+ });
+
+ it('switch should work correctly without prop mode', async () => {
+ const onPanelChange = jest.fn();
+ const date = new Moment(new Date(Date.UTC(2017, 7, 9, 8)));
+ const wrapper = mount(Calendar, {
+ propsData: {
+ value: date,
+ },
+ listeners: {
+ panelChange: onPanelChange,
+ },
+ sync: false,
+ });
+ expect(wrapper.vm.sMode).toBe('month');
+ expect(wrapper.findAll('.ant-fullcalendar-table').length).toBe(1);
+ expect(wrapper.findAll('.ant-fullcalendar-month-panel-table').length).toBe(0);
+ wrapper.findAll('.ant-radio-button-input[value="year"]').trigger('change');
+ await sleep(50);
+ expect(wrapper.findAll('.ant-fullcalendar-table').length).toBe(0);
+ expect(wrapper.findAll('.ant-fullcalendar-month-panel-table').length).toBe(1);
+ expect(onPanelChange).toHaveBeenCalled();
+ expect(onPanelChange.mock.calls[0][1]).toEqual('year');
+ });
+
+ const createWrapper = async (start, end, value, onValueChange) => {
+ document.body.innerHTML = '';
+ const wrapper = mount(
+ {
+ render() {
+ return (
+
+ );
+ },
+ },
+ {
+ sync: false,
+ attachToDocument: true,
+ },
+ );
+ await sleep(50);
+ wrapper
+ .findAll('.ant-fullcalendar-year-select')
+ .at(0)
+ .trigger('click');
+ await sleep(50);
+ $$('.ant-select-dropdown-menu-item')[0].click();
+ await sleep(50);
+ };
+
+ it('if value.month > end.month, set value.month to end.month', async () => {
+ const value = new Moment('1990-01-03');
+ const start = new Moment('2019-04-01');
+ const end = new Moment('2019-11-01');
+ const onValueChange = jest.fn();
+ await createWrapper(start, end, value, onValueChange);
+ expect(onValueChange).toHaveBeenCalledWith(value.year('2019').month('3'));
+ });
+ it('if value.month > end.month, set value.month to end.month1', async () => {
+ const value = new Moment('1990-01-03');
+ const start = new Moment('2019-04-01');
+ const end = new Moment('2019-11-01');
+ const onValueChange = jest.fn();
+ await createWrapper(start, end, value, onValueChange);
+ expect(onValueChange).toHaveBeenCalledWith(value.year('2019').month('3'));
+ });
+
+ it('if start.month > value.month, set value.month to start.month ', async () => {
+ const value = new Moment('1990-01-03');
+ const start = new Moment('2019-11-01');
+ const end = new Moment('2019-03-01');
+ const onValueChange = jest.fn();
+ await createWrapper(start, end, value, onValueChange);
+ expect(onValueChange).toHaveBeenCalledWith(value.year('2019').month('10'));
+ });
+
+ it('onMonthChange should work correctly', async () => {
+ const start = new Moment('2018-11-01');
+ const end = new Moment('2019-03-01');
+ const value = new Moment('2018-12-03');
+ const onValueChange = jest.fn();
+ const wrapper = mount(
+ {
+ render() {
+ return (
+
+ );
+ },
+ },
+ {
+ sync: false,
+ attachToDocument: true,
+ },
+ );
+ await sleep(50);
+ wrapper
+ .findAll('.ant-fullcalendar-month-select')
+ .at(0)
+ .trigger('click');
+ await sleep(50);
+ wrapper
+ .findAll('.ant-select-dropdown-menu-item')
+ .at(0)
+ .trigger('click');
+ await sleep(50);
+ expect(onValueChange).toHaveBeenCalledWith(value.month(10));
+ });
+
+ it('onTypeChange should work correctly', () => {
+ const onTypeChange = jest.fn();
+ const value = new Moment('2018-12-03');
+ const wrapper = mount({
+ render() {
+ return (
+
+ );
+ },
+ });
+ wrapper
+ .findAll('input')
+ .at(1)
+ .trigger('change');
+ expect(onTypeChange).toHaveBeenCalledWith('year');
+ });
});
diff --git a/components/calendar/demo/customize-header.vue b/components/calendar/demo/customize-header.vue
new file mode 100644
index 000000000..f014363bd
--- /dev/null
+++ b/components/calendar/demo/customize-header.vue
@@ -0,0 +1,99 @@
+
+#### 自定义头部
+自定义日历头部内容。
+
+
+
+#### Customize Header
+Customize Calendar header content.
+
+
+```tpl
+
+
+
+
+```
diff --git a/components/calendar/demo/index.vue b/components/calendar/demo/index.vue
index 265c681bf..999df62cd 100644
--- a/components/calendar/demo/index.vue
+++ b/components/calendar/demo/index.vue
@@ -3,6 +3,8 @@ import Basic from './basic';
import Card from './card';
import NoticeCalendar from './notice-calendar';
import Select from './select';
+import CustomizeHeader from './customize-header.vue';
+import CustomizeHeaderString from '!raw-loader!./customize-header.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
const md = {
@@ -40,6 +42,10 @@ export default {
+
+
+
+
diff --git a/components/calendar/index.en-US.md b/components/calendar/index.en-US.md
index 7d031bd60..4fde3061b 100644
--- a/components/calendar/index.en-US.md
+++ b/components/calendar/index.en-US.md
@@ -15,24 +15,25 @@ moment.locale('zh-cn');
customize the progress dot by setting a scoped slot
-| Property | Description | Type | Default |
-| --- | --- | --- | --- |
-| dateCellRender | Customize the display of the date cell by setting a scoped slot, the returned content will be appended to the cell | function(date: moment) | - |
-| dateFullCellRender | Customize the display of the date cell by setting a scoped slot, the returned content will override the cell | function(date: moment) | - |
-| defaultValue | The date selected by default | [moment](http://momentjs.com/) | default date |
+| Property | Description | Type | Default | Version |
+| --- | --- | --- | --- | --- |
+| dateCellRender | Customize the display of the date cell by setting a scoped slot, the returned content will be appended to the cell | function(date: moment) | - | |
+| dateFullCellRender | Customize the display of the date cell by setting a scoped slot, the returned content will override the cell | function(date: moment) | - | |
+| defaultValue | The date selected by default | [moment](http://momentjs.com/) | default date | |
| disabledDate | Function that specifies the dates that cannot be selected | (currentDate: moment) => boolean | - |
-| fullscreen | Whether to display in full-screen | boolean | `true` |
-| locale | The calendar's locale | object | [default](https://github.com/vueComponent/ant-design-vue/blob/master/components/date-picker/locale/example.json) |
-| mode | The display mode of the calendar | `month` \| `year` | `month` |
-| monthCellRender | Customize the display of the month cell by setting a scoped slot, the returned content will be appended to the cell | function(date: moment) | - |
-| monthFullCellRender | Customize the display of the month cell by setting a scoped slot, the returned content will override the cell | function(date: moment) | - |
-| validRange | to set valid range | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | - |
-| value(v-model) | The current selected date | [moment](http://momentjs.com/) | current date |
+| fullscreen | Whether to display in full-screen | boolean | `true` | |
+| locale | The calendar's locale | object | [default](https://github.com/vueComponent/ant-design-vue/blob/master/components/date-picker/locale/example.json) | |
+| mode | The display mode of the calendar | `month` \| `year` | `month` | |
+| monthCellRender | Customize the display of the month cell by setting a scoped slot, the returned content will be appended to the cell | function(date: moment) | - | |
+| monthFullCellRender | Customize the display of the month cell by setting a scoped slot, the returned content will override the cell | function(date: moment) | - | |
+| validRange | to set valid range | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | - | |
+| value(v-model) | The current selected date | [moment](http://momentjs.com/) | current date | |
+| headerRender | render custom header in panel | function(object:{value: moment, type: string, onChange: f(), onTypeChange: f()}) \| slot-scope | - | 1.5.0 |
### events
-| Events Name | Description | Arguments |
-| ----------- | ------------------------------------ | ------------------------------------ |
-| panelChange | Callback for when panel changes | function(date: moment, mode: string) | - |
-| select | Callback for when a date is selected | function(date: moment) | - |
-| change | Callback for when value change | function(date: moment) | - |
+| Events Name | Description | Arguments | Version |
+| --- | --- | --- | --- |
+| panelChange | Callback for when panel changes | function(date: moment, mode: string) | - | |
+| select | Callback for when a date is selected | function(date: moment) | - | |
+| change | Callback for when value change | function(date: moment) | - | |
diff --git a/components/calendar/index.jsx b/components/calendar/index.jsx
index 54fd1183a..4200980fc 100644
--- a/components/calendar/index.jsx
+++ b/components/calendar/index.jsx
@@ -1,10 +1,16 @@
import PropTypes from '../_util/vue-types';
import BaseMixin from '../_util/BaseMixin';
-import { getOptionProps, hasProp, initDefaultProps, getListeners } from '../_util/props-util';
+import {
+ getOptionProps,
+ hasProp,
+ initDefaultProps,
+ getListeners,
+ getComponentFromProp,
+} from '../_util/props-util';
import * as moment from 'moment';
import FullCalendar from '../vc-calendar/src/FullCalendar';
+import Header, { HeaderRender } from './Header';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
-import Header from './Header';
import interopDefault from '../_util/interopDefault';
import { ConfigConsumerProps } from '../config-provider';
import enUS from './locale/en_US';
@@ -46,6 +52,7 @@ export const CalendarProps = () => ({
// onSelect?: (date?: moment.Moment) => void;
disabledDate: PropTypes.func,
validRange: PropTypes.custom(isMomentArray),
+ headerRender: PropTypes.func,
});
const Calendar = {
@@ -54,7 +61,6 @@ const Calendar = {
props: initDefaultProps(CalendarProps(), {
locale: {},
fullscreen: true,
- mode: 'month',
}),
model: {
prop: 'value',
@@ -71,7 +77,7 @@ const Calendar = {
this._sPrefixCls = undefined;
return {
sValue: value,
- sMode: this.mode,
+ sMode: this.mode || 'month',
};
},
watch: {
@@ -87,55 +93,13 @@ const Calendar = {
},
},
methods: {
- monthCellRender2(value) {
- const { _sPrefixCls, $scopedSlots } = this;
- const monthCellRender = this.monthCellRender || $scopedSlots.monthCellRender || noop;
- return (
-
-
{value.localeData().monthsShort(value)}
-
{monthCellRender(value)}
-
- );
- },
-
- dateCellRender2(value) {
- const { _sPrefixCls, $scopedSlots } = this;
- const dateCellRender = this.dateCellRender || $scopedSlots.dateCellRender || noop;
- return (
-
-
{zerofixed(value.date())}
-
{dateCellRender(value)}
-
- );
- },
-
- setValue(value, way) {
- if (way === 'select') {
- this.$emit('select', value);
- } else if (way === 'changePanel') {
- this.onPanelChange(value, this.sMode);
- }
- if (!hasProp(this, 'value')) {
- this.setState({ sValue: value });
- }
- },
-
- setType(type) {
- const mode = type === 'date' ? 'month' : 'year';
- if (this.sMode !== mode) {
- this.setState({ sMode: mode });
- this.onPanelChange(this.sValue, mode);
- }
- },
-
onHeaderValueChange(value) {
this.setValue(value, 'changePanel');
},
-
- onHeaderTypeChange(type) {
- this.setType(type);
+ onHeaderTypeChange(mode) {
+ this.sMode = mode;
+ this.onPanelChange(this.sValue, mode);
},
-
onPanelChange(value, mode) {
this.$emit('panelChange', value, mode);
if (value !== this.sValue) {
@@ -146,6 +110,21 @@ const Calendar = {
onSelect(value) {
this.setValue(value, 'select');
},
+ setValue(value, way) {
+ const prevValue = this.value || this.sValue;
+ const { sMode: mode } = this;
+ if (!hasProp(this, 'value')) {
+ this.setState({ sValue: value });
+ }
+ if (way === 'select') {
+ if (prevValue && prevValue.month() !== value.month()) {
+ this.onPanelChange(value, mode);
+ }
+ this.$emit('select', value);
+ } else if (way === 'changePanel') {
+ this.onPanelChange(value, mode);
+ }
+ },
getDateRange(validRange, disabledDate) {
return current => {
if (!current) {
@@ -170,6 +149,28 @@ const Calendar = {
};
return result;
},
+ monthCellRender2(value) {
+ const { _sPrefixCls, $scopedSlots } = this;
+ const monthCellRender = this.monthCellRender || $scopedSlots.monthCellRender || noop;
+ return (
+
+
{value.localeData().monthsShort(value)}
+
{monthCellRender(value)}
+
+ );
+ },
+
+ dateCellRender2(value) {
+ const { _sPrefixCls, $scopedSlots } = this;
+ const dateCellRender = this.dateCellRender || $scopedSlots.dateCellRender || noop;
+ return (
+
+
{zerofixed(value.date())}
+
{dateCellRender(value)}
+
+ );
+ },
+
renderCalendar(locale, localeCode) {
const props = getOptionProps(this);
const { sValue: value, sMode: mode, $scopedSlots } = this;
@@ -182,9 +183,9 @@ const Calendar = {
dateFullCellRender,
monthFullCellRender,
} = props;
+ const headerRender = this.headerRender || $scopedSlots.headerRender;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('fullcalendar', customizePrefixCls);
- const type = mode === 'year' ? 'month' : 'date';
// To support old version react.
// Have to add prefixCls on the instance.
@@ -211,7 +212,7 @@ const Calendar = {
...props,
Select: {},
locale: locale.lang,
- type: type,
+ type: mode === 'year' ? 'month' : 'date',
prefixCls: prefixCls,
showHeader: false,
value: value,
@@ -228,7 +229,8 @@ const Calendar = {
void;
+ type: string;
+ onTypeChange: (type: string) => void;
+}
export declare class Calendar extends AntdComponent {
+ headerRender: (headerRender: RenderHeader) => any;
/**
* Customize the display of the date cell by setting a scoped slot,
* the returned content will be appended to the cell