From 5a33f2e3ac927a163f4ca289bc622b386b9a034f Mon Sep 17 00:00:00 2001 From: Bernardo Sunderhus Date: Wed, 4 Jan 2023 18:25:23 +0000 Subject: [PATCH] chore: ensures functionality with Menu --- .../react-tree/etc/react-tree.api.md | 4 +- .../react-components/react-tree/package.json | 1 + .../src/components/TreeItem/TreeItem.types.ts | 8 ++- .../src/components/TreeItem/useTreeItem.tsx | 63 ++++++++++++------- .../components/TreeItem/useTreeItemStyles.ts | 11 +++- .../stories/Tree/TreeActions.stories.tsx | 38 +++++------ 6 files changed, 81 insertions(+), 44 deletions(-) diff --git a/packages/react-components/react-tree/etc/react-tree.api.md b/packages/react-components/react-tree/etc/react-tree.api.md index c11618ec4c6ee5..0aeb610ba2e3cc 100644 --- a/packages/react-components/react-tree/etc/react-tree.api.md +++ b/packages/react-components/react-tree/etc/react-tree.api.md @@ -83,7 +83,9 @@ export type TreeItemSlots = BaseTreeItemSlots & { }; // @public -export type TreeItemState = ComponentState & BaseTreeItemState; +export type TreeItemState = ComponentState & BaseTreeItemState & { + keepActionsOpen: boolean; +}; // @public (undocumented) export type TreeProps = ComponentProps & { diff --git a/packages/react-components/react-tree/package.json b/packages/react-components/react-tree/package.json index 5b21cee332a8d7..03863d04307355 100644 --- a/packages/react-components/react-tree/package.json +++ b/packages/react-components/react-tree/package.json @@ -35,6 +35,7 @@ "@fluentui/react-shared-contexts": "^9.1.4", "@fluentui/react-aria": "^9.3.4", "@fluentui/react-tabster": "^9.3.5", + "@fluentui/react-portal": "^9.0.14", "@fluentui/keyboard-keys": "^9.0.1", "@fluentui/react-theme": "^9.1.5", "@fluentui/react-utilities": "^9.3.1", diff --git a/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts b/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts index 862a726f29e0df..60a8e04a24efd1 100644 --- a/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts +++ b/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts @@ -35,4 +35,10 @@ export type TreeItemProps = ComponentProps> & BaseTreeIte /** * State used in rendering TreeItem */ -export type TreeItemState = ComponentState & BaseTreeItemState; +export type TreeItemState = ComponentState & + BaseTreeItemState & { + /** + * boolean indicating that actions should remain open due to focus on some portal + */ + keepActionsOpen: boolean; + }; diff --git a/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx b/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx index d616351fde176a..03762a669c3aa9 100644 --- a/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx +++ b/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx @@ -5,10 +5,11 @@ import { useFluent_unstable } from '@fluentui/react-shared-contexts'; import { useEventCallback } from '@fluentui/react-utilities'; import { useFocusableGroup } from '@fluentui/react-tabster'; import { expandIconInlineStyles } from './useTreeItemStyles'; -import type { TreeItemProps, TreeItemState } from './TreeItem.types'; import { useBaseTreeItem_unstable } from '../BaseTreeItem/index'; import { Enter } from '@fluentui/keyboard-keys'; import { useMergedRefs } from '@fluentui/react-utilities'; +import { elementContains } from '@fluentui/react-portal'; +import type { TreeItemProps, TreeItemState } from './TreeItem.types'; /** * Create the state required to render TreeItem. @@ -22,14 +23,49 @@ import { useMergedRefs } from '@fluentui/react-utilities'; export const useTreeItem_unstable = (props: TreeItemProps, ref: React.Ref): TreeItemState => { const treeItemState = useBaseTreeItem_unstable(props, ref); const { expandIcon, iconBefore, iconAfter, actions, badges, groupper } = props; - const { dir } = useFluent_unstable(); + const { dir, targetDocument } = useFluent_unstable(); const expandIconRotation = treeItemState.open ? 90 : dir !== 'rtl' ? 0 : 180; const groupperProps = useFocusableGroup(); const actionsRef = React.useRef(null); + const handleClick = useEventCallback((event: React.MouseEvent) => { + // if click event originates from actions, ignore it + if (actionsRef.current && elementContains(actionsRef.current, event.target as Node)) { + return; + } + treeItemState.root.onClick?.(event); + }); + + const handleKeyDown = useEventCallback((event: React.KeyboardEvent) => { + if (event.key === Enter) { + // if Enter keydown event comes from actions, ignore it + if (actionsRef.current && elementContains(actionsRef.current, event.target as Node)) { + return; + } + } + treeItemState.root.onKeyDown?.(event); + }); + + const [keepActionsOpen, setKeepActionsOpen] = React.useState(false); + + // Listens to focusout event on the document to ensure treeitem actions visibility on portal scenarios + // TODO: find a better way to ensure this behavior + React.useEffect(() => { + if (actionsRef.current) { + const handleFocusOut = (event: FocusEvent) => { + setKeepActionsOpen(elementContains(actionsRef.current, event.relatedTarget as Node)); + }; + targetDocument?.addEventListener('focusout', handleFocusOut, { passive: true }); + return () => { + targetDocument?.removeEventListener('focusout', handleFocusOut); + }; + } + }, [targetDocument]); + return { ...treeItemState, + keepActionsOpen, components: { ...treeItemState.components, expandIcon: 'span', @@ -41,26 +77,8 @@ export const useTreeItem_unstable = (props: TreeItemProps, ref: React.Ref { - // if click event originates from actions, ignore it - if (actionsRef.current && actionsRef.current?.contains(event.target as Node)) { - return; - } - // if click event comes from a portal, e.g: MenuItem click, ignore it - if (!event.currentTarget.contains(event.target as Node)) { - return; - } - treeItemState.root.onClick?.(event); - }), - onKeyDown: useEventCallback(event => { - if (event.key === Enter) { - // if Enter keydown event comes from actions, ignore it - if (actionsRef.current && actionsRef.current.contains(event.target as Node)) { - return; - } - } - treeItemState.root.onKeyDown?.(event); - }), + onClick: handleClick, + onKeyDown: handleKeyDown, }, groupper: resolveShorthand(groupper, { required: true, @@ -84,7 +102,6 @@ export const useTreeItem_unstable = (props: TreeItemProps, ref: React.Ref ( - <> -