Skip to content

Commit 0b0b4f8

Browse files
authored
chore: Adjust open picker logic (#89)
* adjust open logic * not repeat open * update * fix logic * update test case * test case * trigger now
1 parent ec44c1c commit 0b0b4f8

File tree

6 files changed

+166
-98
lines changed

6 files changed

+166
-98
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# rc-picker
1+
# rc-picker
22

33
[![NPM version][npm-image]][npm-url]
44
[![build status][circleci-image]][circleci-url]

src/RangePicker.tsx

Lines changed: 89 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from 'react';
2+
import { useRef, useEffect, useState } from 'react';
23
import classNames from 'classnames';
34
import warning from 'rc-util/lib/warning';
45
import useMergedState from 'rc-util/lib/hooks/useMergedState';
@@ -205,13 +206,13 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
205206

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

208-
const containerRef = React.useRef<HTMLDivElement>(null);
209-
const panelDivRef = React.useRef<HTMLDivElement>(null);
210-
const startInputDivRef = React.useRef<HTMLDivElement>(null);
211-
const endInputDivRef = React.useRef<HTMLDivElement>(null);
212-
const separatorRef = React.useRef<HTMLDivElement>(null);
213-
const startInputRef = React.useRef<HTMLInputElement>(null);
214-
const endInputRef = React.useRef<HTMLInputElement>(null);
209+
const containerRef = useRef<HTMLDivElement>(null);
210+
const panelDivRef = useRef<HTMLDivElement>(null);
211+
const startInputDivRef = useRef<HTMLDivElement>(null);
212+
const endInputDivRef = useRef<HTMLDivElement>(null);
213+
const separatorRef = useRef<HTMLDivElement>(null);
214+
const startInputRef = useRef<HTMLInputElement>(null);
215+
const endInputRef = useRef<HTMLInputElement>(null);
215216

216217
// ============================= Misc ==============================
217218
const formatList = toArray(getDefaultFormat(format, picker, showTime, use12Hours));
@@ -222,7 +223,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
222223
});
223224

224225
// Operation ref
225-
const operationRef: React.MutableRefObject<ContextOperationRefProps | null> = React.useRef<
226+
const operationRef: React.MutableRefObject<ContextOperationRefProps | null> = useRef<
226227
ContextOperationRefProps
227228
>(null);
228229

@@ -270,10 +271,10 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
270271
},
271272
});
272273

273-
const [rangeHoverValue, setRangeHoverValue] = React.useState<RangeValue<DateType>>(null);
274+
const [rangeHoverValue, setRangeHoverValue] = useState<RangeValue<DateType>>(null);
274275

275276
// ========================== Hover Range ==========================
276-
const [hoverRangedValue, setHoverRangedValue] = React.useState<RangeValue<DateType>>(null);
277+
const [hoverRangedValue, setHoverRangedValue] = useState<RangeValue<DateType>>(null);
277278

278279
const onDateMouseEnter = (date: DateType) => {
279280
setHoverRangedValue(updateValues(selectedValue, date, mergedActivePickerIndex));
@@ -287,7 +288,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
287288
value: mode,
288289
});
289290

