Skip to content

Commit

Permalink
fix: Should scroll when activeKey changed (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
zombieJ authored Jan 20, 2022
1 parent 1f183c5 commit 5905b39
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 92 deletions.
189 changes: 111 additions & 78 deletions src/OptionList/Column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ export default function Column({

const hoverOpen = expandTrigger === 'hover';

// ============================ Render ============================
return (
<ul className={menuPrefixCls} role="menu">
{options.map(option => {
// ============================ Option ============================
const optionInfoList = React.useMemo(
() =>
options.map(option => {
const { disabled } = option;
const searchOptions = option[SEARCH_MARK];
const label = option[fieldNames.label];
Expand All @@ -76,86 +76,119 @@ export default function Column({
// >>>>> halfChecked
const halfChecked = halfCheckedSet.has(fullPathKey);

// >>>>> Open
const triggerOpenPath = () => {
if (!disabled && (!hoverOpen || !isMergedLeaf)) {
onActive(fullPath);
}
return {
disabled,
label,
value,
isLeaf: isMergedLeaf,
isLoading,
checked,
halfChecked,
option,
fullPath,
fullPathKey,
};
}),
[options, checkedSet, fieldNames, halfCheckedSet, loadingKeys, prevValuePath],
);

// >>>>> Selection
const triggerSelect = () => {
if (isSelectable(option)) {
onSelect(fullPath, isMergedLeaf);
}
};
// ============================ Render ============================
return (
<ul className={menuPrefixCls} role="menu">
{optionInfoList.map(
({
disabled,
label,
value,
isLeaf: isMergedLeaf,
isLoading,
checked,
halfChecked,
option,
fullPath,
fullPathKey,
}) => {
// >>>>> Open
const triggerOpenPath = () => {
if (!disabled && (!hoverOpen || !isMergedLeaf)) {
onActive(fullPath);
}
};

// >>>>> Selection
const triggerSelect = () => {
if (isSelectable(option)) {
onSelect(fullPath, isMergedLeaf);
}
};

// >>>>> Title
let title: string;
if (typeof option.title === 'string') {
title = option.title;
} else if (typeof label === 'string') {
title = label;
}
// >>>>> Title
let title: string;
if (typeof option.title === 'string') {
title = option.title;
} else if (typeof label === 'string') {
title = label;
}

// >>>>> Render
return (
<li
key={fullPathKey}
className={classNames(menuItemPrefixCls, {
[`${menuItemPrefixCls}-expand`]: !isMergedLeaf,
[`${menuItemPrefixCls}-active`]: activeValue === value,
[`${menuItemPrefixCls}-disabled`]: disabled,
[`${menuItemPrefixCls}-loading`]: isLoading,
})}
style={dropdownMenuColumnStyle}
role="menuitemcheckbox"
title={title}
aria-checked={checked}
data-path-key={fullPathKey}
onClick={() => {
triggerOpenPath();
if (!multiple || isMergedLeaf) {
triggerSelect();
}
}}
onDoubleClick={() => {
if (changeOnSelect) {
onToggleOpen(false);
}
}}
onMouseEnter={() => {
if (hoverOpen) {
// >>>>> Render
return (
<li
key={fullPathKey}
className={classNames(menuItemPrefixCls, {
[`${menuItemPrefixCls}-expand`]: !isMergedLeaf,
[`${menuItemPrefixCls}-active`]: activeValue === value,
[`${menuItemPrefixCls}-disabled`]: disabled,
[`${menuItemPrefixCls}-loading`]: isLoading,
})}
style={dropdownMenuColumnStyle}
role="menuitemcheckbox"
title={title}
aria-checked={checked}
data-path-key={fullPathKey}
onClick={() => {
triggerOpenPath();
}
}}
onMouseDown={e => {
// Prevent selector from blurring
e.preventDefault();
}}
>
{multiple && (
<Checkbox
prefixCls={`${prefixCls}-checkbox`}
checked={checked}
halfChecked={halfChecked}
disabled={disabled}
onClick={(e: React.MouseEvent<HTMLSpanElement>) => {
e.stopPropagation();
if (!multiple || isMergedLeaf) {
triggerSelect();
}}
/>
)}
<div className={`${menuItemPrefixCls}-content`}>{option[fieldNames.label]}</div>
{!isLoading && expandIcon && !isMergedLeaf && (
<div className={`${menuItemPrefixCls}-expand-icon`}>{expandIcon}</div>
)}
{isLoading && loadingIcon && (
<div className={`${menuItemPrefixCls}-loading-icon`}>{loadingIcon}</div>
)}
</li>
);
})}
}
}}
onDoubleClick={() => {
if (changeOnSelect) {
onToggleOpen(false);
}
}}
onMouseEnter={() => {
if (hoverOpen) {
triggerOpenPath();
}
}}
onMouseDown={e => {
// Prevent selector from blurring
e.preventDefault();
}}
>
{multiple && (
<Checkbox
prefixCls={`${prefixCls}-checkbox`}
checked={checked}
halfChecked={halfChecked}
disabled={disabled}
onClick={(e: React.MouseEvent<HTMLSpanElement>) => {
e.stopPropagation();
triggerSelect();
}}
/>
)}
<div className={`${menuItemPrefixCls}-content`}>{label}</div>
{!isLoading && expandIcon && !isMergedLeaf && (
<div className={`${menuItemPrefixCls}-expand-icon`}>{expandIcon}</div>
)}
{isLoading && loadingIcon && (
<div className={`${menuItemPrefixCls}-loading-icon`}>{loadingIcon}</div>
)}
</li>
);
},
)}
</ul>
);
}
20 changes: 11 additions & 9 deletions src/OptionList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,17 @@ const RefOptionList = React.forwardRef<RefOptionListProps>((props, ref) => {
}
};

useKeyboard(
ref,
mergedOptions,
fieldNames,
activeValueCells,
onPathOpen,
containerRef,
onKeyboardSelect,
);
useKeyboard(ref, mergedOptions, fieldNames, activeValueCells, onPathOpen, onKeyboardSelect);

// >>>>> Active Scroll
React.useEffect(() => {
for (let i = 0; i < activeValueCells.length; i += 1) {
const cellPath = activeValueCells.slice(0, i + 1);
const cellKeyPath = toPathKey(cellPath);
const ele = containerRef.current?.querySelector(`li[data-path-key="${cellKeyPath}"]`);
ele?.scrollIntoView?.({ block: 'nearest' });
}
}, [activeValueCells]);

// ========================== Render ==========================
// >>>>> Empty
Expand Down
5 changes: 0 additions & 5 deletions src/OptionList/useKeyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as React from 'react';
import type { RefOptionListProps } from 'rc-select/lib/OptionList';
import KeyCode from 'rc-util/lib/KeyCode';
import type { DefaultOptionType, InternalFieldNames, SingleValueType } from '../Cascader';
import { toPathKey } from '../utils/commonUtil';
import { useBaseProps } from 'rc-select';

export default (
Expand All @@ -11,7 +10,6 @@ export default (
fieldNames: InternalFieldNames,
activeValueCells: React.Key[],
setActiveValueCells: (activeValueCells: React.Key[]) => void,
containerRef: React.RefObject<HTMLElement>,
onKeyBoardSelect: (valueCells: SingleValueType, option: DefaultOptionType) => void,
) => {
const { direction, searchValue, toggleOpen, open } = useBaseProps();
Expand Down Expand Up @@ -56,9 +54,6 @@ export default (
// Update active value cells and scroll to target element
const internalSetActiveValueCells = (next: React.Key[]) => {
setActiveValueCells(next);

const ele = containerRef.current?.querySelector(`li[data-path-key="${toPathKey(next)}"]`);
ele?.scrollIntoView?.({ block: 'nearest' });
};

// Same options offset
Expand Down
39 changes: 39 additions & 0 deletions tests/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -767,4 +767,43 @@ describe('Cascader.Basic', () => {
expect(wrapper.find('.rc-cascader-selection-item-content').last().text()).toEqual('Child');
});
});

describe('open should auto scroll to position', () => {
let spyScroll: ReturnType<typeof spyElementPrototypes>;
let scrollTimes = 0;

beforeAll(() => {
spyScroll = spyElementPrototypes(HTMLElement, {
scrollIntoView: () => {
scrollTimes += 1;
},
});
});

beforeEach(() => {
scrollTimes = 0;
});

afterAll(() => {
spyScroll.mockRestore();
});

it('work', () => {
const wrapper = mount(
<Cascader
fieldNames={{ value: 'label' }}
options={[
{
label: 'bamboo',
},
]}
defaultValue={['bamboo']}
open
/>,
);

expect(scrollTimes).toEqual(1);
wrapper.unmount();
});
});
});

1 comment on commit 5905b39

@vercel
Copy link

@vercel vercel bot commented on 5905b39 Jan 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.