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:
@@ -20,6 +20,7 @@ import {
|
||||
loadGridLayout,
|
||||
saveGridLayout,
|
||||
mountGridToggle,
|
||||
matchFocusCycleKey,
|
||||
} from '../public/grid-layout.js'
|
||||
|
||||
const KEY = 'web-terminal:grid-layout'
|
||||
@@ -62,11 +63,30 @@ describe('grid-layout: pure logic', () => {
|
||||
it('layoutCapacity maps each layout to its pane count', () => {
|
||||
expect(layoutCapacity('single')).toBe(1)
|
||||
expect(layoutCapacity('split-2')).toBe(2)
|
||||
expect(layoutCapacity('row-3')).toBe(3)
|
||||
expect(layoutCapacity('grid-4')).toBe(4)
|
||||
expect(layoutCapacity('grid-6')).toBe(6)
|
||||
})
|
||||
|
||||
it('GRID_LAYOUTS is ordered single → split-2 → grid-4', () => {
|
||||
expect(GRID_LAYOUTS).toEqual(['single', 'split-2', 'grid-4'])
|
||||
it('GRID_LAYOUTS is ordered by capacity', () => {
|
||||
expect(GRID_LAYOUTS).toEqual(['single', 'split-2', 'row-3', 'grid-4', 'grid-6'])
|
||||
})
|
||||
|
||||
it('visibleIndices honors the higher-capacity layouts', () => {
|
||||
expect(visibleIndices(9, 'row-3')).toEqual([0, 1, 2])
|
||||
expect(visibleIndices(9, 'grid-6')).toEqual([0, 1, 2, 3, 4, 5])
|
||||
expect(visibleIndices(4, 'grid-6')).toEqual([0, 1, 2, 3])
|
||||
})
|
||||
|
||||
it('matchFocusCycleKey recognizes Ctrl+` (fwd) and Ctrl+Shift+` (rev), else null', () => {
|
||||
const k = (init: KeyboardEventInit): KeyboardEvent => new KeyboardEvent('keydown', init)
|
||||
expect(matchFocusCycleKey(k({ ctrlKey: true, key: '`' }))).toBe(1)
|
||||
expect(matchFocusCycleKey(k({ ctrlKey: true, shiftKey: true, key: '`' }))).toBe(-1)
|
||||
expect(matchFocusCycleKey(k({ ctrlKey: true, code: 'Backquote' }))).toBe(1)
|
||||
expect(matchFocusCycleKey(k({ key: '`' }))).toBeNull() // no ctrl
|
||||
expect(matchFocusCycleKey(k({ ctrlKey: true, metaKey: true, key: '`' }))).toBeNull() // meta excluded
|
||||
expect(matchFocusCycleKey(k({ ctrlKey: true, altKey: true, key: '`' }))).toBeNull() // alt excluded
|
||||
expect(matchFocusCycleKey(k({ ctrlKey: true, key: 'a' }))).toBeNull() // wrong key
|
||||
})
|
||||
|
||||
it('visibleIndices returns the first min(tabCount, capacity) indices', () => {
|
||||
|
||||
Reference in New Issue
Block a user