Commit 7364827
feat: rtl for tables (#3227)
* feat: rtl for tables
* fix(rtl-tables): emit logical resize delta and document right-edge index convention
Two review-fix changes on the table-RTL stability work:
1. resize-move / resize-end events now emit the LOGICAL delta (the
value applied to newWidths), restoring the pre-PR contract for
external listeners (logging, analytics, undo metadata). The visual
delta still lives on dragState.constrainedDelta for the in-flight
preview guideline at line 591. LTR consumers see no behavior change;
RTL inner-boundary consumers no longer see a sign-flipped payload.
2. Added a load-bearing comment at the right-edge boundary push
explaining why the reported columnIndex is 0 in RTL and
columns.length - 1 in LTR. Downstream consumers that need 'is this
the outer edge?' should key off type === 'right-edge', not on the
numeric column index.
* fix(rtl-tables): scope right-edge resize affectedColumns + add spec-coverage fixtures
Two changes:
1. dispatchResizeTransaction now passes isRightEdge and constrains
affectedColumns to [columnIndex] on right-edge drags. Pre-fix,
[columnIndex, columnIndex + 1] unconditionally rewrote the next
column's per-cell w:tcW with the grid value, destroying authored
divergent tcW on merged or width-overridden cells. LTR was unaffected
because columnIndex + 1 was past the last column; in RTL the
right-edge handle maps to column 0 so column 1 cells were real
targets. updateCellColwidths's tableCellProperties.cellWidth write
is unconditional, so the value goes through even when the new value
matches the old.
2. Four ECMA-376 §17.4-aligned Word fixtures + a spec-coverage
behavior suite:
- rtl-table-col1-tcw-divergent.docx (column 1 cells have tcW=2400
diverging from grid=1200; contract-guard test asserts a
right-edge drag leaves column 1 attrs byte-identical)
- rtl-table-gridspan.docx (§17.4.17 gridSpan=2 cell in row 0)
- rtl-table-vmerge.docx (§17.4.84 vMerge restart/continue across
3 rows; test currently fixme'd pending DOM-target investigation)
- rtl-table-tblind.docx (§17.4.51 tblInd indent; test asserts
table indents from the page's RIGHT edge in RTL per spec wording
'right edge in a right-to-left table')
- rtl-table-tcborders-startend.docx (§17.4.66 cell with logical
start/end borders; test fixme'd pending border DOM target)
Two of the four spec-coverage tests pass and pin the gridSpan +
tblInd RTL semantics. The other two are skipped with explicit TODO
comments naming what needs DOM-target investigation; the fixtures
are still useful substrate for visual-regression coverage.
* fix(rtl-tables): apply rtl default table alignment
* fix(rtl-tables): map tblBorders start/end to physical sides with rtl-aware fallback
* fix(rtl-tables): map tcMar start/end to physical padding for rtl tables
* fix(rtl-tables): preserve tcBorders start/end for rtl table cell border rendering
* fix(rtl-tables): render outer table left/right borders in separate mode without cellSpacin
* test(behavior): table tab navigation coverage
* fix(rtl-tables): scope rtl arrow cell navigation to bidiVisual tables
* fix(rtl-tables): fix RTL style padding, Shift+Arrow edge, and border typing
* test(tables): align rtl tblInd spec assertion with right-anchored bidiVisual behavior
* fix(rtl-tables): restore legacy border-style alias normalization + tighten tblInd test
Two follow-up fixes from round-2 review:
1. `normalizeLegacyBorderStyle` removal (e11a430) dropped the case
normalization for legacy persisted-cell border `val` strings.
`convertBorderSpec` passes `val` through unchanged, so lowercase/alias
forms (`dot`, `dotdash`, `dotdotdash`, `doublewave`) would have landed
on the painter unrecognized. Re-add the normalizer as a private helper
in table.ts and apply it in the legacy fallback path before
`extractCellBorders`. Adds a unit test covering the alias mapping.
2. `rtl-table-spec-coverage.spec.ts` tblInd assertion was weakened to
just `rightGap < leftGap` - true for any right-anchored RTL table,
regardless of whether the 1-inch tblInd was actually applied. Adds a
magnitude check (`leftGap - rightGap > 40px`) so the test fails if
tblInd is silently ignored.
The other round-2 reviewer findings were investigated and dropped:
- "tcBorders/tcMar double-mirror" was a false positive: per Wave 1a's
axis-separation rule, `bidiVisual` only flips cell ORDER, not the
paragraph inline direction. The pm-adapter and painter swaps are both
keyed off cell-paragraph direction, so they don't compound. Manual
comparison against Word on all 5 R2 fixtures confirms parity.
- "Legacy direction priority can flip explicit ltr" is unreachable per
the SD-2777 pm-adapter pairing invariant test.
- Arrow-nav assertion tightening would need targeted runtime verification
that's out of scope for this incremental commit; left for follow-up.
All 1828 pm-adapter tests pass including the new alias normalization test.
* fix(rtl-tables): keep start/end LTR-default in pm-adapter to avoid double-mirror
Per §17.4.33 "start (Table Cell Leading Edge Border) ... left for LTR
tables, right for RTL tables" and §17.4.12 "end (Trailing Edge) ... right
for LTR, left for RTL", the visual side for tcBorders / tblBorders /
tcMar start/end is determined by *table* direction (bidiVisual), not
paragraph direction.
Round-2 pre-swapping these in pm-adapter based on `isRtl` was correct
about the direction, but the DOM painter (renderTableRow.swapCellBordersLR,
renderTableFragment.applyBorder swap, renderTableCell.ts paddingLeft/Right
swap) also mirrors L<->R for RTL tables. Result: double-mirror — start
landed on the wrong visual edge.
Verified in the dev app loading rtl-table-tcborders-startend.docx:
- Before fix: cell at visual right had border-left=RED (start) and
border-right=BLUE (end) — wrong per §17.4.33.
- After fix: border-right=RED (start, visual right) and border-left=BLUE
(end, visual left) — matches Word and the spec.
Changes:
- extractTableBorders, extractCellBorders, extractCellPadding: drop the
isRtl-driven swap. Always map start -> .left, end -> .right (LTR
default). The `options` param is retained for backwards-compat callers
but isRtl is no longer read.
- table.ts: same fix for the resolved tableCellProperties.borders path.
- Updated borders.test.ts and table.test.ts to assert the new
LTR-default contract (and reference the painter as the single source
of RTL mirror).
The painter remains the single owner of RTL visual mirroring keyed off
the table's `bidiVisual` flag.
1828 pm-adapter tests + 1070 painter-dom tests pass.
* test(rtl-tables): expand RTL coverage for resize, tcBorders sides, gridSpan clicks
Three additions filling test-coverage gaps surfaced by audit:
TableResizeOverlay.test.js (was 1456 lines, ZERO rtl/bidiVisual mentions):
6 new tests under "RTL (bidiVisual) handling":
- parses rtl:true from data-table-boundaries metadata
- defaults rtl to false when omitted
- right-edge boundary in RTL targets column index 0 (not last column)
- right-edge in LTR targets columns.length-1 (regression guard)
- inner boundary visual X is mirrored from logical X in RTL
- right-edge X in RTL equals the table content width
Catches: silent column-0 cellWidth rewrites on right-edge drag,
inner boundary X-coord sign inversion, mismatched logical vs visual
handle position in bidiVisual tables.
rtl-table-tcborders-startend.spec.ts: tightened the width-only assertion
to also check start (RED) lands on cell border-right and end (BLUE) on
border-left per ECMA-376 §17.4.33 + §17.4.12. Width-only would have
silently passed a double-mirror regression.
rtl-table-spec-coverage.spec.ts:
- Activated the previously fixme'd tcBorders color test now that the
pm-adapter / painter mirror contract is settled. Fixed the DOM
target (cell wrapper is the absolutely-positioned div, no
.superdoc-table-cell class). Comment updated to cite the spec.
- Added click-target coverage for gridSpan=2 cell: clicking the
visually-rightmost merged cell lands the cursor inside that cell's
paragraph.
vMerge fixme left disabled (merged-span DOM target still not mapped)
and comment-range RTL coverage deferred to SD-2771 Wave 3.
12785 super-editor tests + 5 of 6 touched Playwright specs pass on
chromium (vMerge fixme skipped as designed).
* test(rtl-tables): add asymmetric tblBorders / tcMar / gridBefore-After fixtures
Three Word-native fixtures pinning ECMA-376 sides where the visual
direction flips with table direction:
- tblBorders/start (RED) end (BLUE) on visual right/left (section 17.4.38)
- tcMar/start (480 dxa) end (60 dxa) on visual right/left (section 17.4.41)
- gridBefore=1 and gridAfter=1 gaps on visual right/left (section 17.4.14/15)
Tests assert the spec-mandated side per row, so a regression that
double-mirrors or skips the mirror fails here instead of silently
producing the LTR rendering.
The tcMar test is fixme'd: importing tableCellProperties.cellMargins
preserves marginStart/marginEnd, but the style-engine projection
that writes top-level cellMargins only handles top/bottom. Test
comment points at the projection step (follow-up under SD-2771).
R2 corpus upload pending (wrangler token refresh).
* test(rtl-tables): fix tcMar spec reference to section 17.4.68
Direct cell w:tcMar is section 17.4.68 (Single Table Cell Margins,
child of w:tcPr). Section 17.4.41 is tblCellMar (Table Cell Margin
Exceptions, child of w:tblPrEx) - a different element.
Also reword the fixme comment to avoid paragraph-direction language.
tcMar start/end follow table direction (same governance as
tblBorders/start/end per section 17.4.33/12 and the leading-edge rule
in section 17.4.15), not paragraph bidi.
* test(rtl-tables): annotate tcMar fixme with AIDEV-NOTE issue anchor
Per comment-policy.md, temporary/workaround comments need an
AIDEV-NOTE anchor with a removal condition and issue id. Convert
the TODO at the tcMar fixme to AIDEV-NOTE: temporary with SD-2771
as the gating issue.
---------
Co-authored-by: Artem Nistuley <artem@superdoc.dev>
Co-authored-by: Caio Pizzol <caio@superdoc.dev>1 parent fd1a740 commit 7364827
38 files changed
Lines changed: 2198 additions & 101 deletions
File tree
- packages
- layout-engine
- contracts/src
- layout-engine/src
- painters/dom/src/table
- pm-adapter/src
- attributes
- converters
- super-editor/src/editors/v1
- components
- extensions/table/tableHelpers
- tests/import-export
- tests/behavior/tests/tables
- fixtures
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
620 | 620 | | |
621 | 621 | | |
622 | 622 | | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
623 | 628 | | |
624 | 629 | | |
625 | 630 | | |
626 | 631 | | |
| 632 | + | |
627 | 633 | | |
628 | 634 | | |
629 | 635 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
181 | 181 | | |
182 | 182 | | |
183 | 183 | | |
184 | | - | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
185 | 188 | | |
186 | | - | |
| 189 | + | |
187 | 190 | | |
188 | 191 | | |
189 | | - | |
190 | | - | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
191 | 200 | | |
192 | 201 | | |
193 | | - | |
194 | 202 | | |
195 | 203 | | |
196 | 204 | | |
| |||
Lines changed: 23 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
116 | 139 | | |
117 | 140 | | |
118 | 141 | | |
| |||
Lines changed: 34 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
135 | 135 | | |
136 | 136 | | |
137 | 137 | | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
138 | 172 | | |
139 | 173 | | |
140 | 174 | | |
| |||
Lines changed: 2 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
306 | 306 | | |
307 | 307 | | |
308 | 308 | | |
| 309 | + | |
309 | 310 | | |
310 | 311 | | |
311 | 312 | | |
| |||
337 | 338 | | |
338 | 339 | | |
339 | 340 | | |
340 | | - | |
| 341 | + | |
341 | 342 | | |
342 | 343 | | |
343 | 344 | | |
| |||
Lines changed: 129 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
399 | 399 | | |
400 | 400 | | |
401 | 401 | | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
402 | 442 | | |
403 | 443 | | |
404 | 444 | | |
| |||
452 | 492 | | |
453 | 493 | | |
454 | 494 | | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
455 | 536 | | |
456 | 537 | | |
457 | 538 | | |
| |||
536 | 617 | | |
537 | 618 | | |
538 | 619 | | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
539 | 668 | | |
540 | 669 | | |
541 | 670 | | |
| |||
0 commit comments