fixed ux for expand object scrolling
This commit is contained in:
parent
79557e51ed
commit
dca587a2df
2 changed files with 39 additions and 7 deletions
|
|
@ -452,6 +452,7 @@ export function FormGeneratorTree<T = any>({
|
||||||
|
|
||||||
const _handleToggleExpand = useCallback(
|
const _handleToggleExpand = useCallback(
|
||||||
async (id: string) => {
|
async (id: string) => {
|
||||||
|
const wasExpanded = expandedIds.has(id);
|
||||||
setExpandedIds((prev) => {
|
setExpandedIds((prev) => {
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
if (next.has(id)) {
|
if (next.has(id)) {
|
||||||
|
|
@ -463,7 +464,7 @@ export function FormGeneratorTree<T = any>({
|
||||||
});
|
});
|
||||||
|
|
||||||
const node = nodes.find((n) => n.id === id);
|
const node = nodes.find((n) => n.id === id);
|
||||||
if (node && !expandedIds.has(id)) {
|
if (node && !wasExpanded) {
|
||||||
const childMap = _buildChildMap(nodes);
|
const childMap = _buildChildMap(nodes);
|
||||||
const existingChildren = childMap.get(id);
|
const existingChildren = childMap.get(id);
|
||||||
if (!existingChildren || existingChildren.length === 0) {
|
if (!existingChildren || existingChildren.length === 0) {
|
||||||
|
|
@ -472,11 +473,28 @@ export function FormGeneratorTree<T = any>({
|
||||||
setNodes((prev) => [...prev, ...childNodes]);
|
setNodes((prev) => [...prev, ...childNodes]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
_scrollExpandedNodeToCenter(id);
|
||||||
|
}, 50);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[nodes, expandedIds, provider, ownership],
|
[nodes, expandedIds, provider, ownership],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const _scrollExpandedNodeToCenter = useCallback((nodeId: string) => {
|
||||||
|
const container = treeContentRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
const el = container.querySelector(`[data-node-id="${nodeId}"]`) as HTMLElement | null;
|
||||||
|
if (!el) return;
|
||||||
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
const elRect = el.getBoundingClientRect();
|
||||||
|
const midpoint = containerRect.top + containerRect.height / 2;
|
||||||
|
if (elRect.top > midpoint) {
|
||||||
|
const scrollTarget = container.scrollTop + (elRect.top - midpoint);
|
||||||
|
container.scrollTo({ top: scrollTarget, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const _handleToggleSelect = useCallback(
|
const _handleToggleSelect = useCallback(
|
||||||
(id: string, e: React.MouseEvent) => {
|
(id: string, e: React.MouseEvent) => {
|
||||||
const newSelection = new Set(selectedIds);
|
const newSelection = new Set(selectedIds);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
* - NavLink integration with React Router
|
* - NavLink integration with React Router
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect, ReactNode } from 'react';
|
import React, { useState, useEffect, useRef, useCallback, ReactNode } from 'react';
|
||||||
import { NavLink, useLocation } from 'react-router-dom';
|
import { NavLink, useLocation } from 'react-router-dom';
|
||||||
import styles from './TreeNavigation.module.css';
|
import styles from './TreeNavigation.module.css';
|
||||||
|
|
||||||
|
|
@ -151,6 +151,7 @@ const TreeNode: React.FC<TreeNodeProps> = ({
|
||||||
const [isExpanded, setIsExpanded] = useState(
|
const [isExpanded, setIsExpanded] = useState(
|
||||||
node.defaultExpanded ?? shouldAutoExpand ?? false
|
node.defaultExpanded ?? shouldAutoExpand ?? false
|
||||||
);
|
);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Auto-expand when path becomes active
|
// Auto-expand when path becomes active
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -159,6 +160,16 @@ const TreeNode: React.FC<TreeNodeProps> = ({
|
||||||
}
|
}
|
||||||
}, [currentPath, autoExpandActive, node]);
|
}, [currentPath, autoExpandActive, node]);
|
||||||
|
|
||||||
|
const _scrollAfterExpand = useCallback(() => {
|
||||||
|
const el = containerRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
const viewportMid = window.innerHeight / 2;
|
||||||
|
if (rect.top > viewportMid) {
|
||||||
|
el.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Check if this node is active (exact match or ancestor of active path)
|
// Check if this node is active (exact match or ancestor of active path)
|
||||||
const isActive = node.path ? currentPath === node.path || currentPath.startsWith(node.path + '/') : false;
|
const isActive = node.path ? currentPath === node.path || currentPath.startsWith(node.path + '/') : false;
|
||||||
// Differentiate: leaf active (strong highlight) vs group active (subtle text only)
|
// Differentiate: leaf active (strong highlight) vs group active (subtle text only)
|
||||||
|
|
@ -179,12 +190,13 @@ const TreeNode: React.FC<TreeNodeProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isExpandable && !node.path) {
|
if (isExpandable && !node.path) {
|
||||||
// If only expandable (no path), toggle expand
|
const willExpand = !isExpanded;
|
||||||
setIsExpanded(!isExpanded);
|
setIsExpanded(willExpand);
|
||||||
|
if (willExpand) setTimeout(_scrollAfterExpand, 50);
|
||||||
} else if (isExpandable && node.path) {
|
} else if (isExpandable && node.path) {
|
||||||
// If both expandable and has path, expand on click but allow navigation
|
|
||||||
if (!isExpanded) {
|
if (!isExpanded) {
|
||||||
setIsExpanded(true);
|
setIsExpanded(true);
|
||||||
|
setTimeout(_scrollAfterExpand, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,7 +209,9 @@ const TreeNode: React.FC<TreeNodeProps> = ({
|
||||||
const handleToggleClick = (e: React.MouseEvent) => {
|
const handleToggleClick = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsExpanded(!isExpanded);
|
const willExpand = !isExpanded;
|
||||||
|
setIsExpanded(willExpand);
|
||||||
|
if (willExpand) setTimeout(_scrollAfterExpand, 50);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render the node content (actions are rendered outside to avoid button-in-button nesting)
|
// Render the node content (actions are rendered outside to avoid button-in-button nesting)
|
||||||
|
|
@ -255,7 +269,7 @@ const TreeNode: React.FC<TreeNodeProps> = ({
|
||||||
const canRenderChildren = maxDepth === 0 || level < maxDepth;
|
const canRenderChildren = maxDepth === 0 || level < maxDepth;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.treeNodeContainer}>
|
<div className={styles.treeNodeContainer} ref={containerRef}>
|
||||||
{nodeElement}
|
{nodeElement}
|
||||||
{node.actions && (
|
{node.actions && (
|
||||||
<span className={styles.nodeActions} onClick={(e) => e.stopPropagation()}>
|
<span className={styles.nodeActions} onClick={(e) => e.stopPropagation()}>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue