feat(grid): split-grid v2 — 1×3/2×3, drag-to-quadrant, Ctrl+` cycle, maximize
Builds on the v1 watch board: - Two more layouts: row-3 (1×3) and grid-6 (2×3). A single `grid` marker class on #term now carries the shared cell chrome so it no longer enumerates each lay-*. - Ctrl+` cycles the focused quadrant (Ctrl+Shift+` reverses); matchFocusCycleKey is an exported pure helper so it's unit-tested. No-op / passthrough in single mode. - Per-quadrant ⛶ maximize: the focused cell expands to fill the grid as an absolute overlay while siblings stay live behind it; follows focus, resets on layout change / tab close. - Drag a tab from the tab bar onto a quadrant to assign it there (reuses the existing tab-drag dragIndex); grid-only. Adversarial review (3 lenses → per-finding verify, incl. a headless-Chrome repro) caught a HIGH: maximizing via `grid-column/row: 1/-1` shoved siblings into implicit rows — a strip instead of fullscreen AND a spurious resize to backgrounded live PTYs. Fixed with an absolute-overlay (`position:absolute; inset:0`), then verified in real Chromium (Playwright): maximized cell fills #term, siblings 0px size delta. Also: silence a covered pane's pending pulse under maximize; coarse-pointer target for .cell-max. Verified: typecheck + build:web clean, 1579 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,15 +16,23 @@
|
||||
*/
|
||||
|
||||
/** A screen layout for the terminal area. Ordered by pane capacity. */
|
||||
export type GridLayout = 'single' | 'split-2' | 'grid-4'
|
||||
export type GridLayout = 'single' | 'split-2' | 'row-3' | 'grid-4' | 'grid-6'
|
||||
|
||||
/** All layouts, in the order the toggle presents them. */
|
||||
export const GRID_LAYOUTS: readonly GridLayout[] = ['single', 'split-2', 'grid-4']
|
||||
/** All layouts, in the order the toggle presents them (by capacity). */
|
||||
export const GRID_LAYOUTS: readonly GridLayout[] = [
|
||||
'single',
|
||||
'split-2',
|
||||
'row-3',
|
||||
'grid-4',
|
||||
'grid-6',
|
||||
]
|
||||
|
||||
const CAPACITY: Readonly<Record<GridLayout, number>> = {
|
||||
single: 1,
|
||||
'split-2': 2,
|
||||
'row-3': 3,
|
||||
'grid-4': 4,
|
||||
'grid-6': 6,
|
||||
}
|
||||
|
||||
/** How many panes a layout shows at once. */
|
||||
@@ -57,6 +65,17 @@ export function isVisibleIndex(
|
||||
return idx >= 0 && idx < Math.min(Math.max(0, tabCount), layoutCapacity(layout))
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the split-grid focus-cycle keybinding (Ctrl+` forward, Ctrl+Shift+`
|
||||
* reverse). Returns the cycle direction, or null if the event isn't the binding.
|
||||
* Extracted from the DOM listener so it's unit-testable in isolation.
|
||||
*/
|
||||
export function matchFocusCycleKey(e: KeyboardEvent): 1 | -1 | null {
|
||||
if (!e.ctrlKey || e.altKey || e.metaKey) return null
|
||||
if (e.key !== '`' && e.code !== 'Backquote') return null
|
||||
return e.shiftKey ? -1 : 1
|
||||
}
|
||||
|
||||
/** Minimum viewport width (px) at which multi-pane layouts are offered. Four
|
||||
* terminals need real width; below this the board falls back to 'single'. */
|
||||
export const GRID_MIN_WIDTH = 1024
|
||||
@@ -73,7 +92,9 @@ export function gridAllowed(): boolean {
|
||||
const GRID_LAYOUT_KEY = 'web-terminal:grid-layout'
|
||||
|
||||
function isGridLayout(v: unknown): v is GridLayout {
|
||||
return v === 'single' || v === 'split-2' || v === 'grid-4'
|
||||
return (
|
||||
v === 'single' || v === 'split-2' || v === 'row-3' || v === 'grid-4' || v === 'grid-6'
|
||||
)
|
||||
}
|
||||
|
||||
/** Load the persisted layout. Forced to 'single' when the screen is too narrow
|
||||
@@ -114,7 +135,9 @@ export interface GridToggle {
|
||||
const LAYOUT_META: Readonly<Record<GridLayout, { title: string; cells: number }>> = {
|
||||
single: { title: 'Single pane', cells: 1 },
|
||||
'split-2': { title: 'Side by side — compare two', cells: 2 },
|
||||
'row-3': { title: '1×3 row — three across', cells: 3 },
|
||||
'grid-4': { title: '2×2 watch board', cells: 4 },
|
||||
'grid-6': { title: '2×3 grid — six sessions', cells: 6 },
|
||||
}
|
||||
|
||||
/** A small CSS-drawn layout glyph (N cells), reliable across platforms. */
|
||||
|
||||
Reference in New Issue
Block a user