Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
Size Change: +13 kB (+0.16%) Total Size: 7.93 MB 📦 View Changed
|
|
Yes please ❤️ |
|
Love the concept and the attempt! Some quick bugs/notes to address: Hard to select items in a row from right to left selecting.all.movMerge cell icon unclear That looks like a copy icon to me and I totally missed it when testing without watching your video. Merging cells loses text Not sure what should happen here but I'd expect both items of text to remain rather than losing it: merging.mov |
|
So when will we have sheets/excel functionality? 😜 |
t-hamano
left a comment
There was a problem hiding this comment.
Thanks for the PR! I've only tested it briefly, but I have a few observations to share.
1. Merging and unmerging cells. If a cell that is already merged is included in the selected cells, merging or unmerging cannot be performed. I believe that if the set of selected cells forms a visual rectangle, it should always be possible to merge them.
merge-unmerge.mp4
2. Add/Delete Row/Cell. Inserting or deleting cells in a row or column that contains merged cells can break the layout. In such cases, we need to add or subtract rowspan or colspan.
insert-delete-row.mp4
Accessibility. Ideally, cell merging should be achievable using only keyboard operations, but this is not currently possible.
keyboard-cell.mp4
Regarding item 1.2, I have previously addressed this with my own plugin. All the logic is consolidated here, so I hope it will be helpful.
https://github.com/t-hamano/flexible-table-block/blob/main/src/utils/table-state.ts
|
This is a pretty nice POC! To be clear, we would implement this as an improvement to Table v1, with migration etc? Otherwise it'd be a bit awkward to have two Table blocks. Would it make sense to be able to add child blocks inside cells? I think that's been requested before |
| if ( multiSelectedBlockClientIds.length > 0 ) { | ||
| const updates = getPerBlockAttributeUpdates( | ||
| attributes, | ||
| newAttributes, | ||
| getBlocksByClientId( multiSelectedBlockClientIds ) | ||
| ); | ||
| if ( updates ) { | ||
| updateBlockAttributes( | ||
| multiSelectedBlockClientIds, | ||
| updates, | ||
| { | ||
| uniqueByBlock: true, | ||
| } | ||
| ); | ||
| } | ||
| } else { | ||
| updateBlockAttributes( clientId, newAttributes ); | ||
| } |
There was a problem hiding this comment.
Interesting. Maybe something like this could be helpful with #41260. cc @tellthemachines
There was a problem hiding this comment.
Yeah, we should definitely extract the fix for that. Most of it is based on the existing PR a contributor did, there was just an extra bug in that PR.
There was a problem hiding this comment.
Most of the fix here is based on #80070, I've asked the contributor if I can rebase and add a couple of commits to that PR.
Delete row/column operated only on the anchor cell's own range, so a multi-cell selection deleted just one row or column despite the plural menu labels. The range now comes from the selection rectangle when a cell set is selected, matching the flat-structure behavior. Merged cells starting in the deleted range whose span extends beyond it are split first, so their surviving slots become real cells and the table stays rectangular, instead of vanishing with the deleted cell. The deletion geometry moves to tested pure functions, getColumnDeletionActions and getRowDeletionActions, computed on fresh placements after the splits.
Merge was blocked whenever the selection contained an already-merged cell. Now any rectangular same-section selection can merge: merged cells in the selection are split first, then the bounding rectangle is merged, matching how other table editors behave. Because the selection rectangle's bounds already account for spans, it always covers the whole of any selected merge, and the splits leave only single cells inside it. Cell removal is computed from fresh placements after the splits, since splitting adds cells the selection didn't know about.
Select row/column only selected the anchor cell's row or column despite the plural menu labels. When a cell set is selected, they now select every cell intersecting the selection rectangle's row or column range, restoring the flat-structure behavior.
Clicking into a cell selects it with a null initial position, and MULTI_SELECT_SET never updated that state, so useMultiSelection bailed and the native drag selection was never cleared for table cell selections. When a later operation (like merging cells) removed the selected cells from the DOM, the browser repaired the lingering range into a non-collapsed selection anchored in a surviving cell, and the selection observer re-interpreted it as a new cell multi-selection covering extra cells. Handle MULTI_SELECT_SET in the initialPosition reducer and pass an initial position for canvas drag selections in the selection observer, so the native selection is cleared when the block selection is established — the same behavior as a regular block multi-selection. Toolbar-triggered sets (Select row/column) omit the position, so focus still stays on the toolbar.
Merging discarded the contents of all but the top-left cell. The merged cell now keeps the contents of every cell in the rectangle, in document order, joined with line breaks. Cells without visible text are skipped so they don't add stray blank lines.
Move getTableCell and getTableCellRectangleClientIds out of the selection observer into a shared module built on a common layout builder, and add getTableCellNeighbor: the block-level cell adjacent to a cell in a given direction, span-aware via the occupancy grid. To be used for grid-aware keyboard selection.
multiSelectSet recorded the set's document-order first and last blocks as the selection start and end, losing the direction of a drag or keyboard extension. A rectangle's document-order ends also cannot represent off-diagonal ends (anchor top-right, focus bottom-left). Accept optional anchorClientId/focusClientId and record them instead. The selection observer passes the drag's start and end cells, so a later keyboard extension grows the end the user was actually dragging.
The shift+arrow paths in use-arrow-nav extended selections in block document order or left the browser to extend the native selection, which both move horizontally across a row. For a caret in a cell or a cell multi-selection, shift+arrow now moves the focus end to the adjacent cell on the table grid (span-aware via getTableCellNeighbor) and selects the rectangle from the anchor cell. At the table's edge, the caret path falls through to native handling so the selection can still extend out of the table.
16772dd to
9fb2566
Compare
The table-v2 block renders its nested structure with the standard useInnerBlocksProps flow, which already forwards options to UncontrolledInnerBlocks and registers the inner block settings there, so the hook and the restructured settings registration are no longer needed.
Its only consumer was useInnerBlockItems.save, removed with the hook. The table-v2 block serializes its nested structure in save.js.
The guard existed only because getRichTextValues polluted the innerBlocksPropsProvider global that useInnerBlockItems.save() read. Both are gone, and getRichTextValues recurses into inner blocks, so footnotes inside table cells are numbered normally.
Remove the comment-only stylesheets for the row and section blocks along with the block.json style fields referencing them, and drop the unused __ import from the table block.
TextControl is flagged by the use-recommended-components lint rule in favor of InputControl from @wordpress/ui. The new component passes native input props through and reports changes via onValueChange, which hands the same string value to the existing state setters.
|
Gave this another test drive. Great stuff. Just mentioning the following for future iterations: I was clicking around testing the "Edit table" dropdown, and tried to delete a "row" when a col was selected and nuked the table! Kapture.2026-08-26.at.18.22.29.mp4 |
Yep, it's a feature to try to mimic what excel/sheets does. The button text said "Delete rows", it deletes every row that the selection covers. Maybe it could say 'Delete rows 2-5' or something, or have a confirm step. It should probably go back to the placeholder when the entire table is deleted too! |
|
I've pushed some updates to the PR and reduced the number of new APIs that are being introduced by changing the technical approach. Also fixed some of the bugs mentioned. As requested by @youknowriad, the PR description has a summary of the API changes. |
|
In terms of how to move this forward. We can iterate on this PR as a proof of concept till we're happy with the general approach, but I wouldn't try to merge. After that I'd suggest smaller PRs to actually ship code, so that the API changes are easier to review.
|
This comment was marked as off-topic.
This comment was marked as off-topic.
Ah today I learned, thanks. If that's what users expect to happen, then 👍🏻 I was very click happy during testing |
|
Thanks for the replies and update to the description. It's a lot clearer and feels doable. The block selection changes seems like the biggest frameworky part. I know that non contiguous selection is also something that @ellatrix looked at in the past and it's a feature that can be shipped regardless of the table block. ctrl + click (then delete or else) could be a good feature on its own. So yeah, this feels like something that can land in parallel. Other than that, from my reading, there's no other API changes, maybe the "editingMode: disabled" is also something that could benefit other blocks (list item, column?) and I wonder if we need to think about this more. Like should it be a runtime or more declarative block API.. Glad to see this moving forward. |
|
Closing this now. I've made an iteration issue (Table block with inner blocks - iteration issue for WordPress 7.3-7.4) that we can use to discuss the implementation, at the moment it tracks what I mentioned in the PR description, I think WordPress 7.3 or 7.4 could be a good target. |

