diff --git a/docs/spec/07-implementation-phases-todo.md b/docs/spec/07-implementation-phases-todo.md index 0859dee..b3d2c96 100644 --- a/docs/spec/07-implementation-phases-todo.md +++ b/docs/spec/07-implementation-phases-todo.md @@ -281,6 +281,13 @@ Tasks: - review sensible default columns and responsive behavior for typical browser widths - redesign column filtering with clearer controls, active-filter indicators and an easy reset action +Implemented layout foundation: + +- table scrolling is contained inside the editor so sticky headers remain visible +- the toolbar remains reachable while the page scrolls +- programmatic grid focus preserves the viewport during undo/redo reloads +- wide tables no longer expand the surrounding browser layout + Acceptance criteria: - users retain column context anywhere in a long list diff --git a/src/app/globals.css b/src/app/globals.css index 8a71bb2..9038930 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -44,12 +44,19 @@ body { display: flex; flex-direction: column; gap: 0.75rem; + min-width: 0; } .editor-toolbar { display: flex; + flex-wrap: wrap; gap: 0.5rem; - position: relative; + position: sticky; + top: 0; + z-index: 12; + padding: 0.35rem 0; + background: #fff; + box-shadow: 0 1px 0 rgba(196, 205, 220, 0.8); } .editor-toolbar button { @@ -137,13 +144,20 @@ body { overflow: auto; border: 1px solid #d9dee8; background: #fff; + width: 100%; + max-width: 100%; + min-width: 0; + max-height: max(28rem, calc(100vh - 12rem)); + scrollbar-gutter: stable; } .tree-editor-layout { display: grid; - grid-template-columns: 320px 1fr; + grid-template-columns: minmax(240px, 320px) minmax(0, 1fr); gap: 0.75rem; min-height: 560px; + min-width: 0; + align-items: start; } .project-device-sidebar { diff --git a/src/frontend/components/circuit-tree-editor.tsx b/src/frontend/components/circuit-tree-editor.tsx index 33efa0a..b5a5927 100644 --- a/src/frontend/components/circuit-tree-editor.tsx +++ b/src/frontend/components/circuit-tree-editor.tsx @@ -482,7 +482,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str setAnchorRowKey(null); } setSelectedCell({ rowKey: row.rowKey, cellKey }); - requestAnimationFrame(() => containerRef.current?.focus()); + focusGridWithoutScroll(); } // Captures semantic identity for post-reload focus restoration. @@ -556,7 +556,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str } await loadTree({ showLoading: false }); if (!intent) { - requestAnimationFrame(() => containerRef.current?.focus()); + focusGridWithoutScroll(); } } @@ -779,7 +779,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str if (resolved) { setSelectedCell(resolved); } - requestAnimationFrame(() => containerRef.current?.focus()); + focusGridWithoutScroll(); }, [data, editableCells, visibleRows]); // Restores multi-selected device rows after operations that rebuild row keys. @@ -870,6 +870,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str return row?.cells.find((cell) => cell.cellKey === cellKey); } + function focusGridWithoutScroll() { + requestAnimationFrame(() => containerRef.current?.focus({ preventScroll: true })); + } + // Opens edit session for selected cell. // Enter/F2/double-click use selectExisting; type-to-edit uses replaceWithTypedChar. function startEdit(cell: SelectedCell, mode: StartEditMode, typedChar?: string) { @@ -1169,7 +1173,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str } setEditingCell(null); setSelectedCell({ rowKey: editingCell.rowKey, cellKey: editingCell.cellKey }); - requestAnimationFrame(() => containerRef.current?.focus()); + focusGridWithoutScroll(); } async function handleAddReserveCircuit(sectionId: string, afterCircuitId?: string) { @@ -3164,7 +3168,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str const active = document.activeElement as HTMLElement | null; if (!active || !containerRef.current?.contains(active)) { setEditingCell(null); - requestAnimationFrame(() => containerRef.current?.focus()); + focusGridWithoutScroll(); } }); }}