fix(runtime): default-Box screen-reader separator to space (Ink parity, G39) (#56)
A plain <Box> (no explicit flexDirection) lays out as a row — yoga's Box default is FLEX_DIRECTION_ROW (host/yoga.ts) — so its screen-reader children must be joined with a space, matching Ink (which hardcodes flexDirection:'row' in Box.tsx and derives the SR separator from style). Root cause: flexDirection is a pure yoga prop. node-ops applies it to the yoga node but does NOT mirror it into node.props (it is not in STYLE_PROPS), so the yoga row default was never reflected there. screen-reader.ts read node.props["flexDirection"], which was undefined for every live-rendered box (both default AND explicit column/row), wrongly defaulting the separator to "\n" in all cases. Fix: resolve the direction from the yoga node (preferring an explicit props.flexDirection for the direct-built unit fixtures), mirroring static-channel.ts's resolvedFlexDirection so both SR linearization paths derive the separator identically. Root keeps undefined → "\n" (Ink's column-default root). row-reverse/column-reverse reversal is unaffected. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,35 @@ function squashTextContent(node: TuiText | TuiVirtualText): string {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a box node's flexDirection as the string form the SR separator logic
|
||||
* compares against ("row" | "row-reverse" | "column" | "column-reverse").
|
||||
*
|
||||
* Prefer an explicit `props.flexDirection` when present (used by unit-test
|
||||
* fixtures that build nodes directly without a live yoga layout), otherwise read
|
||||
* the resolved direction back from the yoga node. node-ops applies flexDirection
|
||||
* to yoga but does NOT mirror it into `props` (it is not in STYLE_PROPS), and the
|
||||
* yoga node holds the Box default of row (host/yoga.ts sets FLEX_DIRECTION_ROW).
|
||||
* Mirrors static-channel.ts's resolvedFlexDirection so both SR linearization
|
||||
* paths derive the separator identically. (Ink parity, G39.)
|
||||
*/
|
||||
function resolveBoxFlexDirection(node: TuiBox): string {
|
||||
const fromProps = node.props["flexDirection"] as string | undefined;
|
||||
if (fromProps !== undefined) {
|
||||
return fromProps;
|
||||
}
|
||||
switch (node.yoga.getFlexDirection()) {
|
||||
case Yoga.FLEX_DIRECTION_ROW:
|
||||
return "row";
|
||||
case Yoga.FLEX_DIRECTION_ROW_REVERSE:
|
||||
return "row-reverse";
|
||||
case Yoga.FLEX_DIRECTION_COLUMN_REVERSE:
|
||||
return "column-reverse";
|
||||
default:
|
||||
return "column";
|
||||
}
|
||||
}
|
||||
|
||||
export interface ScreenReaderOptions {
|
||||
parentRole?: string;
|
||||
skipStaticElements?: boolean;
|
||||
@@ -73,9 +102,11 @@ export function renderScreenReaderOutput(node: TuiNode, options: ScreenReaderOpt
|
||||
if (node.type === "text") {
|
||||
output = squashTextContent(node);
|
||||
} else if (node.type === "box" || node.type === "root") {
|
||||
// Determine separator based on flex direction
|
||||
const flexDirection =
|
||||
node.type === "box" ? (node.props["flexDirection"] as string | undefined) : undefined;
|
||||
// Determine separator based on flex direction (resolved from yoga so the
|
||||
// Box default of row yields a space separator, matching Ink — see
|
||||
// resolveBoxFlexDirection / G39). Root keeps undefined → "\n" (Ink's column
|
||||
// default root).
|
||||
const flexDirection = node.type === "box" ? resolveBoxFlexDirection(node as TuiBox) : undefined;
|
||||
|
||||
const separator = flexDirection === "row" || flexDirection === "row-reverse" ? " " : "\n";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user