Restore center-pane borders, move title to top-left slot
- Current pane gets muted left/right borders only (no full box, no accent ring); border colors are passed only when a border is requested, since opentui flips borderless boxes to bordered when borderColor is supplied. - Remove the Up / <current tab> / Detail titles above the panes; the current pane's title now renders once, top-left in the parent column's header slot. - Drop the parentLabel/previewLabel props from PaneRow and all callers. - Remove the tab/depth indicator from the bottom-left of the status bar. - Tests measure column widths from the border glyphs and assert the left/right edges render muted regardless of focus.
This commit is contained in:
@@ -10,25 +10,26 @@
|
|||||||
* 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 1/5 slot when blank (never collapses to width 0).
|
||||||
* Borderless (no left/right/top/bottom edge).
|
* Borderless (no left/right/top/bottom edge). Carries the single
|
||||||
|
* header row: the CURRENT column's title renders top-left in the
|
||||||
|
* parent's slot (the panes above current/preview were removed).
|
||||||
* current — the current-depth list. The only focusable content column; it
|
* current — the current-depth list. The only focusable content column; it
|
||||||
* is the ONLY bordered column (full border, always muted — no
|
* is the ONLY bordered column — left/right edges only, always
|
||||||
* active-border highlight).
|
* muted (no active-border highlight, focused or not).
|
||||||
* preview — detail of the hovered item in `current`. Borderless.
|
* preview — detail of the hovered item in `current`. Borderless, no header.
|
||||||
*
|
*
|
||||||
* The primitive is purely structural: callers pass their own JSX per column
|
* The primitive is purely structural: callers pass their own JSX per column
|
||||||
* (static elements or accessors) plus header labels. Theme colors are resolved
|
* (static elements or accessors) plus the current-column title. Theme colors
|
||||||
* internally via `useTheme()`. Only the current column's `<scrollbox>` receives
|
* are resolved internally via `useTheme()`. Only the current column's
|
||||||
* `focused`, so scroll focus follows the cursor (j/k stay in the current pane).
|
* `<scrollbox>` receives `focused`, so scroll focus follows the cursor (j/k
|
||||||
|
* stay in the current pane).
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* <PaneRow
|
* <PaneRow
|
||||||
* parent={parentList}
|
* parent={parentList}
|
||||||
* current={currentList}
|
* current={currentList}
|
||||||
* preview={detail}
|
* preview={detail}
|
||||||
* parentLabel="Up"
|
|
||||||
* currentLabel="List · 42"
|
* currentLabel="List · 42"
|
||||||
* previewLabel="Detail"
|
|
||||||
* focused={isActive}
|
* focused={isActive}
|
||||||
* />
|
* />
|
||||||
*/
|
*/
|
||||||
@@ -52,9 +53,9 @@ export type PaneRowProps = {
|
|||||||
/** Preview column content (detail of the hovered item). Omit/undefined
|
/** Preview column content (detail of the hovered item). Omit/undefined
|
||||||
* together with `panes={2}` to render a 2-pane parent|current row. */
|
* together with `panes={2}` to render a 2-pane parent|current row. */
|
||||||
preview?: PaneContent;
|
preview?: PaneContent;
|
||||||
parentLabel?: PaneLabel;
|
/** Title of the current column — rendered once, top-left in the parent
|
||||||
|
* pane's header slot (the per-pane Up/Detail headers are gone). */
|
||||||
currentLabel?: PaneLabel;
|
currentLabel?: PaneLabel;
|
||||||
previewLabel?: PaneLabel;
|
|
||||||
/** Whether the current column's `<scrollbox>` receives scroll focus. Defaults to
|
/** Whether the current column's `<scrollbox>` receives scroll focus. Defaults to
|
||||||
* true; pass `false` (or a signal) when the row is inactive. Does NOT change
|
* true; pass `false` (or a signal) when the row is inactive. Does NOT change
|
||||||
* border colors — the current column's border is always muted. */
|
* border colors — the current column's border is always muted. */
|
||||||
@@ -116,23 +117,32 @@ function Pane(props: {
|
|||||||
flexBasis={0}
|
flexBasis={0}
|
||||||
height="100%"
|
height="100%"
|
||||||
>
|
>
|
||||||
{/* ── slim header label row ─────────────────────────────────────────── */}
|
{/* ── title row: rendered only when the pane carries a label ────────── */}
|
||||||
<box
|
<Show when={props.label() !== ""}>
|
||||||
height={1}
|
<box
|
||||||
paddingLeft={1}
|
height={1}
|
||||||
backgroundColor={
|
paddingLeft={1}
|
||||||
themeContext.transparentBackground()
|
backgroundColor={
|
||||||
? "transparent"
|
themeContext.transparentBackground()
|
||||||
: theme.background
|
? "transparent"
|
||||||
}
|
: theme.background
|
||||||
>
|
}
|
||||||
<text fg={theme.textSecondary}>{props.label()}</text>
|
>
|
||||||
</box>
|
<text fg={theme.textSecondary}>{props.label()}</text>
|
||||||
{/* ── bordered scrollbox ────────────────────────────────────────────── */}
|
</box>
|
||||||
|
</Show>
|
||||||
|
{/* ── scrollbox; border always muted (focused or not) ──────────────── */}
|
||||||
<scrollbox
|
<scrollbox
|
||||||
height="100%"
|
height="100%"
|
||||||
focused={scrollFocused()}
|
focused={scrollFocused()}
|
||||||
border={props.border}
|
border={props.border}
|
||||||
|
// Only supply colors when a border is requested — opentui flips a
|
||||||
|
// borderless box to bordered when borderColor/focusedBorderColor
|
||||||
|
// are passed, which would frame the parent/preview panes too.
|
||||||
|
borderColor={props.border === false ? undefined : theme.border}
|
||||||
|
focusedBorderColor={
|
||||||
|
props.border === false ? undefined : theme.border
|
||||||
|
}
|
||||||
backgroundColor={
|
backgroundColor={
|
||||||
themeContext.transparentBackground()
|
themeContext.transparentBackground()
|
||||||
? "transparent"
|
? "transparent"
|
||||||
@@ -159,9 +169,9 @@ export function PaneRow(props: PaneRowProps) {
|
|||||||
const currentContent = normalizeContent(props.current);
|
const currentContent = normalizeContent(props.current);
|
||||||
const previewContent = normalizeContent(props.preview);
|
const previewContent = normalizeContent(props.preview);
|
||||||
|
|
||||||
const parentLabel = createMemo(() => resolveLabel(props.parentLabel));
|
// The single title: the CURRENT column's label, rendered in the parent
|
||||||
|
// pane's header slot (top-left). Current/preview panes have no headers.
|
||||||
const currentLabel = createMemo(() => resolveLabel(props.currentLabel));
|
const currentLabel = createMemo(() => resolveLabel(props.currentLabel));
|
||||||
const previewLabel = createMemo(() => resolveLabel(props.previewLabel));
|
|
||||||
|
|
||||||
// 2-pane mode (parent|current) grows the current column to fill the
|
// 2-pane mode (parent|current) grows the current column to fill the
|
||||||
// preview slot. Defaults to 3 (parent|current|preview).
|
// preview slot. Defaults to 3 (parent|current|preview).
|
||||||
@@ -174,27 +184,27 @@ 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; always muted ─────────────── */}
|
{/* ── parent (1/5) — previous-depth list; title row top-left ───────── */}
|
||||||
<Pane
|
<Pane
|
||||||
grow={PANE_RATIO.parent}
|
grow={PANE_RATIO.parent}
|
||||||
label={parentLabel}
|
label={currentLabel}
|
||||||
content={parentContent}
|
content={parentContent}
|
||||||
border={false}
|
border={false}
|
||||||
scrollFocused={() => false}
|
scrollFocused={() => false}
|
||||||
/>
|
/>
|
||||||
{/* ── current — the focused list; no border, no highlight ─────────── */}
|
{/* ── current — the focused list; left/right borders only ─────────── */}
|
||||||
<Pane
|
<Pane
|
||||||
grow={currentGrow()}
|
grow={currentGrow()}
|
||||||
label={currentLabel}
|
label={() => ""}
|
||||||
content={currentContent}
|
content={currentContent}
|
||||||
border={false}
|
border={["left", "right"]}
|
||||||
scrollFocused={() => focused()}
|
scrollFocused={() => focused()}
|
||||||
/>
|
/>
|
||||||
{/* ── preview (2/5) — hovered-item detail; always muted ────────────── */}
|
{/* ── preview (2/5) — 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}
|
||||||
label={previewLabel}
|
label={() => ""}
|
||||||
content={previewContent}
|
content={previewContent}
|
||||||
border={false}
|
border={false}
|
||||||
scrollFocused={() => false}
|
scrollFocused={() => false}
|
||||||
|
|||||||
@@ -23,20 +23,11 @@ import { useAppStore } from "@/stores/app";
|
|||||||
import { useToast } from "@/ui/toast";
|
import { useToast } from "@/ui/toast";
|
||||||
import { emit, on } from "@/utils/event-bus";
|
import { emit, on } from "@/utils/event-bus";
|
||||||
import { LayerGraph } from "@/utils/layer-graph";
|
import { LayerGraph } from "@/utils/layer-graph";
|
||||||
import { TABS, TabPaneCount } from "@/utils/navigation";
|
import { TABS } from "@/utils/navigation";
|
||||||
import { createDispatcher } from "@/utils/dispatch";
|
import { createDispatcher } from "@/utils/dispatch";
|
||||||
import { TabListPane } from "@/components/TabPanel";
|
import { TabListPane } from "@/components/TabPanel";
|
||||||
import { PaneRow } from "@/components/PaneRow";
|
import { PaneRow } from "@/components/PaneRow";
|
||||||
|
|
||||||
const TAB_LABEL: Record<TABS, string> = {
|
|
||||||
[TABS.FEED]: "Feed",
|
|
||||||
[TABS.MYSHOWS]: "My Shows",
|
|
||||||
[TABS.DISCOVER]: "Discover",
|
|
||||||
[TABS.SEARCH]: "Search",
|
|
||||||
[TABS.PLAYER]: "Player",
|
|
||||||
[TABS.SETTINGS]: "Settings",
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Shell() {
|
export function Shell() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const t = theme.theme;
|
const t = theme.theme;
|
||||||
@@ -271,9 +262,7 @@ export function Shell() {
|
|||||||
<text fg={t.textMuted}>j/k move · l/Enter open a tab</text>
|
<text fg={t.textMuted}>j/k move · l/Enter open a tab</text>
|
||||||
</box>
|
</box>
|
||||||
}
|
}
|
||||||
parentLabel="Up"
|
|
||||||
currentLabel="Tabs"
|
currentLabel="Tabs"
|
||||||
previewLabel=""
|
|
||||||
focused
|
focused
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -296,15 +285,6 @@ export function Shell() {
|
|||||||
<text fg={t.accent} paddingLeft={1}>
|
<text fg={t.accent} paddingLeft={1}>
|
||||||
{modeLabel()}
|
{modeLabel()}
|
||||||
</text>
|
</text>
|
||||||
<text fg={t.textMuted} paddingLeft={1}>
|
|
||||||
{nav.atRootTab()
|
|
||||||
? "Tabs · root"
|
|
||||||
: `${TAB_LABEL[nav.activeTab()]} · ${
|
|
||||||
nav.isDepthTab()
|
|
||||||
? `depth ${nav.currentDepth()}`
|
|
||||||
: `pane ${nav.activePane()}/${TabPaneCount[nav.activeTab()]}`
|
|
||||||
}`}
|
|
||||||
</text>
|
|
||||||
<Show when={nav.selectedIds().length > 0}>
|
<Show when={nav.selectedIds().length > 0}>
|
||||||
<text fg={t.warning} paddingLeft={1}>
|
<text fg={t.warning} paddingLeft={1}>
|
||||||
● {nav.selectedIds().length}
|
● {nav.selectedIds().length}
|
||||||
|
|||||||
@@ -376,9 +376,7 @@ function DiscoverPage() {
|
|||||||
parent={parentContent}
|
parent={parentContent}
|
||||||
current={currentContent}
|
current={currentContent}
|
||||||
preview={previewContent}
|
preview={previewContent}
|
||||||
parentLabel={() => (depth() >= 1 ? "Categories" : "Up")}
|
|
||||||
currentLabel={currentLabel}
|
currentLabel={currentLabel}
|
||||||
previewLabel="Detail"
|
|
||||||
focused={isActive}
|
focused={isActive}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -301,9 +301,7 @@ function FeedPage() {
|
|||||||
parent={parentContent}
|
parent={parentContent}
|
||||||
current={currentContent}
|
current={currentContent}
|
||||||
preview={previewContent}
|
preview={previewContent}
|
||||||
parentLabel="Up"
|
|
||||||
currentLabel={currentLabel}
|
currentLabel={currentLabel}
|
||||||
previewLabel="Detail"
|
|
||||||
focused={isActive}
|
focused={isActive}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -432,9 +432,7 @@ export function MyShowsPage() {
|
|||||||
parent={parentContent}
|
parent={parentContent}
|
||||||
current={currentContent}
|
current={currentContent}
|
||||||
preview={previewContent}
|
preview={previewContent}
|
||||||
parentLabel={() => (depth() >= 1 ? "Shows" : "Up")}
|
|
||||||
currentLabel={currentLabel}
|
currentLabel={currentLabel}
|
||||||
previewLabel="Detail"
|
|
||||||
focused={isActive}
|
focused={isActive}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ export function PlayerPage() {
|
|||||||
<PaneRow
|
<PaneRow
|
||||||
parent={parentContent}
|
parent={parentContent}
|
||||||
current={currentContent}
|
current={currentContent}
|
||||||
parentLabel="Up"
|
|
||||||
currentLabel="Player"
|
currentLabel="Player"
|
||||||
panes={2}
|
panes={2}
|
||||||
focused={isActive}
|
focused={isActive}
|
||||||
|
|||||||
@@ -431,9 +431,7 @@ function SearchPage() {
|
|||||||
parent={parentContent}
|
parent={parentContent}
|
||||||
current={currentContent}
|
current={currentContent}
|
||||||
preview={previewContent}
|
preview={previewContent}
|
||||||
parentLabel={() => (depth() >= 1 ? "Query" : "Up")}
|
|
||||||
currentLabel={currentLabel}
|
currentLabel={currentLabel}
|
||||||
previewLabel="Detail"
|
|
||||||
focused={isActive}
|
focused={isActive}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -267,12 +267,6 @@ export function SettingsPage() {
|
|||||||
if (d === 1) return sectionForDepth1()?.label ?? "Items";
|
if (d === 1) return sectionForDepth1()?.label ?? "Items";
|
||||||
return editorItem()?.label ?? "Editor";
|
return editorItem()?.label ?? "Editor";
|
||||||
};
|
};
|
||||||
const parentLabel = () => {
|
|
||||||
const d = depth();
|
|
||||||
if (d === 1) return "Sections";
|
|
||||||
if (d === 2) return sectionForDepth1()?.label ?? "";
|
|
||||||
return "Up";
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── parent pane: previous-depth list (blank at depth 0) ────────────────
|
// ── parent pane: previous-depth list (blank at depth 0) ────────────────
|
||||||
// Sibling <Show> blocks per depth (mirrors the preview pane) so Solid
|
// Sibling <Show> blocks per depth (mirrors the preview pane) so Solid
|
||||||
@@ -384,9 +378,7 @@ export function SettingsPage() {
|
|||||||
parent={parentContent}
|
parent={parentContent}
|
||||||
current={currentContent}
|
current={currentContent}
|
||||||
preview={previewContent}
|
preview={previewContent}
|
||||||
parentLabel={parentLabel}
|
|
||||||
currentLabel={currentLabel}
|
currentLabel={currentLabel}
|
||||||
previewLabel="Detail"
|
|
||||||
focused={isActive}
|
focused={isActive}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
* • Unit: three columns render at 1:2:2 (e.g. 20/40/40 of 100) even when the
|
* • Unit: three columns render at 1:2:2 (e.g. 20/40/40 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 panes are fully borderless — `focused` toggles
|
* • Integration: the current pane renders muted left/right border edges
|
||||||
* scroll-following but never surfaces a border or accent ring.
|
* only (no full box, no accent ring) — `focused` toggles scroll-following
|
||||||
|
* but never changes the border; parent and preview stay borderless.
|
||||||
*
|
*
|
||||||
* Runs via `bun test`. The `[test] preload = "@opentui/solid/preload"` entry
|
* Runs via `bun test`. The `[test] preload = "@opentui/solid/preload"` entry
|
||||||
* in bunfig.toml registers the solid JSX transform for the test runner, so
|
* in bunfig.toml registers the solid JSX transform for the test runner, so
|
||||||
@@ -24,29 +25,32 @@ import { PaneRow } from "../src/components/PaneRow";
|
|||||||
type Span = { text: string };
|
type Span = { text: string };
|
||||||
type Frame = { cols: number; lines: { spans: Span[] }[] };
|
type Frame = { cols: number; lines: { spans: Span[] }[] };
|
||||||
|
|
||||||
/** Column of the first span whose text contains `label` in the given line. */
|
/** Positions of all `│` border glyphs in the first body line that has any. */
|
||||||
function labelColumn(line: Frame["lines"][number], label: string): number {
|
function borderColumns(frame: Frame): number[] {
|
||||||
|
const line = frame.lines.find((l) =>
|
||||||
|
l.spans.some((s) => s.text.includes("│")),
|
||||||
|
);
|
||||||
|
if (!line) return [];
|
||||||
|
const cols: number[] = [];
|
||||||
let col = 0;
|
let col = 0;
|
||||||
for (const sp of line.spans) {
|
for (const sp of line.spans) {
|
||||||
if (sp.text.includes(label)) return col;
|
for (const ch of sp.text) {
|
||||||
col += sp.text.length;
|
if (ch === "│") cols.push(col);
|
||||||
|
col++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return cols;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Column widths, measured from the header-label row (`Up|List|Detail`).
|
* Column widths, measured from the current pane's left/right border glyphs:
|
||||||
* Each label box has a 1-col left padding, so a column's left edge is the
|
* the parent runs from column 0 to the left border, the current pane spans
|
||||||
* label start minus 1; the last column runs to the frame's right edge.
|
* both borders, the preview runs from the right border to the frame's edge.
|
||||||
*/
|
*/
|
||||||
function columnWidths(spans: Frame): number[] {
|
function columnWidths(spans: Frame): number[] {
|
||||||
const line = spans.lines[0];
|
const [a, b] = borderColumns(spans);
|
||||||
if (!line) return [];
|
if (b === undefined) return [];
|
||||||
const up = labelColumn(line, "Up");
|
return [a, b - a + 1, spans.cols - b - 1];
|
||||||
const list = labelColumn(line, "List");
|
|
||||||
const detail = labelColumn(line, "Detail");
|
|
||||||
if (up < 0 || list < 0 || detail < 0) return [];
|
|
||||||
return [list - up, detail - list, spans.cols - detail + 1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Entire frame as plain text — used to assert no border glyphs remain. */
|
/** Entire frame as plain text — used to assert no border glyphs remain. */
|
||||||
@@ -77,9 +81,7 @@ async function renderPaneRow(props: TestPaneProps): Promise<{
|
|||||||
parent={props.parent as any}
|
parent={props.parent as any}
|
||||||
current={props.current as any}
|
current={props.current as any}
|
||||||
preview={props.preview as any}
|
preview={props.preview as any}
|
||||||
parentLabel="Up"
|
|
||||||
currentLabel="List"
|
currentLabel="List"
|
||||||
previewLabel="Detail"
|
|
||||||
focused={props.focused as any}
|
focused={props.focused as any}
|
||||||
/>
|
/>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
@@ -87,14 +89,14 @@ async function renderPaneRow(props: TestPaneProps): Promise<{
|
|||||||
{ width: props.width ?? 100, height: props.height ?? 8, useThread: false },
|
{ width: props.width ?? 100, height: props.height ?? 8, useThread: false },
|
||||||
);
|
);
|
||||||
// ThemeProvider only mounts its children once the theme resolves (async
|
// ThemeProvider only mounts its children once the theme resolves (async
|
||||||
// palette/theme loading). Poll the header-label row until it renders, so
|
// palette/theme loading). Poll the title row until it renders, so the
|
||||||
// the captured frame below is actually a mounted PaneRow.
|
// captured frame below is actually a mounted PaneRow.
|
||||||
let spans: Frame | null = null;
|
let spans: Frame | null = null;
|
||||||
for (let i = 0; i < 40 && !spans; i++) {
|
for (let i = 0; i < 40 && !spans; i++) {
|
||||||
await setup.renderOnce();
|
await setup.renderOnce();
|
||||||
const frame = setup.captureSpans() as unknown as Frame;
|
const frame = setup.captureSpans() as unknown as Frame;
|
||||||
const head = frame.lines[0]?.spans.map((s) => s.text).join("") ?? "";
|
const head = frame.lines[0]?.spans.map((s) => s.text).join("") ?? "";
|
||||||
if (head.includes("Up")) spans = frame;
|
if (head.includes("List")) spans = frame;
|
||||||
else await new Promise((r) => setTimeout(r, 100));
|
else await new Promise((r) => setTimeout(r, 100));
|
||||||
}
|
}
|
||||||
if (!spans) throw new Error("PaneRow did not render before timeout");
|
if (!spans) throw new Error("PaneRow did not render before timeout");
|
||||||
@@ -163,14 +165,14 @@ describe("PaneRow layout", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Integration: the accent border was removed — no border or highlight ────
|
// ── Integration: the current pane carries muted left/right borders only ────
|
||||||
describe("PaneRow focus ring (borderless)", () => {
|
describe("PaneRow current-pane borders", () => {
|
||||||
// The current column no longer carries a focus ring: whatever `focused`
|
// The current column renders left/right edge glyphs (│) only — never a
|
||||||
// resolves to, no pane renders a border or an accent color. `focused`
|
// full box. `focused` gates scroll-following but never changes the border
|
||||||
// still gates scroll-following, but it must never surface a separator.
|
// (always muted — no accent ring), and parent/preview stay borderless.
|
||||||
const borderGlyphs = /[┌┐└┘─│]/;
|
const boxGlyphs = /[┌┐└┘─]/;
|
||||||
|
|
||||||
test("focused=true renders no borders and no accent ring", async () => {
|
test("focused=true renders left/right borders on the current pane only", async () => {
|
||||||
const { spans, destroy } = await renderPaneRow({
|
const { spans, destroy } = await renderPaneRow({
|
||||||
parent: null,
|
parent: null,
|
||||||
current: () => <text>ITEM</text>,
|
current: () => <text>ITEM</text>,
|
||||||
@@ -179,10 +181,13 @@ describe("PaneRow focus ring (borderless)", () => {
|
|||||||
});
|
});
|
||||||
cleanups.push(destroy);
|
cleanups.push(destroy);
|
||||||
|
|
||||||
expect(frameText(spans)).not.toMatch(borderGlyphs);
|
// 100-wide row splits as 20 / 40 / 40: the current pane's edges sit at
|
||||||
|
// columns 20 and 59. No horizontal or corner glyphs — edges only.
|
||||||
|
expect(borderColumns(spans)).toEqual([20, 59]);
|
||||||
|
expect(frameText(spans)).not.toMatch(boxGlyphs);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("focused=false renders no borders and no accent ring", async () => {
|
test("focused=false renders the same muted borders (no accent ring)", async () => {
|
||||||
const { spans, destroy } = await renderPaneRow({
|
const { spans, destroy } = await renderPaneRow({
|
||||||
parent: null,
|
parent: null,
|
||||||
current: () => <text>ITEM</text>,
|
current: () => <text>ITEM</text>,
|
||||||
@@ -191,7 +196,8 @@ describe("PaneRow focus ring (borderless)", () => {
|
|||||||
});
|
});
|
||||||
cleanups.push(destroy);
|
cleanups.push(destroy);
|
||||||
|
|
||||||
expect(frameText(spans)).not.toMatch(borderGlyphs);
|
expect(borderColumns(spans)).toEqual([20, 59]);
|
||||||
|
expect(frameText(spans)).not.toMatch(boxGlyphs);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("accepts an accessor for focused (reactive boolean)", async () => {
|
test("accepts an accessor for focused (reactive boolean)", async () => {
|
||||||
@@ -202,7 +208,8 @@ describe("PaneRow focus ring (borderless)", () => {
|
|||||||
focused: () => true,
|
focused: () => true,
|
||||||
});
|
});
|
||||||
cleanups.push(destroy);
|
cleanups.push(destroy);
|
||||||
expect(frameText(spans)).not.toMatch(borderGlyphs);
|
expect(borderColumns(spans)).toEqual([20, 59]);
|
||||||
|
expect(frameText(spans)).not.toMatch(boxGlyphs);
|
||||||
|
|
||||||
const { spans: spans2, destroy: destroy2 } = await renderPaneRow({
|
const { spans: spans2, destroy: destroy2 } = await renderPaneRow({
|
||||||
parent: null,
|
parent: null,
|
||||||
@@ -211,16 +218,18 @@ describe("PaneRow focus ring (borderless)", () => {
|
|||||||
focused: () => false,
|
focused: () => false,
|
||||||
});
|
});
|
||||||
cleanups.push(destroy2);
|
cleanups.push(destroy2);
|
||||||
expect(frameText(spans2)).not.toMatch(borderGlyphs);
|
expect(borderColumns(spans2)).toEqual([20, 59]);
|
||||||
|
expect(frameText(spans2)).not.toMatch(boxGlyphs);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("defaults to focused (still borderless, no accent ring)", async () => {
|
test("defaults to focused (same muted borders)", async () => {
|
||||||
const { spans, destroy } = await renderPaneRow({
|
const { spans, destroy } = await renderPaneRow({
|
||||||
parent: null,
|
parent: null,
|
||||||
current: () => <text>ITEM</text>,
|
current: () => <text>ITEM</text>,
|
||||||
preview: null,
|
preview: null,
|
||||||
});
|
});
|
||||||
cleanups.push(destroy);
|
cleanups.push(destroy);
|
||||||
expect(frameText(spans)).not.toMatch(borderGlyphs);
|
expect(borderColumns(spans)).toEqual([20, 59]);
|
||||||
|
expect(frameText(spans)).not.toMatch(boxGlyphs);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user