Skip to content

Commit 1d1d86e

Browse files
committed
fix: Debounce of onOpenChange repeat trigger
1 parent c69f892 commit 1d1d86e

File tree

4 files changed

+44
-10
lines changed

4 files changed

+44
-10
lines changed

examples/uncontrolled.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,23 @@ export default () => (
99
<div>
1010
<div style={{ margin: '0 8px' }}>
1111
<h3>Uncontrolled</h3>
12+
<Picker<Moment>
13+
generateConfig={momentGenerateConfig}
14+
locale={zhCN}
15+
picker="week"
16+
allowClear
17+
onOpenChange={open => {
18+
console.log('1 =>', open);
19+
}}
20+
/>
1221
<Picker<Moment>
1322
generateConfig={momentGenerateConfig}
1423
locale={zhCN}
1524
picker="week"
1625
allowClear
1726
open
1827
onOpenChange={open => {
19-
console.log('=>', open);
28+
console.log('2 =>', open);
2029
}}
2130
/>
2231
<button type="button">233</button>

src/Picker.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { PickerMode } from './interface';
2828
import {
2929
getDefaultFormat,
3030
getInputSize,
31-
addGlobalClickEvent,
31+
addGlobalMouseDownEvent,
3232
} from './utils/uiUtil';
3333

3434
export interface PickerSharedProps<DateType> {
@@ -326,10 +326,25 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
326326
}
327327
};
328328

329-
const onInputBlur: React.FocusEventHandler<HTMLInputElement> = e => {
329+
/**
330+
* We will prevent blur to handle open event when user click outside,
331+
* since this will repeat trigger `onOpenChange` event.
332+
*/
333+
const preventBlurRef = React.useRef<boolean>(false);
334+
335+
const triggerClose = () => {
330336
triggerOpen(false);
331337
setInnerValue(selectedValue);
332338
triggerChange(selectedValue);
339+
};
340+
341+
const onInputBlur: React.FocusEventHandler<HTMLInputElement> = e => {
342+
if (preventBlurRef.current) {
343+
preventBlurRef.current = false;
344+
return;
345+
}
346+
347+
triggerClose();
333348
setFocused(false);
334349

335350
if (onBlur) {
@@ -361,7 +376,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
361376

362377
// Global click handler
363378
React.useEffect(() =>
364-
addGlobalClickEvent(({ target }: MouseEvent) => {
379+
addGlobalMouseDownEvent(({ target }: MouseEvent) => {
365380
if (
366381
mergedOpen &&
367382
panelDivRef.current &&
@@ -370,7 +385,13 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
370385
!inputDivRef.current.contains(target as Node) &&
371386
onOpenChange
372387
) {
373-
onOpenChange(false);
388+
preventBlurRef.current = true;
389+
triggerClose();
390+
391+
// Always set back in case `onBlur` prevented by user
392+
window.setTimeout(() => {
393+
preventBlurRef.current = false;
394+
}, 0);
374395
}
375396
}),
376397
);

src/utils/uiUtil.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ type ClickEventHandler = (event: MouseEvent) => void;
157157
let globalClickFunc: ClickEventHandler | null = null;
158158
const clickCallbacks = new Set<ClickEventHandler>();
159159

160-
export function addGlobalClickEvent(callback: ClickEventHandler) {
160+
export function addGlobalMouseDownEvent(callback: ClickEventHandler) {
161161
if (
162162
!globalClickFunc &&
163163
typeof window !== 'undefined' &&
@@ -168,15 +168,15 @@ export function addGlobalClickEvent(callback: ClickEventHandler) {
168168
queueFunc(e);
169169
});
170170
};
171-
window.addEventListener('click', globalClickFunc);
171+
window.addEventListener('mousedown', globalClickFunc);
172172
}
173173

174174
clickCallbacks.add(callback);
175175

176176
return () => {
177177
clickCallbacks.delete(callback);
178178
if (clickCallbacks.size === 0) {
179-
window.removeEventListener('click', globalClickFunc!);
179+
window.removeEventListener('mousedown', globalClickFunc!);
180180
globalClickFunc = null;
181181
}
182182
};

tests/picker.spec.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,21 @@ describe('Basic', () => {
120120
});
121121

122122
it('fixed open need repeat trigger onOpenChange', () => {
123+
jest.useFakeTimers();
123124
const onOpenChange = jest.fn();
124-
mount(<MomentPicker onOpenChange={onOpenChange} open />);
125+
const wrapper = mount(<MomentPicker onOpenChange={onOpenChange} open />);
125126

126127
for (let i = 0; i < 10; i += 1) {
127-
const clickEvent = new Event('click');
128+
const clickEvent = new Event('mousedown');
128129
Object.defineProperty(clickEvent, 'target', {
129130
get: () => document.body,
130131
});
131132
window.dispatchEvent(clickEvent);
133+
wrapper.find('input').simulate('blur');
132134
expect(onOpenChange).toHaveBeenCalledTimes(i + 1);
133135
}
136+
jest.runAllTimers();
137+
jest.useRealTimers();
134138
});
135139

136140
it('disabled should not open', () => {

0 commit comments

Comments
 (0)