setHovered(true)}
+ onMouseLeave={() => setHovered(false)}
+ aria-level={onInclude ? node.depth + 1 : undefined}
+ aria-expanded={onInclude ? expanded : undefined}
+ style={{
+ ...styles.row,
+ borderColor: focused ? theme.colors.brand : "transparent",
+ borderRadius: radius.sm,
+ paddingLeft: spacing[2] + node.depth * spacing[4],
+ paddingRight: spacing[1],
+ gap: spacing[1],
+ }}
+ >
+
{
+ setFocused(true);
+ onReveal();
+ }}
+ onBlur={() => setFocused(false)}
+ onKeyDown={onKeyDown}
+ style={{
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ gap: spacing[1.5],
+ flexGrow: 1,
+ minWidth: 0,
+ height: "100%",
+ }}
+ >
+
+
+
+
+ {node.name}
+
+
+
+ {node.fileIds.length}
+
+
+ {onInclude ? (
+
+ {
+ setFocused(true);
+ onReveal();
+ }}
+ onBlur={() => setFocused(false)}
+ onKeyDown={(event) => {
+ if (event.key !== "space" && event.key !== "enter")
+ onKeyDown(event);
+ }}
+ />
+
+ ) : null}
+
+ );
+}
+
/** Shared file/revision list with native scrolling and roving focus; remount when its data owner changes. */
export function RepositoryList({
entries,
@@ -315,6 +496,7 @@ export function RepositoryList({
onPreviewIntent,
renderEntry,
itemHeight,
+ fileTree,
}: {
entries: readonly WorkspaceEntry[];
label: string;
@@ -329,7 +511,11 @@ export function RepositoryList({
onChange: (id: string, included: boolean) => void;
};
multiselect?: boolean;
- onSelect: (id: string, mode?: "toggle" | "range" | "context") => void;
+ onSelect: (
+ id: string,
+ mode?: "toggle" | "range" | "context",
+ visibleIds?: readonly string[]
+ ) => void;
onScroll: (y: number) => void;
onEndReached?: () => void;
fileActions?: FileMenuActions;
@@ -338,10 +524,58 @@ export function RepositoryList({
/** Custom native row contents retain this list's selection, focus and virtualization. */
renderEntry?: (entry: WorkspaceEntry) => ReactNode;
itemHeight?: number;
+ /** Only file panes opt in; revision lists retain their chronological ordering. */
+ fileTree?: {
+ scope: string;
+ owner?: string;
+ allEntries?: readonly WorkspaceEntry[];
+ filtering?: boolean;
+ onInclude?: (ids: readonly string[], included: boolean) => void;
+ };
}) {
const theme = useAppTheme();
const { styles } = themedStyles.get(theme);
+ const { layout, collapsed, toggle, reveal } = useRepositoryFileView(
+ fileTree?.scope
+ );
+
+ const treeEnabled = !!fileTree && layout === "tree";
+ const treeAvailable = !!fileTree;
+ const sourceEntries = fileTree?.allEntries ?? entries;
+
+ const treeNodes = useMemo(
+ () => (treeEnabled ? RepositoryFileTree.build(sourceEntries) : []),
+ [sourceEntries, treeEnabled]
+ );
+
+ const filtering = !!fileTree?.filtering;
+
+ const matches = useMemo(
+ () => (filtering ? new Set(entries.map((entry) => entry.id)) : undefined),
+ [entries, filtering]
+ );
+
+ const displayRows = useMemo