Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c12e7e2
fix: disabledHours works unexpectlly when using 12 hours & hour shoul…
07akioni Jun 18, 2020
372432f
chore
07akioni Jun 18, 2020
74fd67a
chore: typo fix
07akioni Jun 18, 2020
5c0da62
feat: now button now works with step
07akioni Jun 18, 2020
5a0c072
test: now with step
07akioni Jun 18, 2020
495b536
chore: fix typo
07akioni Jun 18, 2020
7425eee
refactor: setTime, use useMemo
07akioni Jun 19, 2020
7f11450
test: dayjs & moment util test
07akioni Jun 19, 2020
db37cd3
chore
07akioni Jun 19, 2020
5c9d614
refactor: set time
07akioni Jun 19, 2020
33f9a13
test: setTime
07akioni Jun 19, 2020
de43ba3
feat: dev warning on step
07akioni Jun 19, 2020
e9fdbf7
fix: dev warning
07akioni Jun 19, 2020
d46d4d1
fix: dev warning
07akioni Jun 19, 2020
fbdc1f5
refactor: get lower bound time rather than closest time
07akioni Jun 19, 2020
8b04a63
refactor: use rc-utils memo
07akioni Jun 19, 2020
08cb22d
refactor: use shallowEqual
07akioni Jun 19, 2020
3a76732
refactor
07akioni Jun 19, 2020
df42550
refactor: code styling
07akioni Jun 22, 2020
f40a105
test: warning on time step
07akioni Jun 22, 2020
5c610ca
chore
07akioni Jun 22, 2020
6577912
chore
07akioni Jun 22, 2020
35967ee
chore: revert format
07akioni Jun 22, 2020
11b9ff3
fix: compare length in time units comparition
07akioni Jun 22, 2020
f8e8d33
chore: revert dayjs util code
07akioni Jun 22, 2020
5b23f36
chore: revert some useMemo to React.useMemo
07akioni Jun 22, 2020
11f58b7
chore: revert code formatting
07akioni Jun 22, 2020
2fb05da
perf: remove useless destructuring assignment
07akioni Jun 23, 2020
9962d03
perf: flatten parameters
07akioni Jun 23, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions src/PickerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { MonthCellRender } from './panels/MonthPanel/MonthBody';
import RangeContext from './RangeContext';
import getExtraFooter from './utils/getExtraFooter';
import getRanges from './utils/getRanges';
import { getLowerBoundTime, setTime } from './utils/timeUtil';

export interface PickerPanelSharedProps<DateType> {
prefixCls?: string;
Expand Down Expand Up @@ -146,13 +147,29 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
onOk,
components,
direction,
hourStep = 1,
minuteStep = 1,
secondStep = 1,
} = props as MergedPickerPanelProps<DateType>;

const needConfirmButton: boolean = (picker === 'date' && !!showTime) || picker === 'time';

const isHourStepValid = 24 % hourStep === 0;
const isMinuteStepValid = 60 % minuteStep === 0;
const isSecondStepValid = 60 % secondStep === 0;

if (process.env.NODE_ENV !== 'production') {
warning(!value || generateConfig.isValidate(value), 'Invalidate date pass to `value`.');
warning(!value || generateConfig.isValidate(value), 'Invalidate date pass to `defaultValue`.');
warning(isHourStepValid, `\`hourStep\` ${hourStep} is invalid. It should be a factor of 24.`);
warning(
isMinuteStepValid,
`\`minuteStep\` ${minuteStep} is invalid. It should be a factor of 60.`,
);
warning(
isSecondStepValid,
`\`secondStep\` ${secondStep} is invalid. It should be a factor of 60.`,
);
}

// ============================ State =============================
Expand Down Expand Up @@ -434,6 +451,26 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
let extraFooter: React.ReactNode;
let rangesNode: React.ReactNode;

const onNow = () => {
const now = generateConfig.getNow();
const lowerBoundTime = getLowerBoundTime(
generateConfig.getHour(now),
generateConfig.getMinute(now),
generateConfig.getSecond(now),
isHourStepValid ? hourStep : 1,
isMinuteStepValid ? minuteStep : 1,
isSecondStepValid ? secondStep : 1,
);
const adjustedNow = setTime(
generateConfig,
now,
lowerBoundTime[0], // hour
lowerBoundTime[1], // minute
lowerBoundTime[2], // second
);
triggerSelect(adjustedNow, 'submit');
};

