feat(layout): widen current pane to 50% — PANE_RATIO 2:5:3
Change the parent|current|preview split from 1:2:2 (20/40/40) to 2:5:3 (20/50/30) so the focused list gets more room. 2-pane tabs now give current the combined 80%. Updates ratio comments and the PaneRow test expectations.
This commit is contained in:
@@ -1,15 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* PaneRow — the shared parent | current | preview 3-pane layout primitive.
|
* PaneRow — the shared parent | current | preview 3-pane layout primitive.
|
||||||
*
|
*
|
||||||
* Implements yazi's `mgr.ratio = [1, 2, 2]` contract: three columns grow at
|
* Implements yazi's `mgr.ratio` contract: three columns grow at
|
||||||
* 1/5 : 2/5 : 2/5 of the row width via Yoga `flexGrow`, so every list tab
|
* 20% : 50% : 30% (PANE_RATIO 2:5:3) of the row width via Yoga `flexGrow`,
|
||||||
* renders an identical, layout-stable shell. Columns use `flexBasis={0}` so
|
* so every list tab renders an identical, layout-stable shell. Columns use
|
||||||
* the ratio is exact regardless of content width — a column's content can
|
* `flexBasis={0}` so the ratio is exact regardless of content width — a
|
||||||
* never stretch its slot.
|
* column's content can never stretch its slot.
|
||||||
*
|
*
|
||||||
* Column semantics (per the yazi depth model):
|
* Column semantics (per the yazi depth model):
|
||||||
* parent — the previous-depth list. Renders a muted `—` placeholder and
|
* parent — the previous-depth list. Renders a muted `—` placeholder and
|
||||||
* KEEPS its 1/5 slot when blank (never collapses to width 0).
|
* KEEPS its 20% slot when blank (never collapses to width 0).
|
||||||
* Borderless (no left/right/top/bottom edge). Carries the single
|
* Borderless (no left/right/top/bottom edge). Carries the single
|
||||||
* header row: the CURRENT column's title renders top-left in the
|
* header row: the CURRENT column's title renders top-left in the
|
||||||
* parent's slot (the panes above current/preview were removed).
|
* parent's slot (the panes above current/preview were removed).
|
||||||
@@ -184,7 +184,7 @@ export function PaneRow(props: PaneRowProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<box flexDirection="row" flexGrow={1} width="100%" height="100%">
|
<box flexDirection="row" flexGrow={1} width="100%" height="100%">
|
||||||
{/* ── parent (1/5) — previous-depth list; title row top-left ───────── */}
|
{/* ── parent (20%) — previous-depth list; title row top-left ────────── */}
|
||||||
<Pane
|
<Pane
|
||||||
grow={PANE_RATIO.parent}
|
grow={PANE_RATIO.parent}
|
||||||
label={currentLabel}
|
label={currentLabel}
|
||||||
@@ -200,7 +200,7 @@ export function PaneRow(props: PaneRowProps) {
|
|||||||
border={["left", "right"]}
|
border={["left", "right"]}
|
||||||
scrollFocused={() => focused()}
|
scrollFocused={() => focused()}
|
||||||
/>
|
/>
|
||||||
{/* ── preview (2/5) — hovered-item detail; no border, no header ────── */}
|
{/* ── preview (30%) — hovered-item detail; no border, no header ────── */}
|
||||||
<Show when={panes() === 3}>
|
<Show when={panes() === 3}>
|
||||||
<Pane
|
<Pane
|
||||||
grow={PANE_RATIO.preview}
|
grow={PANE_RATIO.preview}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
*
|
*
|
||||||
* parent | current | preview
|
* parent | current | preview
|
||||||
*
|
*
|
||||||
* Layout ratios (1/5 : 2/5 : 2/5 in the final remake) live in
|
* Layout ratios (20% : 50% : 30% — PANE_RATIO 2:5:3) live in
|
||||||
* `@/utils/navigation` (PANE_RATIO). This module owns only the *focusable*
|
* `@/utils/navigation` (PANE_RATIO). This module owns only the *focusable*
|
||||||
* nav model — which column is focused and where its list cursor lives. The
|
* nav model — which column is focused and where its list cursor lives. The
|
||||||
* parent/preview columns are always derived, never focused.
|
* parent/preview columns are always derived, never focused.
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ export function rootFrameFor(
|
|||||||
// terminal size — more robust than fixed percentages and exactly mirrors
|
// terminal size — more robust than fixed percentages and exactly mirrors
|
||||||
// yazi's `mgr.ratio` config. Set a slot's ratio to 0 to hide it (2-pane tabs).
|
// yazi's `mgr.ratio` config. Set a slot's ratio to 0 to hide it (2-pane tabs).
|
||||||
//
|
//
|
||||||
// Current ratios: parent : current : preview = 1 : 2 : 2, i.e. 1/5 : 2/5 : 2/5
|
// Current ratios: parent : current : preview = 2 : 5 : 3, i.e. 20% / 50% / 30%
|
||||||
// (20% / 40% / 40% of the row width). 2-pane tabs drop the preview slot and
|
// of the row width (2 : 5 : 3 of 10). 2-pane tabs drop the preview slot and
|
||||||
// give `current` the combined 4/5.
|
// give `current` the combined 8/10 (80%).
|
||||||
export const PANE_RATIO = {
|
export const PANE_RATIO = {
|
||||||
parent: 1,
|
parent: 2,
|
||||||
current: 2,
|
current: 5,
|
||||||
preview: 2,
|
preview: 3,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// Number of *focusable* content panes per tab. The three visible columns
|
// Number of *focusable* content panes per tab. The three visible columns
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* PaneRow tests — the 1:2:2 parent|current|preview layout primitive.
|
* PaneRow tests — the 2:5:3 (20/50/30) parent|current|preview layout primitive.
|
||||||
*
|
*
|
||||||
* Verified through the opentui test renderer's captured frames (the same
|
* Verified through the opentui test renderer's captured frames (the same
|
||||||
* mechanism the `.harness` drive uses), since `flexGrow` ratios are only
|
* mechanism the `.harness` drive uses), since `flexGrow` ratios are only
|
||||||
* observable as rendered column widths.
|
* observable in rendered output, not in unit-testable state.
|
||||||
*
|
*
|
||||||
* • Unit: three columns render at 1:2:2 (e.g. 20/40/40 of 100) even when the
|
* • Unit: three columns render at 2:5:3 (e.g. 20/50/30 of 100) even when the
|
||||||
* parent and preview children are null, and the blank parent keeps its
|
* parent and preview children are null, and the blank parent keeps its
|
||||||
* slot with a muted placeholder.
|
* slot with a muted placeholder.
|
||||||
* • Integration: the current pane renders muted left/right border edges
|
* • Integration: the current pane renders muted left/right border edges
|
||||||
@@ -119,9 +119,9 @@ afterAll(async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Unit: three columns at 1:2:2 regardless of null children ───────────────
|
// ── Unit: three columns at 2:5:3 regardless of null children ───────────────
|
||||||
describe("PaneRow layout", () => {
|
describe("PaneRow layout", () => {
|
||||||
test("renders three columns at 1:2:2 even with null parent/preview", async () => {
|
test("renders three columns at 2:5:3 even with null parent/preview", async () => {
|
||||||
const { spans, destroy } = await renderPaneRow({
|
const { spans, destroy } = await renderPaneRow({
|
||||||
parent: null,
|
parent: null,
|
||||||
current: () => <text>ITEM</text>,
|
current: () => <text>ITEM</text>,
|
||||||
@@ -132,15 +132,15 @@ describe("PaneRow layout", () => {
|
|||||||
const widths = columnWidths(spans);
|
const widths = columnWidths(spans);
|
||||||
expect(widths).toHaveLength(3);
|
expect(widths).toHaveLength(3);
|
||||||
const [p, c, v] = widths;
|
const [p, c, v] = widths;
|
||||||
// 100-wide row splits as 20 / 40 / 40 (1/5 : 2/5 : 2/5).
|
// 100-wide row splits as 20 / 50 / 30 (2 : 5 : 3 of 10).
|
||||||
expect(p).toBe(20);
|
expect(p).toBe(20);
|
||||||
expect(c).toBe(40);
|
expect(c).toBe(50);
|
||||||
expect(v).toBe(40);
|
expect(v).toBe(30);
|
||||||
// Exact 1:2:2 proportion (within 1 col rounding).
|
// Exact 2:5:3 proportion (within 1 col rounding).
|
||||||
expect(c).toBeGreaterThanOrEqual(p * 2 - 1);
|
expect(c).toBeGreaterThanOrEqual(Math.round(p * 2.5) - 1);
|
||||||
expect(c).toBeLessThanOrEqual(p * 2 + 1);
|
expect(c).toBeLessThanOrEqual(Math.round(p * 2.5) + 1);
|
||||||
expect(v).toBeGreaterThanOrEqual(p * 2 - 1);
|
expect(v).toBeGreaterThanOrEqual(Math.round(p * 1.5) - 1);
|
||||||
expect(v).toBeLessThanOrEqual(p * 2 + 1);
|
expect(v).toBeLessThanOrEqual(Math.round(p * 1.5) + 1);
|
||||||
// Parent keeps a visibly non-zero slot and renders the muted placeholder.
|
// Parent keeps a visibly non-zero slot and renders the muted placeholder.
|
||||||
expect(p).toBeGreaterThan(4);
|
expect(p).toBeGreaterThan(4);
|
||||||
const body = spans.lines
|
const body = spans.lines
|
||||||
@@ -150,7 +150,7 @@ describe("PaneRow layout", () => {
|
|||||||
expect(body).toContain("ITEM");
|
expect(body).toContain("ITEM");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("keeps the 1/5 parent slot across widths (ratio stable)", async () => {
|
test("keeps the 20% parent slot across widths (ratio stable)", async () => {
|
||||||
const { spans, destroy } = await renderPaneRow({
|
const { spans, destroy } = await renderPaneRow({
|
||||||
parent: null,
|
parent: null,
|
||||||
current: () => <text>x</text>,
|
current: () => <text>x</text>,
|
||||||
@@ -159,9 +159,9 @@ describe("PaneRow layout", () => {
|
|||||||
});
|
});
|
||||||
cleanups.push(destroy);
|
cleanups.push(destroy);
|
||||||
const [p, c, v] = columnWidths(spans);
|
const [p, c, v] = columnWidths(spans);
|
||||||
expect(p).toBe(14); // 70 → 14 / 28 / 28
|
expect(p).toBe(14); // 70 → 14 / 35 / 21
|
||||||
expect(c).toBe(28);
|
expect(c).toBe(35);
|
||||||
expect(v).toBe(28);
|
expect(v).toBe(21);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -181,9 +181,9 @@ describe("PaneRow current-pane borders", () => {
|
|||||||
});
|
});
|
||||||
cleanups.push(destroy);
|
cleanups.push(destroy);
|
||||||
|
|
||||||
// 100-wide row splits as 20 / 40 / 40: the current pane's edges sit at
|
// 100-wide row splits as 20 / 50 / 30: the current pane's edges sit at
|
||||||
// columns 20 and 59. No horizontal or corner glyphs — edges only.
|
// columns 20 and 69. No horizontal or corner glyphs — edges only.
|
||||||
expect(borderColumns(spans)).toEqual([20, 59]);
|
expect(borderColumns(spans)).toEqual([20, 69]);
|
||||||
expect(frameText(spans)).not.toMatch(boxGlyphs);
|
expect(frameText(spans)).not.toMatch(boxGlyphs);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ describe("PaneRow current-pane borders", () => {
|
|||||||
});
|
});
|
||||||
cleanups.push(destroy);
|
cleanups.push(destroy);
|
||||||
|
|
||||||
expect(borderColumns(spans)).toEqual([20, 59]);
|
expect(borderColumns(spans)).toEqual([20, 69]);
|
||||||
expect(frameText(spans)).not.toMatch(boxGlyphs);
|
expect(frameText(spans)).not.toMatch(boxGlyphs);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ describe("PaneRow current-pane borders", () => {
|
|||||||
focused: () => true,
|
focused: () => true,
|
||||||
});
|
});
|
||||||
cleanups.push(destroy);
|
cleanups.push(destroy);
|
||||||
expect(borderColumns(spans)).toEqual([20, 59]);
|
expect(borderColumns(spans)).toEqual([20, 69]);
|
||||||
expect(frameText(spans)).not.toMatch(boxGlyphs);
|
expect(frameText(spans)).not.toMatch(boxGlyphs);
|
||||||
|
|
||||||
const { spans: spans2, destroy: destroy2 } = await renderPaneRow({
|
const { spans: spans2, destroy: destroy2 } = await renderPaneRow({
|
||||||
@@ -218,7 +218,7 @@ describe("PaneRow current-pane borders", () => {
|
|||||||
focused: () => false,
|
focused: () => false,
|
||||||
});
|
});
|
||||||
cleanups.push(destroy2);
|
cleanups.push(destroy2);
|
||||||
expect(borderColumns(spans2)).toEqual([20, 59]);
|
expect(borderColumns(spans2)).toEqual([20, 69]);
|
||||||
expect(frameText(spans2)).not.toMatch(boxGlyphs);
|
expect(frameText(spans2)).not.toMatch(boxGlyphs);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ describe("PaneRow current-pane borders", () => {
|
|||||||
preview: null,
|
preview: null,
|
||||||
});
|
});
|
||||||
cleanups.push(destroy);
|
cleanups.push(destroy);
|
||||||
expect(borderColumns(spans)).toEqual([20, 59]);
|
expect(borderColumns(spans)).toEqual([20, 69]);
|
||||||
expect(frameText(spans)).not.toMatch(boxGlyphs);
|
expect(frameText(spans)).not.toMatch(boxGlyphs);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user