Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent tree from stealing focus from the page (for Issue #142) #143

Merged
merged 4 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions src/TreeView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ interface IUseTreeProps {
onExpand?: (props: ITreeViewOnExpandProps) => void;
multiSelect?: boolean;
propagateSelectUpwards?: boolean;
treeRef?: React.MutableRefObject<HTMLUListElement | null>;
propagateSelect?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onLoadData?: (props: ITreeViewOnLoadDataProps) => Promise<any>;
Expand All @@ -80,6 +81,7 @@ const useTree = ({
multiSelect,
propagateSelect,
propagateSelectUpwards,
treeRef,
}: IUseTreeProps) => {
const treeParentNode = getTreeParent(data);
const [state, dispatch] = useReducer(treeReducer, {
Expand Down Expand Up @@ -417,10 +419,16 @@ const useTree = ({
nodeRefs?.current != null &&
leafRefs?.current != null
) {
const tabbableNode = nodeRefs.current[tabbableId];
const leafNode = leafRefs.current[lastInteractedWith];
scrollToRef(leafNode);
focusRef(tabbableNode);
const isTreeActive = (treeRef?.current == null) ||
(document.activeElement && treeRef.current.contains(document.activeElement));
if (isTreeActive) {
// Only scroll and focus on the tree when it is the active element on the page.
// This prevents controlled updates from scrolling to the tree and giving it focus.
const tabbableNode = nodeRefs.current[tabbableId];
const leafNode = leafRefs.current[lastInteractedWith];
scrollToRef(leafNode);
focusRef(tabbableNode);
}
}
}, [tabbableId, nodeRefs, leafRefs, lastInteractedWith]);

Expand Down Expand Up @@ -543,6 +551,10 @@ const TreeView = React.forwardRef<HTMLUListElement, ITreeViewProps>(
validateTreeViewData(data);
const nodeRefs = useRef({});
const leafRefs = useRef({});
let innerRef = useRef<HTMLUListElement | null>(null);
if (ref != null) {
innerRef = ref as React.MutableRefObject<HTMLUListElement>;
}
const [state, dispatch] = useTree({
data,
controlledSelectedIds: selectedIds,
Expand All @@ -560,14 +572,10 @@ const TreeView = React.forwardRef<HTMLUListElement, ITreeViewProps>(
multiSelect,
propagateSelect,
propagateSelectUpwards,
treeRef: innerRef,
});
propagateSelect = propagateSelect && multiSelect;

let innerRef = useRef<HTMLUListElement | null>(null);
if (ref != null) {
innerRef = ref as React.MutableRefObject<HTMLUListElement>;
}

return (
<ul
className={cx(baseClassNames.root, className)}
Expand Down
18 changes: 18 additions & 0 deletions src/__tests__/ControlledTree.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -753,4 +753,22 @@ describe("Data with ids", () => {
expect(newNodes[4]).toHaveAttribute("aria-checked", "false");
expect(newNodes[5]).toHaveAttribute("aria-checked", "false");
});

test("SelectedIds should not steal focus if another control has it", () => {
let selectedIds = [42];
const renderContent = (<div>
<input type="text" id="editor"/>
<MultiSelectCheckboxControlled
selectedIds={selectedIds}
data={dataWithIds}
/>
</div>);
const { rerender } = render(renderContent);

const editorElement = document?.getElementById("editor");
editorElement?.focus();
selectedIds = [4];
rerender(renderContent);
expect(document.activeElement).toEqual(editorElement);
});
});