What?
A proof of concept of a table block that uses inner blocks. This isn't intended to be shippable, but the idea is to demonstrate the possibility, discuss whether it's worth it. If we think is worth it, we can discuss exactly what needs to be done to support such a thing, and potentially move forwards with a better implementation than in this PR 😄
The PR is heavily AI generated with human guidance, but not much code review (only high level discussion), so I don't expect others to review the code!
Features in this PR:
A couple of future features that this would become easier to implement:
thcells withscope=row)Why?
In the past there have been discussions about the possibilities for the table block (#18768). The block editor doesn't have built-in support for everything the table block needs to be able to use inner blocks, so those discussions were never explored. I think AI now allows us to explore a proof of concept with lower effort than previously, so it seems a great time to explore improving the table block.
How?
New blocks
Adds four new blocks:
<th>)Table Section and Row blocks exist purely as a way to output
thead,tbody,tfoot,trandthelements. The blocks haveblockEditingMode: disabledset permanently, so they're not visible in the editor. I personally found making these blocks selectable in the editor makes the table block overly complicated. I'm not sure there's a huge amount of value in allowing these blocks to be selected. For sections and rows (and columns), we could possibly support styling via elements instead of via a block. Another point is that it wouldn't be possible to support a column block, so only having row blocks feels unusual.Block selection
The block editor store has added support for non-contiguous
setselections. This is introduced to support selection of columns and rectangular ranges within a table. Broader support for non-contiguous block selections within the editor UI isn't added in this PR, but it could form the basis.There are also writing flow changes to support table selection natively. Whenever table cells are detected as blocks, mouse and keyboard multi-block selection uses a rectangular selection (creating a 'set' selection of
clientIds) , and accounts for situations where cells are merged. This could probably do with some iteration, it's here as a proof of concept currently.Block supports
editingMode: disabledMentioned above, this makes the row and section blocks not selectable in the editor.
listView: falsePrevents the table cell block from displaying in List View. Even a relatively modest 10x10 table has 100 cells, so I'm not sure there's value in displaying every cell, it quickly becomes overwhelming and not particularly useful. It's possible that there might be a better way to support the table block in List View, but it might be best as a separate exploration.
removable: falsePrevents table cells from being individually removable. They're only removable via delete column(s) / delete row(s)
movable: falsePrevents table cells from being individually movable.
duplicate: falsePrevents block duplication for table cells.
copy: falsePrevents block copy/paste for table cells.
Multi selection attribute update fix
The fix from #80070 is also in this PR, this should be shipped separately.
Testing Instructions
Screenshots or screencast
Kapture.2026-08-13.at.17.46.46.mp4
Use of AI Tools
OpenCode / Codex, Kimi K3, GLM-5.2