import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, type ReactNode, type Ref, } from 'react'; import { useVirtualizer } from '@tanstack/react-virtual'; import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element'; import type { TreeNode, DropOp } from '../model/tree-model.types'; import { treeModel } from '../model/tree-model'; import { DocTreeRow } from './doc-tree-row'; import styles from '../styles/tree.module.css'; export type RenderRowProps = { node: TreeNode; level: number; isOpen: boolean; hasChildren: boolean; isSelected: boolean; isDragging: boolean; isReceivingDrop: 'before' | 'after' | 'make-child' | null; rowRef: Ref; // Roving tabindex: exactly one row in the tree carries tabIndex={0} (the // active row); every other row gets tabIndex={-1}. Consumers must spread // this onto the same element they wire rowRef to. tabIndex: 0 | -1; // Treeitem semantics for the row's focusable element. Consumers MUST spread // these onto the same element rowRef points at, so the focused element IS // the treeitem. This makes screen readers announce "treeitem" (not "link") // and replaces the descendant-text accname with the row's label, so action // button labels inside the row don't get concatenated. treeItemProps: { role: 'treeitem'; 'aria-level': number; 'aria-expanded'?: boolean; 'aria-selected'?: true; 'aria-current'?: 'page'; 'aria-label': string; 'data-row-id': string; }; toggleOpen: () => void; }; export type DocTreeProps = { data: TreeNode[]; openIds: ReadonlySet; selectedId?: string; renderRow: (props: RenderRowProps) => ReactNode; indentPerLevel?: number; rowHeight?: number; emptyState?: ReactNode; onMove: (sourceId: string, op: DropOp) => void | Promise; onToggle: (id: string, isOpen: boolean) => void; onSelect?: (id: string) => void; readOnly?: boolean; disableDrag?: (node: TreeNode) => boolean; disableDrop?: (node: TreeNode) => boolean; getDragLabel: (node: TreeNode) => string; uniqueContextId?: symbol; // Accessible name for the tree itself (e.g. "Pages"). Rendered as // aria-label on the