{
- treeRef.current = element;
- setForwardedRef(ref, element);
- }}
- role="tree"
- tabIndex={0}
- aria-activedescendant={
- adapter.focusedId ? `${treeDomId}-${adapter.focusedId}` : undefined
- }
- aria-multiselectable={multiSelect ? true : undefined}
- className={cx(
- "ui-tree",
- variant === "explorer" && "ui-tree--explorer",
- adapter.hasDomFocus && "ui-tree--focused",
- className,
- )}
- onFocus={(event) => {
- onFocus?.(event);
- if (
- !event.defaultPrevented &&
- ownsTarget(event.currentTarget, event.target)
- ) {
- adapter.onFocusIn(event.target);
- }
- }}
- onBlur={(event) => {
- onBlur?.(event);
- if (
- !event.defaultPrevented &&
- !ownsTarget(event.currentTarget, event.relatedTarget)
- ) {
- adapter.onBlurOut();
+
+ {
+ treeRef.current = element;
+ setForwardedRef(ref, element);
+ }}
+ role="tree"
+ tabIndex={0}
+ aria-activedescendant={
+ adapter.focusedId ? `${treeDomId}-${adapter.focusedId}` : undefined
}
- }}
- onClick={adapter.onClick}
- onKeyDown={adapter.onKeyDown}
- >
- {stickyScroll ? (
-
- ) : null}
- {adapter.model.visibleRows.map((row) => (
-
- ))}
-
+ aria-multiselectable={multiSelect ? true : undefined}
+ className={cx(
+ "ui-tree",
+ variant === "explorer" && "ui-tree--explorer",
+ adapter.hasDomFocus && "ui-tree--focused",
+ className,
+ )}
+ onFocus={(event) => {
+ onFocus?.(event);
+ if (
+ !event.defaultPrevented &&
+ ownsTarget(event.currentTarget, event.target)
+ ) {
+ adapter.onFocusIn(event.target);
+ }
+ }}
+ onBlur={(event) => {
+ onBlur?.(event);
+ if (
+ !event.defaultPrevented &&
+ !ownsTarget(event.currentTarget, event.relatedTarget)
+ ) {
+ adapter.onBlurOut();
+ }
+ }}
+ onClick={adapter.onClick}
+ onKeyDown={adapter.onKeyDown}
+ >
+
+ {stickyScroll ? (
+
+ ) : null}
+ {adapter.model.visibleRows.map((row) => (
+
+ ))}
+
+
+
);
}
diff --git a/packages/ui/src/components/Tree/TreeHover.tsx b/packages/ui/src/components/Tree/TreeHover.tsx
new file mode 100644
index 0000000000..61502fe56e
--- /dev/null
+++ b/packages/ui/src/components/Tree/TreeHover.tsx
@@ -0,0 +1,156 @@
+import {
+ useEffect,
+ useImperativeHandle,
+ useRef,
+ useState,
+ type ReactNode,
+ type RefObject,
+} from "react";
+
+import {
+ HoverDelegateScope,
+ Tooltip,
+ useTooltipDelay,
+ type HoverDelegate,
+ type HoverTarget,
+} from "../Tooltip/Tooltip";
+
+const GRACE_MS = 100;
+
+/** Native reopens with no delay this soon after hiding, and only for a dense
+ cluster of targets such as an action bar. */
+const INSTANT_MS = 200;
+const DENSE_CLUSTER = ".ui-tree-item__action";
+
+/** setupCustomHover offsets a cursor-placed hover by this much. */
+const CURSOR_OFFSET_PX = 10;
+
+const ROW = ".ui-tree-item";
+
+export type TreeHoverControl = RefObject