[lexical-table] Bug Fix: Infer column header state from position during DOM import#8259
Conversation
When a <th> element has no scope attribute, infer header state from its position in the table instead of always defaulting to ROW: - First row <th> → ROW header state - First column <th> → COLUMN header state - First row + first column → BOTH This ensures that column headers imported from HTML are correctly propagated when inserting new rows via $insertTableRowAtSelection. Closes facebook#8090
Add tests verifying that <th> elements get correct header state based on position: BOTH for first-row first-column, COLUMN for first-column in non-first row, and ROW for non-first-column in thead.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| // Infer header state from position when no scope is specified | ||
| const parentRow = domNode_.parentElement; | ||
| const isFirstRow = | ||
| parentRow instanceof HTMLTableRowElement && |
There was a problem hiding this comment.
It's best to avoid using instanceof for DOM because it won't work across certain contexts which is why we have isHTMLElement and comparisons with tagName instead.
There was a problem hiding this comment.
makes sense, switched to isHTMLElement() with tagName checks
| const parentRow = domNode_.parentElement; | ||
| const isFirstRow = | ||
| parentRow instanceof HTMLTableRowElement && | ||
| (parentRow.parentElement?.nodeName.toLowerCase() === 'thead' || |
There was a problem hiding this comment.
For mostly historical reasons nullish coalescing isn't used in lexical outside of tests and the playground
There was a problem hiding this comment.
got it, replaced with an explicit isHTMLElement() guard
| headerState = TableCellHeaderStates.ROW; | ||
| // Infer header state from position when no scope is specified | ||
| const parentRow = domNode_.parentElement; | ||
| const isFirstRow = |
There was a problem hiding this comment.
This name is a bit misleading because if it's in a thead it may not be the first row
There was a problem hiding this comment.
good point, renamed to isInHeaderRow
| // Fallback for detached elements | ||
| if (headerState === TableCellHeaderStates.NO_STATUS) { | ||
| headerState = TableCellHeaderStates.ROW; | ||
| } |
There was a problem hiding this comment.
I don't think we need to worry about detached elements, the unit test can be updated to have semantically correct input with a table ancestor. The extension and legacy plugin for tables has transforms that would remove any TableCellNode or TableRowNode that doesn't have the correct TableNode -> TableRowNode -> TableCellNode hierarchy.
There was a problem hiding this comment.
updated the test to use a proper table > tr > th structure
|
The title of this PR doesn't match the template https://raw.githubusercontent.com/facebook/lexical/refs/heads/main/.github/pull_request_template.md |
|
pnpm run ci-check is failing and must pass for any PR before it can be accepted |
… structure - Replace instanceof HTMLTableRowElement with isHTMLElement() and tagName checks - Remove nullish coalescing in favor of explicit isHTMLElement guards - Rename isFirstRow to isInHeaderRow for clarity - Remove unnecessary comments - Update test to use proper table > tr > th structure instead of detached element
|
updated the PR body to match the template (Description + Before/After test plan). ci-check passes locally — tsc, flow, prettier, lint all clean. |
Description
When importing a
<th>element without ascopeattribute, the header state is now inferred from position instead of always defaulting toROW:<th>→ROWheader state<th>→COLUMNheader stateBOTHThis fixes the issue where column headers were not correctly propagated when inserting new rows via
$insertTableRowAtSelection, because<th>elements in header columns were incorrectly marked asROWinstead ofCOLUMN.Closes #8090
Test plan
Before
<th>elements imported via DOM always defaulted toROWheader type regardless of positionAfter
<th>elements:<th>in first row first column →BOTH<th>in first column of non-first row →COLUMN<th>in<thead>non-first column →ROW<th>→ROW(unchanged fallback)pnpm run ci-checkpasses