diff --git a/docs/demo/limitation.md b/docs/demo/limitation.md
new file mode 100644
index 000000000..8697fd98f
--- /dev/null
+++ b/docs/demo/limitation.md
@@ -0,0 +1,8 @@
+---
+title: limitation
+nav:
+ title: Demo
+ path: /demo
+---
+
+
diff --git a/docs/demo/multiple.md b/docs/demo/multiple.md
new file mode 100644
index 000000000..8cbb6d647
--- /dev/null
+++ b/docs/demo/multiple.md
@@ -0,0 +1,8 @@
+---
+title: multiple
+nav:
+ title: Demo
+ path: /demo
+---
+
+
diff --git a/docs/examples/limitation.tsx b/docs/examples/limitation.tsx
new file mode 100644
index 000000000..6fe3b06d2
--- /dev/null
+++ b/docs/examples/limitation.tsx
@@ -0,0 +1,52 @@
+import * as React from 'react';
+import '../../assets/index.less';
+import SinglePicker from '../../src/PickerInput/SinglePicker';
+
+import dayjs from 'dayjs';
+import 'dayjs/locale/ar';
+import 'dayjs/locale/zh-cn';
+import LocalizedFormat from 'dayjs/plugin/localizedFormat';
+import dayjsGenerateConfig from '../../src/generate/dayjs';
+import zhCN from '../../src/locale/zh_CN';
+
+dayjs.locale('zh-cn');
+dayjs.extend(LocalizedFormat);
+
+console.clear();
+
+(window as any).dayjs = dayjs;
+
+const sharedLocale = {
+ locale: zhCN,
+ generateConfig: dayjsGenerateConfig,
+ style: { width: 300 },
+};
+
+export default () => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/docs/examples/multiple.tsx b/docs/examples/multiple.tsx
new file mode 100644
index 000000000..b07c956d1
--- /dev/null
+++ b/docs/examples/multiple.tsx
@@ -0,0 +1,35 @@
+import * as React from 'react';
+import '../../assets/index.less';
+import type { PickerRef } from '../../src/interface';
+import SinglePicker from '../../src/PickerInput/SinglePicker';
+
+import dayjs from 'dayjs';
+import 'dayjs/locale/ar';
+import 'dayjs/locale/zh-cn';
+import LocalizedFormat from 'dayjs/plugin/localizedFormat';
+import dayjsGenerateConfig from '../../src/generate/dayjs';
+import zhCN from '../../src/locale/zh_CN';
+
+dayjs.locale('zh-cn');
+dayjs.extend(LocalizedFormat);
+
+console.clear();
+
+(window as any).dayjs = dayjs;
+
+const sharedLocale = {
+ locale: zhCN,
+ generateConfig: dayjsGenerateConfig,
+ style: { width: 300 },
+};
+
+export default () => {
+ const singleRef = React.useRef(null);
+
+ return (
+
+
+
+
+ );
+};
diff --git a/src/PickerInput/Popup/PopupPanel.tsx b/src/PickerInput/Popup/PopupPanel.tsx
index 94cfccc4f..0d056e33b 100644
--- a/src/PickerInput/Popup/PopupPanel.tsx
+++ b/src/PickerInput/Popup/PopupPanel.tsx
@@ -13,8 +13,6 @@ export type PopupPanelProps = MustProp
Omit, 'onPickerValueChange' | 'showTime'> &
FooterProps & {
multiplePanel?: boolean;
- minDate?: DateType;
- maxDate?: DateType;
range?: boolean;
onPickerValueChange: (date: DateType) => void;
@@ -23,17 +21,8 @@ export type PopupPanelProps = MustProp
export default function PopupPanel(
props: PopupPanelProps,
) {
- const {
- picker,
- multiplePanel,
- pickerValue,
- onPickerValueChange,
- onSubmit,
- minDate,
- maxDate,
- range,
- hoverValue,
- } = props;
+ const { picker, multiplePanel, pickerValue, onPickerValueChange, onSubmit, range, hoverValue } =
+ props;
const { prefixCls, generateConfig } = React.useContext(PickerContext);
// ======================== Offset ========================
@@ -63,36 +52,6 @@ export default function PopupPanel(
const hideHeader = picker === 'time';
- // ====================== Limitation ======================
- const needLimit = React.useCallback(
- (currentPickerValue: DateType) => {
- let hidePrev = false;
- let hideNext = false;
-
- const dateBeforePickerValue = internalOffsetDate(currentPickerValue, -1);
- if (minDate && generateConfig.isAfter(minDate, dateBeforePickerValue)) {
- hidePrev = true;
- }
-
- const dateAfterPickerValue = internalOffsetDate(currentPickerValue, 1);
- if (maxDate && generateConfig.isAfter(dateAfterPickerValue, maxDate)) {
- hideNext = true;
- }
-
- return {
- hidePrev,
- hideNext,
- };
- },
- [minDate, maxDate, internalOffsetDate, generateConfig],
- );
-
- const firstPanelNeedLimit = React.useMemo(() => needLimit(pickerValue), [pickerValue, needLimit]);
- const secondPanelNeedLimit = React.useMemo(
- () => needLimit(nextPickerValue),
- [nextPickerValue, needLimit],
- );
-
// ======================== Props =========================
const pickerProps = {
...props,
@@ -113,12 +72,18 @@ export default function PopupPanel(
return (
{...pickerProps} />
{...pickerProps}
@@ -135,7 +100,6 @@ export default function PopupPanel(
diff --git a/src/PickerInput/hooks/useFilledProps.ts b/src/PickerInput/hooks/useFilledProps.ts
index f65198364..e87e23bb4 100644
--- a/src/PickerInput/hooks/useFilledProps.ts
+++ b/src/PickerInput/hooks/useFilledProps.ts
@@ -128,8 +128,9 @@ export default function useFilledProps<
const internalPicker: InternalMode = picker === 'date' && showTime ? 'datetime' : picker;
/** The picker is `datetime` or `time` */
- const complexPicker = internalPicker === 'time' || internalPicker === 'datetime' || multiple;
- const mergedNeedConfirm = needConfirm ?? complexPicker;
+ const multipleInteractivePicker = internalPicker === 'time' || internalPicker === 'datetime';
+ const complexPicker = multipleInteractivePicker || multiple;
+ const mergedNeedConfirm = needConfirm ?? multipleInteractivePicker;
// ========================== Time ==========================
// Auto `format` need to check `showTime.showXXX` first.
diff --git a/src/PickerInput/hooks/useRangeValue.ts b/src/PickerInput/hooks/useRangeValue.ts
index 68a855e15..8e1c8a52a 100644
--- a/src/PickerInput/hooks/useRangeValue.ts
+++ b/src/PickerInput/hooks/useRangeValue.ts
@@ -137,7 +137,7 @@ export function useInnerValue date) as ValueType, generateConfig);
}
// Update merged value
diff --git a/src/PickerPanel/DatePanel/index.tsx b/src/PickerPanel/DatePanel/index.tsx
index fe6fe2a62..7b24b9e2e 100644
--- a/src/PickerPanel/DatePanel/index.tsx
+++ b/src/PickerPanel/DatePanel/index.tsx
@@ -46,7 +46,8 @@ export default function DatePanel(props: DatePane
// ========================== Base ==========================
const [info, now] = useInfo(props, mode);
const weekFirstDay = generateConfig.locale.getWeekFirstDay(locale.locale);
- const baseDate = getWeekStartDate(locale.locale, generateConfig, pickerValue);
+ const monthStartDate = generateConfig.setDate(pickerValue, 1);
+ const baseDate = getWeekStartDate(locale.locale, generateConfig, monthStartDate);
const month = generateConfig.getMonth(pickerValue);
// =========================== PrefixColumn ===========================
@@ -176,12 +177,16 @@ export default function DatePanel(props: DatePane
{/* Header */}
-
{
- onPickerValueChange(generateConfig.addMonth(pickerValue, offset));
- }}
- onSuperOffset={(offset) => {
- onPickerValueChange(generateConfig.addYear(pickerValue, offset));
+
+ offset={(distance) => generateConfig.addMonth(pickerValue, distance)}
+ superOffset={(distance) => generateConfig.addYear(pickerValue, distance)}
+ onChange={onPickerValueChange}
+ // Limitation
+ getStart={(date) => generateConfig.setDate(date, 1)}
+ getEnd={(date) => {
+ let clone = generateConfig.setDate(date, 1);
+ clone = generateConfig.addMonth(clone, 1);
+ return generateConfig.addDate(clone, -1);
}}
>
{monthYearNodes}
diff --git a/src/PickerPanel/DecadePanel/index.tsx b/src/PickerPanel/DecadePanel/index.tsx
index d57a067d5..7c9c374f7 100644
--- a/src/PickerPanel/DecadePanel/index.tsx
+++ b/src/PickerPanel/DecadePanel/index.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import type { DisabledDate, SharedPanelProps } from '../../interface';
-import { formatValue } from '../../utils/dateUtil';
+import { formatValue, isInRange, isSameDecade } from '../../utils/dateUtil';
import { PanelContext, useInfo } from '../context';
import PanelBody from '../PanelBody';
import PanelHeader from '../PanelHeader';
@@ -15,13 +15,20 @@ export default function DecadePanel(
// ========================== Base ==========================
const [info] = useInfo(props, 'decade');
- const startYear = Math.floor(generateConfig.getYear(pickerValue) / 100) * 100;
- const endYear = startYear + 99;
- const baseDate = generateConfig.setYear(pickerValue, startYear - 10);
+ const getStartYear = (date: DateType) => {
+ const startYear = Math.floor(generateConfig.getYear(pickerValue) / 100) * 100;
+ return generateConfig.setYear(date, startYear);
+ };
+ const getEndYear = (date: DateType) => {
+ const startYear = getStartYear(date);
+ return generateConfig.addYear(startYear, 99);
+ };
+
+ const startYearDate = getStartYear(pickerValue);
+ const endYearDate = getEndYear(pickerValue);
- const startYearDate = generateConfig.setYear(baseDate, startYear);
- const endYearDate = generateConfig.setYear(startYearDate, endYear);
+ const baseDate = generateConfig.addYear(startYearDate, -10);
// ========================= Cells ==========================
const getCellDate = (date: DateType, offset: number) => {
@@ -46,9 +53,11 @@ export default function DecadePanel(
};
const getCellClassName = (date: DateType) => {
- const dateYear = generateConfig.getYear(date);
return {
- [`${prefixCls}-cell-in-view`]: startYear <= dateYear && dateYear <= endYear,
+ [`${prefixCls}-cell-in-view`]:
+ isSameDecade(generateConfig, date, startYearDate) ||
+ isSameDecade(generateConfig, date, endYearDate) ||
+ isInRange(generateConfig, startYearDate, endYearDate, date),
};
};
@@ -88,9 +97,11 @@ export default function DecadePanel(
{/* Header */}
{
- onPickerValueChange(generateConfig.addYear(pickerValue, offset * 100));
- }}
+ superOffset={(distance) => generateConfig.addYear(pickerValue, distance * 100)}
+ onChange={onPickerValueChange}
+ // Limitation
+ getStart={getStartYear}
+ getEnd={getEndYear}
>
{yearNode}
diff --git a/src/PickerPanel/MonthPanel/index.tsx b/src/PickerPanel/MonthPanel/index.tsx
index 26421b2b6..2d18e1801 100644
--- a/src/PickerPanel/MonthPanel/index.tsx
+++ b/src/PickerPanel/MonthPanel/index.tsx
@@ -91,9 +91,11 @@ export default function MonthPanel
(
{/* Header */}
{
- onPickerValueChange(generateConfig.addYear(pickerValue, offset));
- }}
+ superOffset={(distance) => generateConfig.addYear(pickerValue, distance)}
+ onChange={onPickerValueChange}
+ // Limitation
+ getStart={(date) => generateConfig.setMonth(date, 0)}
+ getEnd={(date) => generateConfig.setMonth(date, 11)}
>
{yearNode}
diff --git a/src/PickerPanel/PanelHeader.tsx b/src/PickerPanel/PanelHeader.tsx
index 6233ec4dd..794962bf6 100644
--- a/src/PickerPanel/PanelHeader.tsx
+++ b/src/PickerPanel/PanelHeader.tsx
@@ -1,21 +1,32 @@
+import classNames from 'classnames';
import * as React from 'react';
+import { isSameOrAfter } from '../utils/dateUtil';
import { PickerHackContext, usePanelContext } from './context';
const HIDDEN_STYLE: React.CSSProperties = {
visibility: 'hidden',
};
-export interface HeaderProps {
- onOffset?: (offset: number) => void;
- onSuperOffset?: (offset: number) => void;
+export interface HeaderProps
{
+ offset?: (distance: number, date: DateType) => DateType;
+ superOffset?: (distance: number, date: DateType) => DateType;
+ onChange?: (date: DateType) => void;
+
+ // Limitation
+ getStart?: (date: DateType) => DateType;
+ getEnd?: (date: DateType) => DateType;
children?: React.ReactNode;
}
-function PanelHeader(props: HeaderProps) {
+function PanelHeader(props: HeaderProps) {
const {
- onOffset,
- onSuperOffset,
+ offset,
+ superOffset,
+ onChange,
+
+ getStart,
+ getEnd,
children,
} = props;
@@ -28,59 +39,136 @@ function PanelHeader(props: HeaderProps) {
nextIcon = '\u203A',
superPrevIcon = '\u00AB',
superNextIcon = '\u00BB',
+
+ // Limitation
+ minDate,
+ maxDate,
+ generateConfig,
+ locale,
+ pickerValue,
+ panelType: type,
} = usePanelContext();
const headerPrefixCls = `${prefixCls}-header`;
const { hidePrev, hideNext, hideHeader } = React.useContext(PickerHackContext);
+ // ======================= Limitation =======================
+ const disabledOffsetPrev = React.useMemo(() => {
+ if (!minDate || !offset || !getEnd) {
+ return false;
+ }
+
+ const prevPanelLimitDate = getEnd(offset(-1, pickerValue));
+
+ return !isSameOrAfter(generateConfig, locale, prevPanelLimitDate, minDate, type);
+ }, [minDate, offset, pickerValue, getEnd, generateConfig, locale, type]);
+
+ const disabledSuperOffsetPrev = React.useMemo(() => {
+ if (!minDate || !superOffset || !getEnd) {
+ return false;
+ }
+
+ const prevPanelLimitDate = getEnd(superOffset(-1, pickerValue));
+
+ return !isSameOrAfter(generateConfig, locale, prevPanelLimitDate, minDate, type);
+ }, [minDate, superOffset, pickerValue, getEnd, generateConfig, locale, type]);
+
+ const disabledOffsetNext = React.useMemo(() => {
+ if (!maxDate || !offset || !getStart) {
+ return false;
+ }
+
+ const nextPanelLimitDate = getStart(offset(1, pickerValue));
+
+ return !isSameOrAfter(generateConfig, locale, maxDate, nextPanelLimitDate, type);
+ }, [maxDate, offset, pickerValue, getStart, generateConfig, locale, type]);
+
+ const disabledSuperOffsetNext = React.useMemo(() => {
+ if (!maxDate || !superOffset || !getStart) {
+ return false;
+ }
+
+ const nextPanelLimitDate = getStart(superOffset(1, pickerValue));
+
+ return !isSameOrAfter(generateConfig, locale, maxDate, nextPanelLimitDate, type);
+ }, [maxDate, superOffset, pickerValue, getStart, generateConfig, locale, type]);
+
+ // ========================= Offset =========================
+ const onOffset = (distance: number) => {
+ if (offset) {
+ onChange(offset(distance, pickerValue));
+ }
+ };
+
+ const onSuperOffset = (distance: number) => {
+ if (superOffset) {
+ onChange(superOffset(distance, pickerValue));
+ }
+ };
+
+ // ========================= Render =========================
if (hideHeader) {
return null;
}
- // ========================= Render =========================
+ const prevBtnCls = `${headerPrefixCls}-prev-btn`;
+ const nextBtnCls = `${headerPrefixCls}-next-btn`;
+ const superPrevBtnCls = `${headerPrefixCls}-super-prev-btn`;
+ const superNextBtnCls = `${headerPrefixCls}-super-next-btn`;
+
return (
- {onSuperOffset && (
+ {superOffset && (
)}
- {onOffset && (
+ {offset && (
)}
{children}
- {onOffset && (
+ {offset && (
)}
- {onSuperOffset && (
+ {superOffset && (