Skip to content

Commit e9411e8

Browse files
committed
chore: Internal OptionList support onMouseEnter
1 parent 89fb071 commit e9411e8

File tree

3 files changed

+47
-25
lines changed

3 files changed

+47
-25
lines changed

src/OptionList.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export interface OptionListProps<OptionsType extends object[]> {
3535
/** Tell Select that some value is now active to make accessibility work */
3636
onActiveValue: (value: RawValueType, index: number) => void;
3737
onScroll: React.UIEventHandler<HTMLDivElement>;
38+
39+
/** Tell Select that mouse enter the popup to force re-render */
40+
onMouseEnter?: React.MouseEventHandler;
3841
}
3942

4043
export interface RefOptionListProps {
@@ -70,6 +73,7 @@ const OptionList: React.RefForwardingComponent<
7073
onToggleOpen,
7174
onActiveValue,
7275
onScroll,
76+
onMouseEnter,
7377
},
7478
ref,
7579
) => {
@@ -269,6 +273,7 @@ const OptionList: React.RefForwardingComponent<
269273
onMouseDown={onListMouseDown}
270274
onScroll={onScroll}
271275
virtual={virtual}
276+
onMouseEnter={onMouseEnter}
272277
>
273278
{({ group, groupOption, data }, itemIndex) => {
274279
const { label, key } = data;

src/generate.tsx

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import * as React from 'react';
11+
import { useState, useRef, useEffect, useMemo } from 'react';
1112
import KeyCode from 'rc-util/lib/KeyCode';
1213
import classNames from 'classnames';
1314
import useMergedState from 'rc-util/lib/hooks/useMergedState';
@@ -316,17 +317,17 @@ export default function generateSelector<
316317
delete domProps[prop];
317318
});
318319

319-
const containerRef = React.useRef<HTMLDivElement>(null);
320-
const triggerRef = React.useRef<RefTriggerProps>(null);
321-
const selectorRef = React.useRef<RefSelectorProps>(null);
322-
const listRef = React.useRef<RefOptionListProps>(null);
320+
const containerRef = useRef<HTMLDivElement>(null);
321+
const triggerRef = useRef<RefTriggerProps>(null);
322+
const selectorRef = useRef<RefSelectorProps>(null);
323+
const listRef = useRef<RefOptionListProps>(null);
323324

324325
/** Used for component focused management */
325326
const [mockFocused, setMockFocused, cancelSetMockFocused] = useDelayReset();
326327

327328
// Inner id for accessibility usage. Only work in client side
328-
const [innerId, setInnerId] = React.useState<string>();
329-
React.useEffect(() => {
329+
const [innerId, setInnerId] = useState<string>();
330+
useEffect(() => {
330331
setInnerId(`rc_select_${getUUID()}`);
331332
}, []);
332333
const mergedId = id || innerId;
@@ -346,7 +347,7 @@ export default function generateSelector<
346347
showSearch !== undefined ? showSearch : isMultiple || mode === 'combobox';
347348

348349
// ============================== Ref ===============================
349-
const selectorDomRef = React.useRef<HTMLDivElement>(null);
350+
const selectorDomRef = useRef<HTMLDivElement>(null);
350351

351352
React.useImperativeHandle(ref, () => ({
352353
focus: selectorRef.current.focus,
@@ -359,7 +360,7 @@ export default function generateSelector<
359360
});
360361

361362
/** Unique raw values */
362-
const mergedRawValue = React.useMemo<RawValueType[]>(
363+
const mergedRawValue = useMemo<RawValueType[]>(
363364
() =>
364365
toInnerValue(mergedValue, {
365366
labelInValue: mergedLabelInValue,
@@ -368,14 +369,12 @@ export default function generateSelector<
368369
[mergedValue, mergedLabelInValue],
369370
);
370371
/** We cache a set of raw values to speed up check */
371-
const rawValues = React.useMemo<Set<RawValueType>>(() => new Set(mergedRawValue), [
372-
mergedRawValue,
373-
]);
372+
const rawValues = useMemo<Set<RawValueType>>(() => new Set(mergedRawValue), [mergedRawValue]);
374373

375374
// ============================= Option =============================
376375
// Set by option list active, it will merge into search input when mode is `combobox`
377-
const [activeValue, setActiveValue] = React.useState<string>(null);
378-
const [innerSearchValue, setInnerSearchValue] = React.useState('');
376+
const [activeValue, setActiveValue] = useState<string>(null);
377+
const [innerSearchValue, setInnerSearchValue] = useState('');
379378
let mergedSearchValue = innerSearchValue;
380379
if (mode === 'combobox' && mergedValue !== undefined) {
381380
mergedSearchValue = mergedValue as string;
@@ -385,7 +384,7 @@ export default function generateSelector<
385384
mergedSearchValue = inputValue;
386385
}
387386

388-
const mergedOptions = React.useMemo<OptionsType>((): OptionsType => {
387+
const mergedOptions = useMemo<OptionsType>((): OptionsType => {
389388
let newOptions = options;
390389
if (newOptions === undefined) {
391390
newOptions = convertChildrenToData(children);
@@ -407,15 +406,15 @@ export default function generateSelector<
407406
return newOptions || ([] as OptionsType);
408407
}, [options, children, mode, mergedValue]);
409408

410-
const mergedFlattenOptions: FlattenOptionsType<OptionsType> = React.useMemo(
409+
const mergedFlattenOptions: FlattenOptionsType<OptionsType> = useMemo(
411410
() => flattenOptions(mergedOptions, props),
412411
[mergedOptions],
413412
);
414413

415414
const getValueOption = useCacheOptions(mergedRawValue, mergedFlattenOptions);
416415

417416
// Display options for OptionList
418-
const displayOptions = React.useMemo<OptionsType>(() => {
417+
const displayOptions = useMemo<OptionsType>(() => {
419418
if (!mergedSearchValue || !mergedShowSearch) {
420419
return [...mergedOptions] as OptionsType;
421420
}
@@ -434,19 +433,19 @@ export default function generateSelector<
434433
return filteredOptions;
435434
}, [mergedOptions, mergedSearchValue, mode, mergedShowSearch]);
436435

437-
const displayFlattenOptions: FlattenOptionsType<OptionsType> = React.useMemo(
436+
const displayFlattenOptions: FlattenOptionsType<OptionsType> = useMemo(
438437
() => flattenOptions(displayOptions, props),
439438
[displayOptions],
440439
);
441440

442-
React.useEffect(() => {
441+
useEffect(() => {
443442
if (listRef.current && listRef.current.scrollTo) {
444443
listRef.current.scrollTo(0);
445444
}
446445
}, [mergedSearchValue]);
447446

448447
// ============================ Selector ============================
449-
let displayValues = React.useMemo<DisplayLabelValueType[]>(() => {
448+
let displayValues = useMemo<DisplayLabelValueType[]>(() => {
450449
const tmpValues = mergedRawValue.map((val: RawValueType) => {
451450
const valueOptions = getValueOption([val]);
452451
const displayValue = getLabeledValue(val, {
@@ -675,14 +674,14 @@ export default function generateSelector<
675674
};
676675

677676
// Close dropdown when disabled change
678-
React.useEffect(() => {
677+
useEffect(() => {
679678
if (innerOpen && !!disabled) {
680679
setInnerOpen(false);
681680
}
682681
}, [disabled]);
683682

684683
// Close will clean up single mode search text
685-
React.useEffect(() => {
684+
useEffect(() => {
686685
if (!mergedOpen && !isMultiple && mode !== 'combobox') {
687686
triggerSearch('', false, false);
688687
}
@@ -747,7 +746,7 @@ export default function generateSelector<
747746

748747
// ========================== Focus / Blur ==========================
749748
/** Record real focus status */
750-
const focusRef = React.useRef<boolean>(false);
749+
const focusRef = useRef<boolean>(false);
751750

752751
const onContainerFocus: React.FocusEventHandler<HTMLElement> = (...args) => {
753752
setMockFocused(true);
@@ -793,7 +792,7 @@ export default function generateSelector<
793792
};
794793

795794
const activeTimeoutIds: number[] = [];
796-
React.useEffect(
795+
useEffect(
797796
() => () => {
798797
activeTimeoutIds.forEach(timeoutId => clearTimeout(timeoutId));
799798
activeTimeoutIds.splice(0, activeTimeoutIds.length);
@@ -830,7 +829,7 @@ export default function generateSelector<
830829
};
831830

832831
// ========================= Accessibility ==========================
833-
const [accessibilityIndex, setAccessibilityIndex] = React.useState<number>(0);
832+
const [accessibilityIndex, setAccessibilityIndex] = useState<number>(0);
834833
const mergedDefaultActiveFirstOption =
835834
defaultActiveFirstOption !== undefined ? defaultActiveFirstOption : mode !== 'combobox';
836835

@@ -843,7 +842,13 @@ export default function generateSelector<
843842
};
844843

845844
// ============================= Popup ==============================
846-
const [containerWidth, setContainerWidth] = React.useState(null);
845+
const [containerWidth, setContainerWidth] = useState(null);
846+
847+
const [, forceUpdate] = useState({});
848+
// We need force update here since popup dom is render async
849+
function onPopupMouseEnter() {
850+
forceUpdate({});
851+
}
847852

848853
useLayoutEffect(() => {
849854
if (triggerOpen) {
@@ -876,6 +881,7 @@ export default function generateSelector<
876881
searchValue={mergedSearchValue}
877882
menuItemSelectedIcon={menuItemSelectedIcon}
878883
virtual={virtual !== false && dropdownMatchSelectWidth !== false}
884+
onMouseEnter={onPopupMouseEnter}
879885
/>
880886
);
881887

tests/Select.test.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,4 +1621,15 @@ describe('Select.Basic', () => {
16211621
wrapper.setProps({ options: [] });
16221622
expect(findSelection(wrapper).text()).toEqual('Bamboo');
16231623
});
1624+
1625+
// https://github.com/ant-design/ant-design/issues/24747
1626+
// This can not test function called with jest spy, coverage only
1627+
it('mouse enter to refresh', () => {
1628+
const wrapper = mount(<Select options={[{ value: 903, label: 'Bamboo' }]} open />);
1629+
wrapper
1630+
.find('List')
1631+
.find('div')
1632+
.first()
1633+
.simulate('mouseenter');
1634+
});
16241635
});

0 commit comments

Comments
 (0)