290-
React.useEffect(() => {
291+
useEffect(() => {
291292
setInnerModes([picker, picker]);
292293
}, [picker]);
293294

@@ -330,34 +331,48 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
330331

331332
// ============================= Popup =============================
332333
// Popup min width
333-
const [popupMinWidth, setPopupMinWidth] = React.useState(0);
334-
React.useEffect(() => {
334+
const [popupMinWidth, setPopupMinWidth] = useState(0);
335+
useEffect(() => {
335336
if (!mergedOpen && containerRef.current) {
336337
setPopupMinWidth(containerRef.current.offsetWidth);
337338
}
338339
}, [mergedOpen]);
339340

340341
// ============================ Trigger ============================
341-
let triggerOpen: (newOpen: boolean, index: 0 | 1, preventChangeEvent?: boolean) => void;
342+
// We record opened status here in case repeat open with picker
343+
// - start -> end
344+
// - end -> start
345+
// - start -> end -> not start!
346+
const openRecordsRef = useRef<Record<number, boolean>>({});
342347

343-
const triggerChange = (
344-
newValue: RangeValue<DateType>,
345-
config: {
346-
source?: 'open';
347-
forceInput?: boolean;
348-
} = {},
349-
) => {
350-
const { forceInput = true, source } = config;
348+
function triggerOpen(newOpen: boolean, index: 0 | 1) {
349+
if (newOpen) {
350+
openRecordsRef.current[index] = true;
351+
352+
setMergedActivePickerIndex(index);
353+
triggerInnerOpen(newOpen);
354+
355+
// Open to reset view date
356+
if (!mergedOpen) {
357+
setViewDate(null, index);
358+
}
359+
} else if (mergedActivePickerIndex === index) {
360+
openRecordsRef.current = {};
361+
triggerInnerOpen(newOpen);
362+
}
363+
}
351364

365+
function triggerChange(newValue: RangeValue<DateType>, sourceIndex: 0 | 1) {
352366
let values = newValue;
353367
const startValue = getValue(values, 0);
354368
let endValue = getValue(values, 1);
355369

370+
// >>>>> Format start & end values
356371
if (startValue && endValue && generateConfig.isAfter(startValue, endValue)) {
357372
if (
358373
// WeekPicker only compare week
359374
(picker === 'week' && !isSameWeek(generateConfig, locale.locale, startValue, endValue)) ||
360-
// WeekPicker only compare week
375+
// QuotaPicker only compare week
361376
(picker === 'quarter' && !isSameQuarter(generateConfig, startValue, endValue)) ||
362377
// Other non-TimePicker compare date
363378
(picker !== 'week' &&
@@ -368,6 +383,9 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
368383
// Clean up end date when start date is after end date
369384
values = [startValue, null];
370385
endValue = null;
386+
387+
// Clean up cache since invalidate
388+
openRecordsRef.current = {};
371389
} else if (picker !== 'time' || order !== false) {
372390
// Reorder when in same date
373391
values = reorderValues(values, generateConfig);
@@ -389,6 +407,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
389407
onCalendarChange(values, [startStr, endStr]);
390408
}
391409

410+
// >>>>> Trigger `onChange` event
392411
const canStartValueTrigger = canValueTrigger(startValue, 0, mergedDisabled, allowEmpty);
393412
const canEndValueTrigger = canValueTrigger(endValue, 1, mergedDisabled, allowEmpty);
394413

@@ -397,9 +416,6 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
397416
if (canTrigger) {
398417
// Trigger onChange only when value is validate
399418
setInnerValue(values);
400-
if (source !== 'open') {
401-
triggerOpen(false, mergedActivePickerIndex, true);
402-
}
403419

404420
if (
405421
onChange &&
@@ -408,45 +424,38 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
408424
) {
409425
onChange(values, [startStr, endStr]);
410426
}
411-
} else if (forceInput) {
412-
// Open miss value panel to force user input
413-
const missingValueIndex = canStartValueTrigger ? 1 : 0;
427+
}
414428

415-
// Same index means user choice to close picker
416-
if (missingValueIndex === mergedActivePickerIndex) {
417-
return;
418-
}
429+
// >>>>> Open picker when
419430

420-
if (source !== 'open') {
421-
triggerOpen(true, missingValueIndex);
422-
}
431+
// * Finished the first picker selection
432+
// * Some of start / end is not ready
433+
// Open miss value panel to force user input
434+
let nextOpenIndex: 0 | 1 = null;
435+
if (sourceIndex === 0 && !mergedDisabled[1]) {
436+
nextOpenIndex = 1;
437+
} else if (sourceIndex === 1 && !canStartValueTrigger) {
438+
nextOpenIndex = 0;
439+
}
440+
441+
if (
442+
nextOpenIndex !== null &&
443+
nextOpenIndex !== mergedActivePickerIndex &&
444+
!openRecordsRef.current[nextOpenIndex]
445+
) {
446+
triggerOpen(true, nextOpenIndex);
423447

424448
// Delay to focus to avoid input blur trigger expired selectedValues
425449
setTimeout(() => {
426-
const inputRef = [startInputRef, endInputRef][missingValueIndex];
450+
const inputRef = [startInputRef, endInputRef][nextOpenIndex];
427451
if (inputRef.current) {
428452
inputRef.current.focus();
429453
}
430454
}, 0);
455+
} else {
456+
triggerOpen(false, sourceIndex);
431457
}
432-
};
433-
434-
triggerOpen = (newOpen: boolean, index: 0 | 1, preventChangeEvent: boolean = false) => {
435-
if (newOpen) {
436-
setMergedActivePickerIndex(index);
437-
triggerInnerOpen(newOpen);
438-
439-
// Open to reset view date
440-
if (!mergedOpen) {
441-
setViewDate(null, index);
442-
}
443-
} else if (mergedActivePickerIndex === index) {
444-
triggerInnerOpen(newOpen);
445-
if (!preventChangeEvent) {
446-
triggerChange(selectedValue, { source: 'open' });
447-
}
448-
}
449-
};
458+
}
450459

451460
const forwardKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
452461
if (mergedOpen && operationRef.current && operationRef.current.onKeyDown) {
@@ -493,12 +502,12 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
493502
}
494503
};
495504

496-
const [startText, triggerStartTextChange, resetStartText] = useTextValueMapping<DateType>({
505+
const [startText, triggerStartTextChange, resetStartText] = useTextValueMapping({
497506
valueTexts: startValueTexts,
498507
onTextChange: newText => onTextChange(newText, 0),
499508
});
500509

501-
const [endText, triggerEndTextChange, resetEndText] = useTextValueMapping<DateType>({
510+
const [endText, triggerEndTextChange, resetEndText] = useTextValueMapping({
502511
valueTexts: endValueTexts,
503512
onTextChange: newText => onTextChange(newText, 1),
504513
});
@@ -519,13 +528,23 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
519528
onFocus(e);
520529
}
521530
},
522-
triggerOpen: (newOpen: boolean) => triggerOpen(newOpen, index),
531+
triggerOpen: (newOpen: boolean) => {
532+
// >>> triggerOpenOld(newOpen, index)
533+
triggerOpen(newOpen, index);
534+
535+
// Only blur will close open
536+
if (!newOpen && mergedActivePickerIndex === index) {
537+
triggerChange(selectedValue, index);
538+
}
539+
},
523540
onSubmit: () => {
524-
triggerChange(selectedValue);
541+
// >>> triggerChangeOld(selectedValue);
542+
triggerChange(selectedValue, index);
525543
resetText();
526544
},
527545
onCancel: () => {
528-
triggerOpen(false, index, true);
546+
// >>> triggerOpenOld(false, index, true);
547+
triggerOpen(false, index);
529548
setSelectedValue(mergedValue);
530549
resetText();
531550
},
@@ -552,7 +571,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
552571
? generateConfig.locale.format(locale.locale, mergedValue[1], 'YYYYMMDDHHmmss')
553572
: '';
554573

555-
React.useEffect(() => {
574+
useEffect(() => {
556575
if (!mergedOpen) {
557576
setSelectedValue(mergedValue);
558577

@@ -570,7 +589,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
570589
}, [mergedOpen, startValueTexts, endValueTexts]);
571590

572591
// Sync innerValue with control mode
573-
React.useEffect(() => {
592+
useEffect(() => {
574593
setSelectedValue(mergedValue);
575594
}, [startStr, endStr]);
576595

@@ -618,7 +637,8 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
618637
return {
619638
label,
620639
onClick: () => {
621-
triggerChange(newValues);
640+
// triggerChangeOld(newValues);
641+
triggerChange(newValues, null);
622642
},
623643
onMouseEnter: () => {
624644
setRangeHoverValue(newValues);
@@ -754,7 +774,8 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
754774
rangeList,
755775
onOk: () => {
756776
if (getValue(selectedValue, mergedActivePickerIndex)) {
757-
triggerChange(selectedValue);
777+
// triggerChangeOld(selectedValue);
778+
triggerChange(selectedValue, mergedActivePickerIndex);
758779
if (onOk) {
759780
onOk(selectedValue);
760781
}
@@ -864,7 +885,8 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
864885
values = updateValues(values, null, 1);
865886
}
866887

867-
triggerChange(values, { forceInput: false });
888+
// >>> triggerChangeOld(values, { forceInput: false });
889+
triggerChange(values, null);
868890
}}
869891
className={`${prefixCls}-clear`}
870892
>
@@ -895,7 +917,8 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
895917

896918
if (type === 'submit' || (type !== 'key' && !needConfirmButton)) {
897919
// triggerChange will also update selected values
898-
triggerChange(values);
920+
// triggerChangeOld(values);
921+
triggerChange(values, mergedActivePickerIndex);
899922
} else {
900923
setSelectedValue(values);
901924
}

src/hooks/useTextValueMapping.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22

3-
export default function useTextValueMapping<ValueType>({
3+
export default function useTextValueMapping({
44
valueTexts,
55
onTextChange,
66
}: {

0 commit comments

Comments
 (0)