if (!hideRanges) {
extraFooter = getExtraFooter(prefixCls, mergedMode, renderExtraFooter);
rangesNode = getRanges({
Expand All @@ -443,11 +480,7 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
okDisabled: !mergedValue || (disabledDate && disabledDate(mergedValue)),
locale,
showNow,
onNow:
needConfirmButton &&
(() => {
triggerSelect(generateConfig.getNow(), 'submit');
}),
onNow: needConfirmButton && onNow,
onOk: () => {
if (mergedValue) {
triggerSelect(mergedValue, 'submit', true);
Expand Down
75 changes: 56 additions & 19 deletions src/panels/TimePanel/TimeBody.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import * as React from 'react';
import useMemo from 'rc-util/lib/hooks/useMemo';
import { GenerateConfig } from '../../generate';
import { Locale, OnSelect } from '../../interface';
import TimeUnitColumn, { Unit } from './TimeUnitColumn';
import { leftPad } from '../../utils/miscUtil';
import { SharedTimeProps } from '.';
import { setTime as utilSetTime } from '../../utils/timeUtil';

function shouldUnitsUpdate(prevUnits: Unit[], nextUnits: Unit[]) {
if (prevUnits.length !== nextUnits.length) return true;
// if any unit's disabled status is different, the units should be re-evaluted
for (let i = 0; i < prevUnits.length; i += 1) {
if (prevUnits[i].disabled !== nextUnits[i].disabled) return true;
}
return false;
}

function generateUnits(
start: number,
Expand Down Expand Up @@ -83,37 +94,60 @@ function TimeBody<DateType>(props: TimeBodyProps<DateType>) {
const mergedMinute = Math.max(0, newMinute);
const mergedSecond = Math.max(0, newSecond);

newDate = generateConfig.setSecond(newDate, mergedSecond);
newDate = generateConfig.setMinute(newDate, mergedMinute);
newDate = generateConfig.setHour(
newDate = utilSetTime(
generateConfig,
newDate,
!use12Hours || !isNewPM ? mergedHour : mergedHour + 12,
mergedMinute,
mergedSecond,
);

return newDate;
};

// ========================= Unit =========================
const hours = generateUnits(
0,
use12Hours ? 11 : 23,
hourStep,
disabledHours && disabledHours(),
);
const rawHours = generateUnits(0, 23, hourStep, disabledHours && disabledHours());

const memorizedRawHours = useMemo(() => rawHours, rawHours, shouldUnitsUpdate);

// Should additional logic to handle 12 hours
if (use12Hours && hour !== -1) {
isPM = hour >= 12;
if (use12Hours) {
isPM = hour >= 12; // -1 means should display AM
hour %= 12;
hours[0].label = '12';
}

const minutes = generateUnits(
0,
59,
minuteStep,
disabledMinutes && disabledMinutes(hour),
);
const [AMDisabled, PMDisabled] = React.useMemo(() => {
if (!use12Hours) {
return [false, false];
}
const AMPMDisabled = [true, true];
memorizedRawHours.forEach(({ disabled, value: hourValue }) => {
if (disabled) return;
if (hourValue >= 12) {
AMPMDisabled[1] = false;
} else {
AMPMDisabled[0] = false;
}
});
return AMPMDisabled;
}, [use12Hours, memorizedRawHours]);

const hours = React.useMemo(() => {
if (!use12Hours) return memorizedRawHours;
return memorizedRawHours
.filter(isPM ? hourMeta => hourMeta.value >= 12 : hourMeta => hourMeta.value < 12)
.map(hourMeta => {
const hourValue = hourMeta.value % 12;
const hourLabel = hourValue === 0 ? '12' : leftPad(hourValue, 2);
return {
...hourMeta,
label: hourLabel,
value: hourValue,
};
});
}, [use12Hours, memorizedRawHours]);

const minutes = generateUnits(0, 59, minuteStep, disabledMinutes && disabledMinutes(hour));

const seconds = generateUnits(
0,
Expand Down Expand Up @@ -207,7 +241,10 @@ function TimeBody<DateType>(props: TimeBodyProps<DateType>) {
use12Hours === true,
<TimeUnitColumn key="12hours" />,
PMIndex,
[{ label: 'AM', value: 0 }, { label: 'PM', value: 1 }],
[
{ label: 'AM', value: 0, disabled: AMDisabled },
{ label: 'PM', value: 1, disabled: PMDisabled },
],
num => {
onSelect(setTime(!!num, hour, minute, second), 'mouse');
},
Expand Down
2 changes: 1 addition & 1 deletion src/panels/TimePanel/TimeUnitColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PanelContext from '../../PanelContext';
export interface Unit {
label: React.ReactText;
value: number;
disabled?: boolean;
disabled: boolean;
}

export interface TimeUnitColumnProps {
Expand Down
34 changes: 34 additions & 0 deletions src/utils/timeUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { GenerateConfig } from '../generate';

export function setTime<DateType>(
generateConfig: GenerateConfig<DateType>,
date: DateType,
hour: number,
minute: number,
second: number,
): DateType {
let nextTime = generateConfig.setHour(date, hour);
nextTime = generateConfig.setMinute(nextTime, minute);
nextTime = generateConfig.setSecond(nextTime, second);
return nextTime;
}

export function getLowerBoundTime(
hour: number,
minute: number,
second: number,
hourStep: number,
minuteStep: number,
secondStep: number,
): [number, number, number] {
const lowerBoundHour = Math.floor(hour / hourStep) * hourStep;
if (lowerBoundHour < hour) {
return [lowerBoundHour, 60 - minuteStep, 60 - secondStep];
}
const lowerBoundMinute = Math.floor(minute / minuteStep) * minuteStep;
if (lowerBoundMinute < minute) {
return [lowerBoundHour, lowerBoundMinute, 60 - secondStep];
}
const lowerBoundSecond = Math.floor(second / secondStep) * secondStep;
return [lowerBoundHour, lowerBoundMinute, lowerBoundSecond];
}
110 changes: 93 additions & 17 deletions tests/panel.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -344,24 +344,100 @@ describe('Picker.Panel', () => {
});
});

it('time with use12Hours', () => {
const onChange = jest.fn();
const wrapper = mount(
<MomentPickerPanel
picker="time"
defaultValue={getMoment('2000-01-01 00:01:02')}
use12Hours
onChange={onChange}
/>,
);
describe('time with use12Hours', () => {
it('should work', () => {
const onChange = jest.fn();
const wrapper = mount(
<MomentPickerPanel
picker="time"
defaultValue={getMoment('2000-01-01 00:01:02')}
use12Hours
onChange={onChange}
/>,
);

wrapper
.find('.rc-picker-time-panel-column')
.last()
.find('li')
.last()
.simulate('click');
expect(isSame(onChange.mock.calls[0][0], '2000-01-01 12:01:02', 'second')).toBeTruthy();
wrapper
.find('.rc-picker-time-panel-column')
.last()
.find('li')
.last()
.simulate('click');
expect(isSame(onChange.mock.calls[0][0], '2000-01-01 12:01:02', 'second')).toBeTruthy();
});

it('should display hour from 12 at AM', () => {
const wrapper = mount(
<MomentPickerPanel
picker="time"
defaultValue={getMoment('2000-01-01 00:00:00')}
use12Hours
/>,
);

const startHour = wrapper
.find('.rc-picker-time-panel-column')
.first()
.find('li')
.first()
.text();
expect(startHour).toEqual('12');
});

it('should display hour from 12 at AM', () => {
const wrapper = mount(
<MomentPickerPanel
picker="time"
defaultValue={getMoment('2000-01-01 12:00:00')}
use12Hours
/>,
);

const startHour = wrapper
.find('.rc-picker-time-panel-column')
.first()
.find('li')
.first()
.text();
expect(startHour).toEqual('12');
});

it('should disable AM when 00 ~ 11 is disabled', () => {
const wrapper = mount(
<MomentPickerPanel
picker="time"
defaultValue={getMoment('2000-01-01 12:00:00')}
use12Hours
disabledHours={() => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]}
/>,
);

const disabledAMItem = wrapper
.find('.rc-picker-time-panel-column')
.last()
.find('li')
.first()
.find('.rc-picker-time-panel-cell-disabled');
expect(disabledAMItem.length).toEqual(1);
});

it('should disable PM when 12 ~ 23 is disabled', () => {
const wrapper = mount(
<MomentPickerPanel
picker="time"
defaultValue={getMoment('2000-01-01 12:00:00')}
use12Hours
disabledHours={() => [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]}
/>,
);

const disabledPMItem = wrapper
.find('.rc-picker-time-panel-column')
.last()
.find('li')
.last()
.find('.rc-picker-time-panel-cell-disabled');
expect(disabledPMItem.length).toEqual(1);
});
});

it('time disabled columns', () => {
Expand Down
40 changes: 40 additions & 0 deletions tests/picker.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,46 @@ describe('Picker.Basic', () => {
});
});

describe('time step', () => {
it('work with now', () => {
MockDate.set(getMoment('1990-09-03 00:09:00').toDate());
const onSelect = jest.fn();
const wrapper = mount(<MomentPicker onSelect={onSelect} picker="time" minuteStep={10} />);
wrapper.openPicker();
wrapper.find('.rc-picker-now > a').simulate('click');
expect(isSame(onSelect.mock.calls[0][0], '1990-09-03 00:00:59', 'second')).toBeTruthy();
MockDate.set(getMoment('1990-09-03 00:00:00').toDate());
});
it('should show warning when hour step is invalid', () => {
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
expect(spy).not.toBeCalled();
const wrapper = mount(<MomentPicker picker="time" hourStep={9} />);
wrapper.openPicker();
expect(spy).toBeCalledWith('Warning: `hourStep` 9 is invalid. It should be a factor of 24.');
spy.mockRestore();
});
it('should show warning when minute step is invalid', () => {
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
expect(spy).not.toBeCalled();
const wrapper = mount(<MomentPicker picker="time" minuteStep={9} />);
wrapper.openPicker();
expect(spy).toBeCalledWith(
'Warning: `minuteStep` 9 is invalid. It should be a factor of 60.',
);
spy.mockRestore();
});
it('should show warning when second step is invalid', () => {
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
expect(spy).not.toBeCalled();
const wrapper = mount(<MomentPicker picker="time" secondStep={9} />);
wrapper.openPicker();
expect(spy).toBeCalledWith(
'Warning: `secondStep` 9 is invalid. It should be a factor of 60.',
);
spy.mockRestore();
});
});

it('pass data- & aria- & role', () => {
const wrapper = mount(<MomentPicker data-test="233" aria-label="3334" role="search" />);

Expand Down
Loading