Skip to content

Commit 0411b7a

Browse files
committed
refactor(a11y): synchronize activeKey with search state
1 parent e2112e9 commit 0411b7a

File tree

4 files changed

+101
-52
lines changed

4 files changed

+101
-52
lines changed

src/OptionList.tsx

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -102,42 +102,21 @@ const OptionList: React.ForwardRefRenderFunction<ReviseRefOptionListProps> = (_,
102102
// eslint-disable-next-line react-hooks/exhaustive-deps
103103
}, [open]);
104104

105-
// ========================== Search ==========================
106-
const lowerSearchValue = String(searchValue).toLowerCase();
107-
const filterTreeNode = (treeNode: EventDataNode<any>) => {
108-
if (!lowerSearchValue) {
109-
return false;
110-
}
111-
return String(treeNode[treeNodeFilterProp]).toLowerCase().includes(lowerSearchValue);
112-
};
113-
114-
// =========================== Keys ===========================
115-
const [expandedKeys, setExpandedKeys] = React.useState<Key[]>(treeDefaultExpandedKeys);
116-
const [searchExpandedKeys, setSearchExpandedKeys] = React.useState<Key[]>(null);
117-
118-
const mergedExpandedKeys = React.useMemo(() => {
119-
if (treeExpandedKeys) {
120-
return [...treeExpandedKeys];
121-
}
122-
return searchValue ? searchExpandedKeys : expandedKeys;
123-
}, [expandedKeys, searchExpandedKeys, treeExpandedKeys, searchValue]);
124-
105+
// =========================== Search Effect ===========================
125106
React.useEffect(() => {
126107
if (searchValue) {
127108
setSearchExpandedKeys(getAllKeys(treeData, fieldNames));
109+
110+
const firstMatchNode = getFirstMatchNode(memoTreeData);
111+
if (firstMatchNode) {
112+
setActiveKey(firstMatchNode[fieldNames.value]);
113+
} else {
114+
setActiveKey(null);
115+
}
128116
}
129117
// eslint-disable-next-line react-hooks/exhaustive-deps
130118
}, [searchValue]);
131119

132-
const onInternalExpand = (keys: Key[]) => {
133-
setExpandedKeys(keys);
134-
setSearchExpandedKeys(keys);
135-
136-
if (onTreeExpand) {
137-
onTreeExpand(keys);
138-
}
139-
};
140-
141120
// ========================== Events ==========================
142121
const onListMouseDown: React.MouseEventHandler<HTMLDivElement> = event => {
143122
event.preventDefault();
@@ -159,7 +138,7 @@ const OptionList: React.ForwardRefRenderFunction<ReviseRefOptionListProps> = (_,
159138
}
160139
};
161140

