diff --git a/change/@fluentui-react-tree-409b60b2-2543-40ee-9e92-f53b02cecb5d.json b/change/@fluentui-react-tree-409b60b2-2543-40ee-9e92-f53b02cecb5d.json
new file mode 100644
index 0000000000000..0899b9b325e16
--- /dev/null
+++ b/change/@fluentui-react-tree-409b60b2-2543-40ee-9e92-f53b02cecb5d.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "bugfix: fix parent navigation after independency from id",
+ "packageName": "@fluentui/react-tree",
+ "email": "bernardo.sunderhus@gmail.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/react-components/react-tree/src/components/Tree/Tree.cy.tsx b/packages/react-components/react-tree/src/components/Tree/Tree.cy.tsx
index 5fbe860fe3aa4..b5bf3164c6a14 100644
--- a/packages/react-components/react-tree/src/components/Tree/Tree.cy.tsx
+++ b/packages/react-components/react-tree/src/components/Tree/Tree.cy.tsx
@@ -18,40 +18,42 @@ const mount = (element: JSX.Element) => {
mountBase({element});
};
+const treeItems = (
+ <>
+
+ level 1, item 1
+
+
+ level 2, item 1
+
+
+ level 2, item 2
+
+
+ level 2, item 3
+
+
+
+
+ level 1, item 2
+
+
+ level 2, item 1
+
+
+ level 3, item 1
+
+
+
+
+
+ >
+);
+
const NestedTree: React.FC = props => {
return (
- {props.children ?? (
- <>
-
- level 1, item 1
-
-
- level 2, item 1
-
-
- level 2, item 2
-
-
- level 2, item 3
-
-
-
-
- level 1, item 2
-
-
- level 2, item 1
-
-
- level 3, item 1
-
-
-
-
-
- >
- )}
+ {props.children ?? treeItems}
);
};
@@ -59,41 +61,7 @@ NestedTree.displayName = 'NestedTree';
const FlatTree: React.FC = (props: TreeProps) => {
const flatTree = useFlatTree_unstable(
- flattenTreeFromElement(
- props.children ? (
- <>{props.children}>
- ) : (
- <>
-
- level 1, item 1
-
-
- level 2, item 1
-
-
- level 2, item 2
-
-
- level 2, item 3
-
-
-
-
- level 1, item 2
-
-
- level 2, item 1
-
-
- level 3, item 1
-
-
-
-
-
- >
- ),
- ),
+ flattenTreeFromElement(props.children ? <>{props.children}> : treeItems),
props,
);
return (
@@ -110,30 +78,30 @@ for (const TreeTest of [NestedTree, FlatTree]) {
describe(TreeTest.displayName!, () => {
it('should have all but first level items hidden', () => {
mount();
- cy.get('#item1__item1').should('not.exist');
- cy.get('#item1__item2').should('not.exist');
- cy.get('#item1__item3').should('not.exist');
- cy.get('#item2__item1').should('not.exist');
- cy.get('#item2__item1__item1').should('not.exist');
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
+ cy.get('[data-testid="item1__item2"]').should('not.exist');
+ cy.get('[data-testid="item1__item3"]').should('not.exist');
+ cy.get('[data-testid="item2__item1"]').should('not.exist');
+ cy.get('[data-testid="item2__item1__item1"]').should('not.exist');
});
it('should have all items visible', () => {
mount();
- cy.get('#item1__item1').should('exist');
- cy.get('#item1__item2').should('exist');
- cy.get('#item1__item3').should('exist');
- cy.get('#item2__item1').should('exist');
- cy.get('#item2__item1__item1').should('exist');
+ cy.get('[data-testid="item1__item1"]').should('exist');
+ cy.get('[data-testid="item1__item2"]').should('exist');
+ cy.get('[data-testid="item1__item3"]').should('exist');
+ cy.get('[data-testid="item2__item1"]').should('exist');
+ cy.get('[data-testid="item2__item1__item1"]').should('exist');
});
describe('Mouse interactions', () => {
it('should expand/collapse item on layout click', () => {
mount();
- cy.get('#item1__item1').should('not.exist');
- cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realClick();
- cy.get('#item1__item1').should('exist');
- cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realClick();
- cy.get('#item1__item1').should('not.exist');
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
+ cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realClick();
+ cy.get('[data-testid="item1__item1"]').should('exist');
+ cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realClick();
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
});
it('should expand/collapse item on expandIcon click only', () => {
mount(
@@ -145,14 +113,14 @@ for (const TreeTest of [NestedTree, FlatTree]) {
}}
/>,
);
- cy.get('#item1__item1').should('not.exist');
- cy.get(`#item1 .${treeItemClassNames.expandIcon}`).realClick();
- cy.get('#item1__item1').should('exist');
- cy.get(`#item1 .${treeItemClassNames.expandIcon}`).realClick();
- cy.get('#item1__item1').should('not.exist');
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
+ cy.get(`[data-testid="item1"] .${treeItemClassNames.expandIcon}`).realClick();
+ cy.get('[data-testid="item1__item1"]').should('exist');
+ cy.get(`[data-testid="item1"] .${treeItemClassNames.expandIcon}`).realClick();
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
- cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realClick();
- cy.get('#item1__item1').should('not.exist');
+ cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realClick();
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
});
it('should not expand/collapse item on actions click', () => {
mount(
@@ -163,46 +131,47 @@ for (const TreeTest of [NestedTree, FlatTree]) {
>
}
- id="item1"
+ value="item1"
+ data-testid="item1"
>
level 1, item 1
-
+
level 2, item 1
,
);
- cy.get('#item1__item1').should('not.exist');
- cy.get('#item1').focus();
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
+ cy.get('[data-testid="item1"]').focus();
cy.get(`#action`).realClick();
- cy.get('#item1__item1').should('not.exist');
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
});
});
describe('Keyboard interactions', () => {
it('should expand/collapse item on Enter key', () => {
mount();
- cy.get('#item1').focus();
- cy.get('#item1__item1').should('not.exist');
- cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realPress('{enter}');
- cy.get('#item1__item1').should('exist');
+ cy.get('[data-testid="item1"]').focus();
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
+ cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realPress('{enter}');
+ cy.get('[data-testid="item1__item1"]').should('exist');
});
it('should expand item on Right key', () => {
mount();
- cy.get('#item1').focus();
- cy.get('#item1__item1').should('not.exist');
- cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realPress('{rightarrow}');
- cy.get('#item1__item1').should('exist');
+ cy.get('[data-testid="item1"]').focus();
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
+ cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realPress('{rightarrow}');
+ cy.get('[data-testid="item1__item1"]').should('exist');
});
it('should collapse item on Left key', () => {
mount();
- cy.get('#item1').focus();
- cy.get('#item1__item1').should('not.exist');
- cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realPress('{rightarrow}');
- cy.get('#item1__item1').should('exist');
- cy.get(`#item1 .${treeItemLayoutClassNames.root}`).realPress('{leftarrow}');
- cy.get('#item1__item1').should('not.exist');
+ cy.get('[data-testid="item1"]').focus();
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
+ cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realPress('{rightarrow}');
+ cy.get('[data-testid="item1__item1"]').should('exist');
+ cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realPress('{leftarrow}');
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
});
it('should focus on actions when pressing tab key', () => {
mount(
@@ -213,11 +182,12 @@ for (const TreeTest of [NestedTree, FlatTree]) {
>
}
- id="item1"
+ value="item1"
+ data-testid="item1"
>
level 1, item 1
-
+
level 2, item 1
@@ -226,7 +196,7 @@ for (const TreeTest of [NestedTree, FlatTree]) {
);
cy.focused().should('not.exist');
cy.document().realPress('Tab');
- cy.get('#item1').should('be.focused');
+ cy.get('[data-testid="item1"]').should('be.focused');
cy.document().realPress('Tab');
cy.get('#action').should('be.focused');
});
@@ -239,11 +209,12 @@ for (const TreeTest of [NestedTree, FlatTree]) {
>
}
- id="item1"
+ value="item1"
+ data-testid="item1"
>
level 1, item 1
-
+
level 2, item 1
@@ -252,53 +223,53 @@ for (const TreeTest of [NestedTree, FlatTree]) {
);
cy.focused().should('not.exist');
cy.document().realPress('Tab');
- cy.get('#item1').should('be.focused');
+ cy.get('[data-testid="item1"]').should('be.focused');
cy.document().realPress('Tab');
cy.get('#action').should('be.focused').realPress('{enter}');
- cy.get('#item1__item1').should('not.exist');
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
cy.get('#action').should('be.focused').realPress('Space');
- cy.get('#item1__item1').should('not.exist');
+ cy.get('[data-testid="item1__item1"]').should('not.exist');
});
it('should focus on first item when pressing tab key', () => {
mount();
cy.focused().should('not.exist');
cy.document().realPress('Tab');
- cy.get('#item1').should('be.focused');
+ cy.get('[data-testid="item1"]').should('be.focused');
});
it('should focus out of tree when pressing tab key inside tree.', () => {
mount();
cy.focused().should('not.exist');
cy.document().realPress('Tab');
- cy.get('#item1').should('be.focused');
+ cy.get('[data-testid="item1"]').should('be.focused');
cy.focused().realPress('Tab');
cy.focused().should('not.exist');
});
describe('Navigation', () => {
it('should move with Up/Down keys', () => {
mount();
- cy.get('#item1').focus().realPress('{downarrow}');
- cy.get('#item2').should('be.focused');
+ cy.get('[data-testid="item1"]').focus().realPress('{downarrow}');
+ cy.get('[data-testid="item2"]').should('be.focused');
cy.focused().realPress('Tab').should('not.exist');
});
it('should move with Left/Right keys', () => {
mount();
- cy.get('#item1').focus().realPress('{downarrow}');
- cy.get('#item2').should('be.focused').realPress('{rightarrow}');
- cy.get('#item2__item1').should('be.focused').realPress('{rightarrow}');
- cy.get('#item2__item1__item1').should('be.focused').realPress('{leftarrow}');
- cy.get('#item2__item1').should('be.focused').realPress('{leftarrow}').realPress('{leftarrow}');
- cy.get('#item2').should('be.focused');
+ cy.get('[data-testid="item1"]').focus().realPress('{downarrow}');
+ cy.get('[data-testid="item2"]').should('be.focused').realPress('{rightarrow}');
+ cy.get('[data-testid="item2__item1"]').should('be.focused').realPress('{rightarrow}');
+ cy.get('[data-testid="item2__item1__item1"]').should('be.focused').realPress('{leftarrow}');
+ cy.get('[data-testid="item2__item1"]').should('be.focused').realPress('{leftarrow}').realPress('{leftarrow}');
+ cy.get('[data-testid="item2"]').should('be.focused');
});
it('should move to last item with End key', () => {
mount();
- cy.get('#item1').focus().realPress('{end}');
- cy.get('#item2__item1__item1').should('be.focused');
+ cy.get('[data-testid="item1"]').focus().realPress('{end}');
+ cy.get('[data-testid="item2__item1__item1"]').should('be.focused');
});
it('should move to first item with Home key', () => {
mount();
- cy.get('#item1').focus().realPress('{end}');
- cy.get('#item2__item1__item1').should('be.focused').realPress('{home}');
- cy.get('#item1').should('be.focused');
+ cy.get('[data-testid="item1"]').focus().realPress('{end}');
+ cy.get('[data-testid="item2__item1__item1"]').should('be.focused').realPress('{home}');
+ cy.get('[data-testid="item1"]').should('be.focused');
});
});
});
diff --git a/packages/react-components/react-tree/src/hooks/useFlatTree.ts b/packages/react-components/react-tree/src/hooks/useFlatTree.ts
index 6c125c1fd570e..b3def6b57e043 100644
--- a/packages/react-components/react-tree/src/hooks/useFlatTree.ts
+++ b/packages/react-components/react-tree/src/hooks/useFlatTree.ts
@@ -31,6 +31,7 @@ export type MutableFlatTreeItem = {
index: number;
value: Value;
level: number;
+ ref: React.RefObject;
getTreeItemProps(): Required<
Pick, 'value' | 'aria-setsize' | 'aria-level' | 'aria-posinset' | 'leaf'>
> &
diff --git a/packages/react-components/react-tree/src/hooks/useFlatTreeNavigation.ts b/packages/react-components/react-tree/src/hooks/useFlatTreeNavigation.ts
index 79eb5773f1d5c..b1b9e6d55cc7c 100644
--- a/packages/react-components/react-tree/src/hooks/useFlatTreeNavigation.ts
+++ b/packages/react-components/react-tree/src/hooks/useFlatTreeNavigation.ts
@@ -25,7 +25,7 @@ export function useFlatTreeNavigation(flatTreeItems: FlatTreeIte
treeItemWalker.currentElement = data.target;
return nextTypeAheadElement(treeItemWalker, data.event.key);
case treeDataTypes.arrowLeft:
- return parentElement(flatTreeItems, data.value, targetDocument);
+ return parentElement(flatTreeItems, data.value);
case treeDataTypes.arrowRight:
treeItemWalker.currentElement = data.target;
return firstChild(data.target, treeItemWalker);
@@ -66,13 +66,11 @@ function firstChild(target: HTMLElement, treeWalker: HTMLElementWalker): HTMLEle
return null;
}
-function parentElement(flatTreeItems: FlatTreeItems, value: Value, document: Document) {
+function parentElement(flatTreeItems: FlatTreeItems, value: Value) {
const flatTreeItem = flatTreeItems.get(value);
- if (flatTreeItem && flatTreeItem.parentValue) {
- const parentId = flatTreeItems.get(flatTreeItem.parentValue)?.getTreeItemProps().id;
- if (parentId) {
- return document.getElementById(parentId);
- }
+ if (flatTreeItem?.parentValue) {
+ const parentItem = flatTreeItems.get(flatTreeItem.parentValue);
+ return parentItem?.ref.current ?? null;
}
return null;
}
diff --git a/packages/react-components/react-tree/src/utils/createFlatTreeItems.ts b/packages/react-components/react-tree/src/utils/createFlatTreeItems.ts
index b04f9a6b635d0..1cb3d00eb1fe4 100644
--- a/packages/react-components/react-tree/src/utils/createFlatTreeItems.ts
+++ b/packages/react-components/react-tree/src/utils/createFlatTreeItems.ts
@@ -1,5 +1,6 @@
import type { ImmutableSet } from './ImmutableSet';
import type { FlatTreeItem, FlatTreeItemProps, MutableFlatTreeItem } from '../hooks/useFlatTree';
+import * as React from 'react';
/**
* @internal
@@ -40,8 +41,9 @@ export function createFlatTreeItems(
const isLeaf = nextItemProps?.parentValue !== treeItemProps.value;
const currentLevel = (currentParent.level ?? 0) + 1;
const currentChildrenSize = ++currentParent.childrenSize;
+ const ref = React.createRef();
- const flatTreeItem: FlatTreeItem = {
+ const flatTreeItem: MutableFlatTreeItem = {
value: treeItemProps.value,
getTreeItemProps: () => ({
...treeItemProps,
@@ -49,7 +51,10 @@ export function createFlatTreeItems(
'aria-posinset': currentChildrenSize,
'aria-setsize': currentParent.childrenSize,
leaf: isLeaf,
+ // a reference to every parent element is necessary to ensure navigation
+ ref: flatTreeItem.childrenSize > 0 ? ref : undefined,
}),
+ ref,
level: currentLevel,
parentValue,
childrenSize: 0,
@@ -72,6 +77,7 @@ export const flatTreeRootId = '__fuiFlatTreeRoot' as unknown;
function createFlatTreeRootItem(): FlatTreeItem {
return {
+ ref: { current: null },
value: flatTreeRootId as Value,
getTreeItemProps: () => {
if (process.env.NODE_ENV !== 'production') {