@momentum-ui/react-collaboration
Version:
Cisco Momentum UI Framework for React Collaboration Applications
339 lines (310 loc) • 9.01 kB
text/typescript
import {
CSSProperties,
DetailedHTMLProps,
HTMLAttributes,
MutableRefObject,
ReactNode,
} from 'react';
import { ItemSelection, UseItemSelectedProps } from '../../hooks/useItemSelected';
import { AriaLabelRequired } from '../../utils/a11y';
/**
* The key codes used to navigate the tree.
*/
export type TreeNavKeyCodes = 'ArrowUp' | 'ArrowDown' | 'ArrowRight' | 'ArrowLeft' | 'Enter';
/**
* The unique identifier of a tree node.
*/
export type TreeNodeId = string;
/**
* Tree node navigation actions
*/
export type TreeNodeAction =
| {
/** No operation */
action: 'noop';
nodeId: TreeNodeId;
}
| {
/** Open node */
action: 'open';
nodeId: TreeNodeId;
}
| {
/** Close node */
action: 'close';
nodeId: TreeNodeId;
}
| {
/** Move active node */
action: 'move';
nextNodeId: TreeNodeId;
};
/**
* Toggles the state of the tree node.
*/
export type ToggleTreeNode = (id: TreeNodeId, isOpen?: boolean) => void;
/**
* Empty tree type
*/
export type EmptyTree = Record<string, never>;
/**
* The root node of the tree.
*/
export type TreeRoot = TreeNode | EmptyTree;
/**
* A node in the tree.
*/
export interface TreeNode {
/**
* Children of the node. It is an empty array when the node is a leaf.
*/
children: Array<TreeNode>;
/**
* Default open state of the node
* @default true
*/
isOpenByDefault?: boolean;
/**
* The unique identifier of the node.
*/
id: TreeNodeId;
}
/**
* Tree node states in the Tree component.
*/
export interface TreeNodeRecord extends Omit<TreeNode, 'children' | 'isOpenByDefault'> {
/**
* List of child node ids.
*/
children: Array<TreeNodeId>;
/**
* The unique identifier of the parent node.
*
* `undefined` when it is the root node.
*/
parent?: TreeNodeId;
/**
* Index of the node in the parent's children list. Starts from 0.
*/
index: number;
/**
* The level of the node in the tree. The root node is level 0.
*/
level: number;
/**
* Determines if the node is a leaf node.
*/
isLeaf: boolean;
/**
* Determines if the node with the children is open.
*/
isOpen: boolean;
/**
* Node is hidden when any of its ancestors are closed.
*/
isHidden: boolean;
}
/**
* Tree representation with map.
*
* It is used to store the tree states internally.
* @internal
*/
export type TreeIdNodeMap = Map<TreeNodeId, TreeNodeRecord>;
/**
* The props of the Tree component.
* aria-label or aria-labelledby is required, but union type enforcement doesn't work properly with forwardRef
*/
export type Props = Partial<UseItemSelectedProps<TreeNodeId>> &
DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> &
AriaLabelRequired & {
/**
* Custom class for overriding this component's CSS.
*/
className?: string;
/**
* Custom id for overriding this component's CSS.
*/
id?: string;
/**
* Child components of this component.
*/
children?: ReactNode;
/**
* Custom style for overriding this component's CSS.
*/
style?: CSSProperties;
/**
* Determines whether the focus ring around tree nodes should be inset or outset
* It has only visual effect.
*/
shouldNodeFocusBeInset?: boolean;
/**
* The initial tree structure
*
* It is used to build an internal tree for navigation and follow open/close states.
*
* The tree can be updated dynamically via `treeStructure`, but `isOpen` will be migrated from the old tree:
* If the node exists the both old and new tree then the value used from the old tree otherwise
* falls back to the `isOpenByDefault ?? true`
*/
treeStructure: TreeRoot;
/**
* Tree structure can be rendered 2 ways in the DOM:
* 1) Nested: The tree is rendered as a nested list where the structure reflect the semantic structure of the tree
* 2) Flat: The tree is rendered as a single level list where the DOM does not reflect the semantic structure of the tree, and
* we need to provide additional aria attributes to re-build it for the accessibility tree.
* Virtualized trees are usually rendered flat.
* @default true
*/
isRenderedFlat?: boolean;
/**
* Determines if the tree root should be excluded from the tree keyboard navigation.
*
* In many case we want to hide the root of the tree, for example when the tree used for grouping some list items.
*
* Note: it does not change the visibility of the root node.
* @default true
*/
excludeTreeRoot?: boolean;
/**
* The selection mode of the tree nodes.
*
* Note: When user click to select a node and `selectableNodes` is `any` and the node is not a leaf node, the node will be opened/closed.
* WCAG Tree patter sample implementation has the same behavior.
*
* @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ WCAG Tree Pattern}
*/
selectableNodes?: 'leafOnly' | 'any';
/**
* Set of functions to communicate with virtualized tree and sync states.
*/
virtualTreeConnector?: {
/**
* External function to scroll to a node.
* This is used when the tree is rendered in a virtualized tree.
*
* @param id
*/
scrollToNode: (id: TreeNodeId) => void;
/**
* Toggle open/close state of the tree node.
*
* @param id
* @param isOpen
*/
setNodeOpen?: (id: TreeNodeId, isOpen: boolean) => void | Promise<void>;
};
/**
* Called when a node's open / close state is toggled.
*
* @param id
* @param isOpen
*/
onToggleNode?: (id: TreeNodeId, isOpen: boolean) => void;
};
/**
* Props of the virtualized tree hook
* @internal
*/
export interface UseVirtualTreeNavigationProps extends Pick<Props, 'virtualTreeConnector'> {
/**
* Reference to the active node id in the tree.
*
* @remarks This prevent to destroy and re-create MutationObserver every time when active node
* changes. Also, this solves the problem of missed mutations.
*/
activeNodeIdRef: MutableRefObject<TreeNodeId>;
/**
* The reference of the tree DOM element.
*/
treeRef: MutableRefObject<HTMLDivElement>;
}
export interface NodeAriaProps {
/**
* Additional attributes for the tree node.
*
* It is used when the tree is rendered flat and needs to provide additional
* attributes to re-build the semantic structure of the tree.
*/
nodeProps: Partial<HTMLAttributes<HTMLElement>>;
/**
* Additional attributes for the tree node's content.
*/
nodeContentProps: Partial<HTMLAttributes<HTMLElement>>;
/**
* Additional attributes for the node connection group.
*
* It is used when the tree is rendered flat and needs to provide
* a group element to re-build the semantic structure of the tree.
*/
groupProps: Partial<HTMLAttributes<HTMLElement>>;
}
/**
* Empty props object when the node is not found.
*/
type EmptyNodeAriaProps = Record<string, never>;
/**
* The context value of the Tree component.
*/
export interface TreeContextValue
extends Pick<Props, 'shouldNodeFocusBeInset' | 'isRenderedFlat' | 'selectableNodes'> {
/**
* The active node id in the tree.
*/
activeNodeId: TreeNodeId;
/**
* Get semantic attributes of the tree node.
* It returns with empty object when the node is not found.
* @param id unique identifier of the tree node
*/
getNodeAriaProps: (id: TreeNodeId) => NodeAriaProps | EmptyNodeAriaProps;
/**
* Get the details of the tree node.
* @param id unique identifier of the tree node
*/
getNodeDetails: (id: TreeNodeId) => undefined | TreeNodeRecord;
/**
* Set the active node id in the tree.
* @param id unique identifier of the tree node
*/
setActiveNodeId: (id: TreeNodeId) => void;
/**
* Toggle the isOpen state of the tree node.
* @param id unique identifier of the tree node
*/
toggleTreeNode: (id: TreeNodeId) => Promise<void>;
/**
* The item selection state of the tree nodes.
*/
itemSelection: ItemSelection<TreeNodeId>;
/**
* True when the focus is inside the tree component before the tree structure changed,
* otherwise false
*/
isFocusWithin: boolean;
}
/**
* Imperative tree component API
*
* Accessible through the ref prop of the Tree component.
*/
export interface TreeRefObject
extends Pick<TreeContextValue, 'setActiveNodeId' | 'toggleTreeNode'> {
/**
* DOM reference of the tree component.
*/
treeRef: MutableRefObject<HTMLDivElement>;
/**
* Toggle a single item selection
*/
toggleSelection: ItemSelection<TreeNodeId>['toggle'];
/**
* Update selection with new list of selected items
*/
updateSelection: ItemSelection<TreeNodeId>['update'];
/**
* Clear all selection
*/
clearSelection: ItemSelection<TreeNodeId>['clear'];
}