Skip to content

Commit

Permalink
chore: range of it
Browse files Browse the repository at this point in the history
  • Loading branch information
zombieJ committed Nov 3, 2023
1 parent 1fa9cec commit dfa16db
Show file tree
Hide file tree
Showing 17 changed files with 317 additions and 63 deletions.
3 changes: 3 additions & 0 deletions assets/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
&-focused {
border: 1px solid blue;
}
&-invalid {
box-shadow: 0 0 2px red;
}
&-panel {
display: inline-block;
vertical-align: top;
Expand Down
5 changes: 3 additions & 2 deletions docs/examples/debug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export default () => {
// // has start
// [moment('2023-11-15'), null],
// has end
[null, moment('2023-11-15')],
// [null, moment('2023-11-15')],
[moment('2023-11-5'), moment('2023-12-29')],
);

return (
Expand All @@ -61,7 +62,7 @@ export default () => {
format: 'YYYY-MM-DD',
// format: 'YYYY-MM-DD HH:mm:ss.SSS',
// // format: 'YYYYMMDD',
align: true,
// align: true,
}}
// preserveInvalidOnBlur
// showTime={{}}
Expand Down
101 changes: 90 additions & 11 deletions src/NewPicker/PickerInput/RangePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useMergedState } from 'rc-util';
import * as React from 'react';
import useShowTime from '../hooks/useShowTime';
import type {
InternalMode,
OnOpenChange,
Expand All @@ -12,6 +13,7 @@ import type { PickerPanelProps } from '../PickerPanel';
import PickerTrigger from '../PickerTrigger';
import PickerContext from './context';
import { useFieldFormat } from './hooks/useFieldFormat';
import useInvalidate from './hooks/useInvalidate';
import useOpen from './hooks/useOpen';
import useRangeDisabledDate from './hooks/useRangeDisabledDate';
import useRangeValue from './hooks/useRangeValue';
Expand Down Expand Up @@ -67,6 +69,7 @@ export default function Picker<DateType = any>(props: RangePickerProps<DateType>

// Value
changeOnBlur,
order = true,

// Disabled
disabled,
Expand Down Expand Up @@ -114,16 +117,20 @@ export default function Picker<DateType = any>(props: RangePickerProps<DateType>

const selectorRef = React.useRef<SelectorRef>();

// ======================= ShowTime =======================
const mergedShowTime = useShowTime(showTime);

// ======================== Picker ========================
const [mergedMode, setMergedMode] = useMergedState(picker, {
value: mode,
onChange: onModeChange,
});

const internalPicker: InternalMode = picker === 'date' && showTime ? 'datetime' : picker;
const internalPicker: InternalMode = picker === 'date' && mergedShowTime ? 'datetime' : picker;

/** Extends from `mergedMode` to patch `datetime` mode */
const internalMode: InternalMode = mergedMode === 'date' && showTime ? 'datetime' : mergedMode;
const internalMode: InternalMode =
mergedMode === 'date' && mergedShowTime ? 'datetime' : mergedMode;

const needConfirm = internalMode === 'time' || internalMode === 'datetime';

Expand Down Expand Up @@ -168,14 +175,24 @@ export default function Picker<DateType = any>(props: RangePickerProps<DateType>
const mergedDisabled = separateConfig(disabled, false);
const mergedAllowEmpty = separateConfig(allowEmpty, false);

const isInvalidateDate = useInvalidate(generateConfig, picker, disabledDate, mergedShowTime);

// ======================== Order =========================
// When exist disabled, it should not support order
const orderOnChange = mergedDisabled.some((d) => d) ? false : order;

// ======================== Value =========================
const [calendarValue, triggerCalendarChange, triggerSubmitChange] = useRangeValue({
...props,
formatList,
allowEmpty: mergedAllowEmpty,
disabled: mergedDisabled,
focused,
});
const [calendarValue, triggerCalendarChange, triggerSubmitChange] = useRangeValue(
{
...props,
formatList,
allowEmpty: mergedAllowEmpty,
focused,
order,
},
orderOnChange,
isInvalidateDate,
);

// ===================== DisabledDate =====================
const mergedDisabledDate = useRangeDisabledDate(
Expand All @@ -187,6 +204,36 @@ export default function Picker<DateType = any>(props: RangePickerProps<DateType>
disabledDate,
);

// ======================= Validate =======================
const isInvalidRange = React.useMemo(() => {
// Not check if get focused
if (focused) {
return false;
}

const [start, end] = calendarValue;

if (
// No start
(!mergedAllowEmpty[0] && !start) ||
// No end
(!mergedAllowEmpty[1] && !end)
) {
return true;
}

if (
// Invalidate start
(start && isInvalidateDate(start)) ||
// Invalidate end
(end && isInvalidateDate(end))
) {
return true;
}

return false;
}, [focused, calendarValue, mergedAllowEmpty, isInvalidateDate]);

// ===================== Picker Value =====================
const [mergedStartPickerValue, setStartPickerValue] = useMergedState(
() => defaultPickerValue?.[0] || calendarValue?.[0] || generateConfig.getNow(),
Expand Down Expand Up @@ -284,7 +331,27 @@ export default function Picker<DateType = any>(props: RangePickerProps<DateType>
setMergeOpen(true);
};

// ======================== Panels ========================
// ======================== Hover =========================
const [hoverDate, setHoverDate] = React.useState<DateType>(null);

const hoverValues = React.useMemo(() => {
const clone: RangeValueType<DateType> = [...calendarValue];

if (hoverDate) {
clone[activeIndex] = hoverDate;
}

return clone;
}, [calendarValue, hoverDate, activeIndex]);

// ========================================================
// == Panels ==
// ========================================================
const onPanelHover = (date: DateType) => {
setHoverDate(date);
};

// >>> Focus
const onPanelFocus: React.FocusEventHandler<HTMLDivElement> = () => {
setFocused(true);
setMergeOpen(true);
Expand All @@ -294,6 +361,7 @@ export default function Picker<DateType = any>(props: RangePickerProps<DateType>
setFocused(false);
};

// >>> Calendar
const onPanelCalendarChange: PickerPanelProps<DateType>['onChange'] = (date) => {
const clone: RangeValueType<DateType> = [...calendarValue];
clone[activeIndex] = date;
Expand All @@ -305,6 +373,7 @@ export default function Picker<DateType = any>(props: RangePickerProps<DateType>
}
};

// >>> Close
const onPopupClose = () => {
if (changeOnBlur) {
triggerCalendarChange(calendarValue);
Expand All @@ -314,13 +383,16 @@ export default function Picker<DateType = any>(props: RangePickerProps<DateType>
setMergeOpen(false);
};

// >>> Value
const panelValue = calendarValue[activeIndex] || null;

// >>> Render
const panel = (
<Popup
// MISC
{...(props as Omit<RangePickerProps<DateType>, 'onChange' | 'onCalendarChange'>)}
showNow={mergedShowNow}
showTime={mergedShowTime}
range
// Disabled
disabledDate={mergedDisabledDate}
Expand All @@ -339,6 +411,9 @@ export default function Picker<DateType = any>(props: RangePickerProps<DateType>
// PickerValue
pickerValue={currentPickerValue}
onPickerValueChange={setCurrentPickerValue}
//Hover
hoverValue={hoverValues}
onHover={onPanelHover}
// Submit
needConfirm={needConfirm}
onSubmit={triggerChangeAndFocusNext}
Expand All @@ -357,6 +432,7 @@ export default function Picker<DateType = any>(props: RangePickerProps<DateType>
);

// ======================== Render ========================

return (
<PickerContext.Provider value={context}>
<PickerTrigger
Expand All @@ -381,11 +457,12 @@ export default function Picker<DateType = any>(props: RangePickerProps<DateType>
suffixIcon={suffixIcon}
// Active
activeIndex={focusedIndex}
activeHelp={!!hoverDate}
onFocus={onSelectorFocus}
onBlur={onSelectorBlur}
onSubmit={triggerChangeAndFocusNext}
// Change
value={calendarValue}
value={hoverValues}
format={formatList}
maskFormat={maskFormat}
onChange={onSelectorChange}
Expand All @@ -396,6 +473,8 @@ export default function Picker<DateType = any>(props: RangePickerProps<DateType>
onOpenChange={onSelectorOpenChange}
// Click
onClick={onSelectorClick}
// Invalid
invalid={isInvalidRange}
/>
</PickerTrigger>
</PickerContext.Provider>
Expand Down
14 changes: 10 additions & 4 deletions src/NewPicker/PickerInput/Selector/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
value?: string;
onChange: (value: string) => void;
onEnter: VoidFunction;
helped?: boolean;
/**
* Trigger when input need additional help.
* Like open the popup for interactive.
Expand All @@ -46,6 +47,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
validateFormat,
onChange,
onInput,
helped,
onHelp,
onEnter,
preserveInvalidOnBlur,
Expand Down Expand Up @@ -79,10 +81,13 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
// ======================== Format ========================
const maskFormat = React.useMemo(() => new MaskFormat(format || ''), [format]);

const [selectionStart, selectionEnd] = React.useMemo(
() => maskFormat.getSelection(focusCellIndex),
[maskFormat, focusCellIndex],
);
const [selectionStart, selectionEnd] = React.useMemo(() => {
if (helped) {
return [0, 0];
}

return maskFormat.getSelection(focusCellIndex);
}, [maskFormat, focusCellIndex, helped]);

// ======================== Modify ========================
// When input modify content, trigger `onText` if is not the format
Expand Down Expand Up @@ -346,6 +351,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
<div
className={classNames(inputPrefixCls, {
[`${inputPrefixCls}-active`]: active,
[`${inputPrefixCls}-placeholder`]: helped,
})}
>
<input
Expand Down
5 changes: 5 additions & 0 deletions src/NewPicker/PickerInput/Selector/RangeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const RangeSelector = React.forwardRef<SelectorRef, RangeSelectorProps>((props,
suffixIcon,
separator = '~',
activeIndex,
activeHelp,
onFocus,
onBlur,
locale,
Expand All @@ -35,6 +36,7 @@ const RangeSelector = React.forwardRef<SelectorRef, RangeSelectorProps>((props,

// Disabled
disabled,
invalid,

// Open
open,

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable open.
Expand Down Expand Up @@ -97,6 +99,8 @@ const RangeSelector = React.forwardRef<SelectorRef, RangeSelectorProps>((props,

active: activeIndex === index,

helped: activeHelp && activeIndex === index,

disabled: disabled[index],

onFocus: (event) => {
Expand Down Expand Up @@ -143,6 +147,7 @@ const RangeSelector = React.forwardRef<SelectorRef, RangeSelectorProps>((props,
<div
className={classNames(prefixCls, `${prefixCls}-range`, {
[`${prefixCls}-focused`]: activeIndex !== null,
[`${prefixCls}-invalid`]: invalid,
})}
ref={rootRef}
onClick={onClick}
Expand Down
52 changes: 52 additions & 0 deletions src/NewPicker/PickerInput/hooks/useInvalidate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useEvent } from 'rc-util';
import type { GenerateConfig } from '../../../generate';
import type { PanelMode, SharedPickerProps, SharedTimeProps } from '../../interface';

/**
* Check if provided date is valid for the `disabledDate` & `showTime.disabledTime`.
*/
export default function useInvalidate<DateType = any>(
generateConfig: GenerateConfig<DateType>,
picker: PanelMode,
disabledDate?: SharedPickerProps<DateType>['disabledDate'],
showTime?: SharedTimeProps<DateType>,
) {
// Check disabled date
const isInvalidate = useEvent((date: DateType) => {
if (disabledDate && disabledDate(date, { type: picker })) {
return true;
}

if (picker === 'date' && showTime) {
const { disabledHours, disabledMinutes, disabledSeconds, disabledMilliSeconds } =
showTime.disabledTime?.(date) || {};

const hour = generateConfig.getHour(date);
const minute = generateConfig.getMinute(date);
const second = generateConfig.getSecond(date);
const millisecond = generateConfig.getMillisecond(date);

if (disabledHours && disabledHours().includes(hour)) {
return true;
}

if (disabledMinutes && disabledMinutes(hour).includes(minute)) {
return true;
}

if (disabledSeconds && disabledSeconds(hour, minute).includes(second)) {
return true;
}

if (
disabledMilliSeconds &&
disabledMilliSeconds(hour, minute, second).includes(millisecond)
) {
return true;
}
}
return false;
});

return isInvalidate;
}
Loading

0 comments on commit dfa16db

Please sign in to comment.