Skip to content

Commit c6fbcf8

Browse files
authored
fix(direction): preserve explicit bidiVisual=false in resolveTableDirection (SD-3141) (#3283)
resolveTableDirection previously emitted visualDirection only for the explicit-true case, conflating "no signal" with "explicit false." Per ECMA-376 section 17.4.1 + 17.17.4, w:bidiVisual w:val="0" is an explicit-false that can override a style-cascade true. Cascade-override semantics match paragraph w:bidi, which resolveParagraphDirection already handles symmetrically. The table resolver had a gap. Add the missing else-if branch so an explicit false emits visualDirection: 'ltr'. The helper getTableVisualDirection added in SD-3138 (PR #3279) already returned 'ltr' for the legacy fallback path; this aligns the resolver with the helper so Phase 1B (pm-adapter wiring of tableDirectionContext) does not silently regress the explicit-false case from 'ltr' to undefined. Tests: - rightToLeft: false -> visualDirection: 'ltr' - bidiVisual: false -> visualDirection: 'ltr' - empty input -> visualDirection: undefined - rtl wins when mixed signals present - 24 existing direction tests still pass.
1 parent a81be59 commit c6fbcf8

2 files changed

Lines changed: 37 additions & 0 deletions

File tree

packages/layout-engine/pm-adapter/src/direction/non-collapse.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,37 @@ describe('Non-collapse rule 2: table w:bidiVisual MUST NOT make cell paragraphs
7272
const paragraphContext = resolveParagraphDirection({}, sectionContext, cellContext);
7373
expect(paragraphContext.inlineDirection).toBeUndefined();
7474
});
75+
76+
// SD-3141: explicit `w:bidiVisual w:val="0"` is a real signal per §17.4.1 +
77+
// §17.17.4 and can override a style-cascade `true`. The resolver must
78+
// distinguish "no signal" (undefined) from "explicit false" (ltr), mirroring
79+
// the paragraph resolver's handling of `w:bidi w:val="0"`.
80+
81+
it('table with explicit rightToLeft:false → visualDirection is ltr', () => {
82+
const sectionContext = resolveSectionDirection(undefined);
83+
const tableContext = resolveTableDirection({ rightToLeft: false }, sectionContext);
84+
expect(tableContext.visualDirection).toBe('ltr');
85+
});
86+
87+
it('table with explicit bidiVisual:false → visualDirection is ltr', () => {
88+
const sectionContext = resolveSectionDirection(undefined);
89+
const tableContext = resolveTableDirection({ bidiVisual: false }, sectionContext);
90+
expect(tableContext.visualDirection).toBe('ltr');
91+
});
92+
93+
it('table with no signal → visualDirection stays undefined', () => {
94+
const sectionContext = resolveSectionDirection(undefined);
95+
const tableContext = resolveTableDirection({}, sectionContext);
96+
expect(tableContext.visualDirection).toBeUndefined();
97+
});
98+
99+
it('table with rightToLeft:true wins when both signals present', () => {
100+
// Mixed shape (one true, one false) should NOT happen in practice but
101+
// the rtl branch is checked first to keep this case explicit. SD-3141.
102+
const sectionContext = resolveSectionDirection(undefined);
103+
const tableContext = resolveTableDirection({ rightToLeft: true, bidiVisual: false }, sectionContext);
104+
expect(tableContext.visualDirection).toBe('rtl');
105+
});
75106
});
76107

77108
describe('Non-collapse rule 3: run-level w:rtl MUST NOT bubble up to paragraph', () => {

packages/layout-engine/pm-adapter/src/direction/resolveTableDirection.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,14 @@ export const resolveTableDirection = (
2727
parentSection: SectionDirectionContext,
2828
): TableDirectionContext => {
2929
let visualDirection: BaseDirection | undefined;
30+
// Mirror the paragraph resolver shape (resolveParagraphDirection): explicit
31+
// false is a real signal and must be distinguished from "no signal." Per
32+
// ECMA-376 §17.4.1 + §17.17.4, w:bidiVisual w:val="0" is an explicit-false
33+
// that can override a style-cascade true. SD-3141.
3034
if (tableProperties?.rightToLeft === true || tableProperties?.bidiVisual === true) {
3135
visualDirection = 'rtl';
36+
} else if (tableProperties?.rightToLeft === false || tableProperties?.bidiVisual === false) {
37+
visualDirection = 'ltr';
3238
}
3339
return { visualDirection, parentSection };
3440
};

0 commit comments

Comments
 (0)