162-
const getFirstMatchNode = (nodes: EventDataNode<any>[]): EventDataNode<any> | null => {
141+
const getFirstMatchNode = (nodes: EventDataNode<any>): EventDataNode<any> | null => {
163142
for (const node of nodes) {
164143
if (filterTreeNode(node) && !node.disabled) {
165144
return node;
@@ -174,6 +153,35 @@ const OptionList: React.ForwardRefRenderFunction<ReviseRefOptionListProps> = (_,
174153
return null;
175154
};
176155

156+
// ========================== Search ==========================
157+
const lowerSearchValue = String(searchValue).toLowerCase();
158+
const filterTreeNode = (treeNode: EventDataNode<any>) => {
159+
if (!lowerSearchValue) {
160+
return false;
161+
}
162+
return String(treeNode[treeNodeFilterProp]).toLowerCase().includes(lowerSearchValue);
163+
};
164+
165+
// =========================== Keys ===========================
166+
const [expandedKeys, setExpandedKeys] = React.useState<Key[]>(treeDefaultExpandedKeys);
167+
const [searchExpandedKeys, setSearchExpandedKeys] = React.useState<Key[]>(null);
168+
169+
const mergedExpandedKeys = React.useMemo(() => {
170+
if (treeExpandedKeys) {
171+
return [...treeExpandedKeys];
172+
}
173+
return searchValue ? searchExpandedKeys : expandedKeys;
174+
}, [expandedKeys, searchExpandedKeys, treeExpandedKeys, searchValue]);
175+
176+
const onInternalExpand = (keys: Key[]) => {
177+
setExpandedKeys(keys);
178+
setSearchExpandedKeys(keys);
179+
180+
if (onTreeExpand) {
181+
onTreeExpand(keys);
182+
}
183+
};
184+
177185
// ========================= Keyboard =========================
178186
React.useImperativeHandle(ref, () => ({
179187
scrollTo: treeRef.current?.scrollTo,
@@ -190,18 +198,7 @@ const OptionList: React.ForwardRefRenderFunction<ReviseRefOptionListProps> = (_,
190198

191199
// >>> Select item
192200
case KeyCode.ENTER: {
193-
if (searchValue) {
194-
const firstMatchNode = getFirstMatchNode(memoTreeData);
195-
196-
if (firstMatchNode) {
197-
if (firstMatchNode.selectable !== false) {
198-
onInternalSelect(null, {
199-
node: { key: firstMatchNode[fieldNames.value] },
200-
selected: !checkedKeys.includes(firstMatchNode[fieldNames.value]),
201-
});
202-
}
203-
}
204-
} else if (activeEntity) {
201+
if (activeEntity) {
205202
const { selectable, value } = activeEntity?.node || {};
206203
if (selectable !== false) {
207204
onInternalSelect(null, {

tests/Select.SearchInput.spec.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,5 +292,27 @@ describe('TreeSelect.SearchInput', () => {
292292
wrapper.find('input').first().simulate('keyDown', { which: KeyCode.ENTER });
293293
expect(onSelect).not.toHaveBeenCalled();
294294
});
295+
296+
it('should activate first matched node when searching', () => {
297+
const wrapper = mount(
298+
<TreeSelect
299+
showSearch
300+
open
301+
treeData={[
302+
{ value: '1', label: '1' },
303+
{ value: '2', label: '2', disabled: true },
304+
{ value: '3', label: '3' },
305+
]}
306+
/>,
307+
);
308+
309+
// When searching, first matched non-disabled node should be activated
310+
wrapper.search('1');
311+
expect(wrapper.find('.rc-tree-select-tree-treenode-active').text()).toBe('1');
312+
313+
// Should skip disabled nodes
314+
wrapper.search('2');
315+
expect(wrapper.find('.rc-tree-select-tree-treenode-active').length).toBe(0);
316+
});
295317
});
296318
});

tests/__snapshots__/Select.checkable.spec.tsx.snap

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -620,8 +620,14 @@ exports[`TreeSelect.checkable uncheck remove by tree check 1`] = `
620620
>
621621
<div>
622622
<div>
623+
<span
624+
aria-live="assertive"
625+
style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
626+
>
627+
0
628+
</span>
623629
<div
624-
class="rc-tree-select-tree"
630+
class="rc-tree-select-tree rc-tree-select-tree-active-focused"
625631
role="tree"
626632
>
627633
<div>
@@ -660,7 +666,7 @@ exports[`TreeSelect.checkable uncheck remove by tree check 1`] = `
660666
>
661667
<div
662668
aria-grabbed="false"
663-
class="rc-tree-select-tree-treenode rc-tree-select-tree-treenode-switcher-open rc-tree-select-tree-treenode-checkbox-checked rc-tree-select-tree-treenode-leaf-last filter-node"
669+
class="rc-tree-select-tree-treenode rc-tree-select-tree-treenode-switcher-open rc-tree-select-tree-treenode-checkbox-checked rc-tree-select-tree-treenode-active rc-tree-select-tree-treenode-leaf-last filter-node"
664670
draggable="false"
665671
>
666672
<span
@@ -821,8 +827,14 @@ exports[`TreeSelect.checkable uncheck remove by tree check 2`] = `
821827
>
822828
<div>
823829
<div>
830+
<span
831+
aria-live="assertive"
832+
style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
833+
>
834+
0
835+
</span>
824836
<div
825-
class="rc-tree-select-tree"
837+
class="rc-tree-select-tree rc-tree-select-tree-active-focused"
826838
role="tree"
827839
>
828840
<div>
@@ -861,7 +873,7 @@ exports[`TreeSelect.checkable uncheck remove by tree check 2`] = `
861873
>
862874
<div
863875
aria-grabbed="false"
864-
class="rc-tree-select-tree-treenode rc-tree-select-tree-treenode-switcher-open rc-tree-select-tree-treenode-leaf-last filter-node"
876+
class="rc-tree-select-tree-treenode rc-tree-select-tree-treenode-switcher-open rc-tree-select-tree-treenode-active rc-tree-select-tree-treenode-leaf-last filter-node"
865877
draggable="false"
866878
>
867879
<span

tests/__snapshots__/Select.spec.tsx.snap

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -740,8 +740,14 @@ exports[`TreeSelect.basic search nodes check tree changed by filter 1`] = `
740740
>
741741
<div>
742742
<div>
743+
<span
744+
aria-live="assertive"
745+
style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
746+
>
747+
a
748+
</span>
743749
<div
744-
class="rc-tree-select-tree"
750+
class="rc-tree-select-tree rc-tree-select-tree-active-focused"
745751
role="tree"
746752
>
747753
<div>
@@ -780,7 +786,7 @@ exports[`TreeSelect.basic search nodes check tree changed by filter 1`] = `
780786
>
781787
<div
782788
aria-grabbed="false"
783-
class="rc-tree-select-tree-treenode rc-tree-select-tree-treenode-switcher-open rc-tree-select-tree-treenode-leaf-last filter-node"
789+
class="rc-tree-select-tree-treenode rc-tree-select-tree-treenode-switcher-open rc-tree-select-tree-treenode-active rc-tree-select-tree-treenode-leaf-last filter-node"
784790
draggable="false"
785791
>
786792
<span
@@ -856,8 +862,14 @@ exports[`TreeSelect.basic search nodes check tree changed by filter 2`] = `
856862
>
857863
<div>
858864
<div>
865+
<span
866+
aria-live="assertive"
867+
style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
868+
>
869+
a
870+
</span>
859871
<div
860-
class="rc-tree-select-tree"
872+
class="rc-tree-select-tree rc-tree-select-tree-active-focused"
861873
role="tree"
862874
>
863875
<div>
@@ -896,7 +908,7 @@ exports[`TreeSelect.basic search nodes check tree changed by filter 2`] = `
896908
>
897909
<div
898910
aria-grabbed="false"
899-
class="rc-tree-select-tree-treenode rc-tree-select-tree-treenode-switcher-open"
911+
class="rc-tree-select-tree-treenode rc-tree-select-tree-treenode-switcher-open rc-tree-select-tree-treenode-active"
900912
draggable="false"
901913
>
902914
<span
@@ -998,8 +1010,14 @@ exports[`TreeSelect.basic search nodes filter node but not remove then 1`] = `
9981010
>
9991011
<div>
10001012
<div>
1013+
<span
1014+
aria-live="assertive"
1015+
style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
1016+
>
1017+
a
1018+
</span>
10011019
<div
1002-
class="rc-tree-select-tree"
1020+
class="rc-tree-select-tree rc-tree-select-tree-active-focused"
10031021
role="tree"
10041022
>
10051023
<div>
@@ -1038,7 +1056,7 @@ exports[`TreeSelect.basic search nodes filter node but not remove then 1`] = `
10381056
>
10391057
<div
10401058
aria-grabbed="false"
1041-
class="rc-tree-select-tree-treenode rc-tree-select-tree-treenode-switcher-close filter-node"
1059+
class="rc-tree-select-tree-treenode rc-tree-select-tree-treenode-switcher-close rc-tree-select-tree-treenode-active filter-node"
10421060
draggable="false"
10431061
>
10441062
<span

0 commit comments

Comments
 (0)