Skip to content

[lexical-table][lexical-playground] Feature: nested tables resize themselves if hasFitNestedTables: true#8183

Merged
etrepum merged 11 commits intofacebook:mainfrom
randal-atticus:feat-fit-nested-table-transform
Mar 5, 2026
Merged

[lexical-table][lexical-playground] Feature: nested tables resize themselves if hasFitNestedTables: true#8183
etrepum merged 11 commits intofacebook:mainfrom
randal-atticus:feat-fit-nested-table-transform

Conversation

@randal-atticus
Copy link
Copy Markdown
Contributor

@randal-atticus randal-atticus commented Feb 27, 2026

Description

#8097 implemented nested table fitting when inserting tables. However, beyond the initial insertion, it didn't handle the nested table itself changing width (1), or the surrounding cell changing width (2).

This PR changes the original implementation to a Node Transform, which handles (1).
To handle (2), the simplest approach is to mark all child tables as dirty when a column resize event occurs, so that the transform fires. I tried to do this with a Transform as well, but had issues getting it to terminate. Open to alternative ideas.

Test plan

Before

Screen.Recording.2026-02-27.at.6.16.29.pm.mov

After

Screen.Recording.2026-02-27.at.6.10.55.pm.mov

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Mar 5, 2026 8:00am
lexical-playground Ready Ready Preview, Comment Mar 5, 2026 8:00am

Request Review

@meta-cla meta-cla bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Feb 27, 2026
* @returns The horizontal insets of the cell, in pixels.
*/
function $calculateCellInsets(cell: TableCellNode) {
function $calculateCellHorizontalInsets(cell: TableCellNode) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of exporting this, and $getCellWidth, so that a similar pattern could be used for things like ImageNodes (basically, any node that can be resized)?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding exports is fine as long as the functions are suitable for being maintained as public API, or marked as @internal

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to expose $getCellWidth. This specific function ($calculateCellHorizontalInsets) is now a user-specified callback and no longer needs to be exported.

@etrepum
Copy link
Copy Markdown
Collaborator

etrepum commented Feb 27, 2026

Haven’t looked at the code but node transforms run before the DOM is updated so layout can’t be (correctly) calculated at that time. At best you can read the previously rendered DOM, but there are some big edge cases when you are working with a different version of the document.

@randal-atticus
Copy link
Copy Markdown
Contributor Author

randal-atticus commented Mar 2, 2026

Hmm, true. For my awareness, can you elaborate on those edge cases?

If the transform doesn't find the DOM node, at the moment it will just return zero insets, i.e. the resulting nested will be slightly too wide - unfortunate but not fatal.

original table, from main:
Screenshot 2026-03-02 at 11 03 12 am
pasted into this branch:
Screenshot 2026-03-02 at 11 03 22 am

If this won't work, some alternatives:

  • Replace $calculateCellHorizontalInsets with some property (tableCellHorizontalInsets: number) or user-provided callback ((cell: TableCellNode) => number). This API is fairly rough/unergonomic so I would probably end up moving this entire thing to another plugin (e.g. <NestedTableAutoResizerPlugin getTableCellInsets={...} />
  • Keep measuring the DOM but use an update listener, and accept the downside of the nested update.

@randal-atticus
Copy link
Copy Markdown
Contributor Author

randal-atticus commented Mar 2, 2026

I've gone ahead and added a property to the extension getCellHorizontalInsets that allows the user to provide a callback of the insets. It's a callback because different cells might have different styling (e.g. nested tables might have less padding, or users might have configurable table styling via state).

I don't love that you need to provide both properties for this to work, so it might make sense to pull this out to another plugin.

@etrepum
Copy link
Copy Markdown
Collaborator

etrepum commented Mar 2, 2026

The edge cases are basically that subsequent versions of the lexical document can have completely arbitrary changes from one to the next. Cells could be moved to different rows or resized, but if you're trying to measure their layout from a transform you're going to either get nothing or whatever the metrics were from the last version. In many cases this could be fine, but in cases where something happens that would affect the metrics of the cell after the new version is rendered you're going to have a stale read that wouldn't be fixed up until some future update that happens to trigger a transform of the same node.

If I were designing something like this I would probably use a mutation and/or update listener to handle it and maybe see if I could do most of it with direct DOM manipulation and not pushing the state back into the nodes (this kind of mechanism would be more responsive, since not all devices would necessarily measure things the same way). If not, then a cascading update to do that state update with history-merge or something like that (only if something actually needs the extra update cycle). Possibly some hybrid of the two approaches where the DOM updates are done eagerly and the render of the cascading update is mostly a no-op.

@randal-atticus
Copy link
Copy Markdown
Contributor Author

randal-atticus commented Mar 3, 2026

@etrepum Cool idea with direct DOM manipulation. Updated the PR accordingly (triggered by a mutation listener). One effect of this change is that, since the underlying colWidths are not modified, if you shrink then restore a parent cell, the inner table reverts to its original size.

Screen.Recording.2026-03-03.at.5.01.50.pm.mov

I also made it measure widths against the nearest root or shadow root, rather than just the nearest table cell. This makes tables also refit themselves inside things like the collapsible containers or column layouts in the Playground. In effect, that means maybe this feature is more aptly named hasFitTables instead?

Screen.Recording.2026-03-03.at.5.02.46.pm.mov

@randal-atticus randal-atticus marked this pull request as ready for review March 3, 2026 06:18
Copy link
Copy Markdown
Collaborator

@etrepum etrepum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small runtime compat issue that needs to be sorted but otherwise the code looks good and it works well in my testing at https://lexical-playground-2vjr0b74e-fbopensource.vercel.app/?hasNestedTables=true&tableHorizontalScroll=false&hasFitNestedTables=true

@etrepum etrepum added this pull request to the merge queue Mar 5, 2026
Merged via the queue into facebook:main with commit 62842a4 Mar 5, 2026
34 checks passed
@etrepum etrepum mentioned this pull request Mar 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants