Skip to content

DataViews: Render table bulk actions outside the table - #85250

Open
dinhtungdu wants to merge 9 commits into
WordPress:trunkfrom
dinhtungdu:try/table-bulk-actions-sibling
Open

dinhtungdu wants to merge 9 commits into
WordPress:trunkfrom
dinhtungdu:try/table-bulk-actions-sibling

Conversation

@dinhtungdu

@dinhtungdu dinhtungdu commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

What?

Follow-up to #82730. Move the table bulk actions toolbar outside the table while preserving its placement over the column headers. No intended visual change.

Why?

Remove the workaround that places the bulk actions toolbar in an extra table row and restore the expected table structure.

How?

Use a sticky sibling element and measure the header height with a resize observer.

Testing Instructions

  1. Open Site Editor → Pages in table view, in both classic and extensible editors.
  2. Select rows. Check bulk actions, Cancel, and horizontal/vertical scrolling at desktop and narrow widths.
  3. Change table density and confirm the toolbar stays aligned.

Testing Instructions for Keyboard

Tab to Select all and press Space. Focus moves to the toolbar checkbox. Tab through actions, then activate Cancel. Focus returns to Select all, and column sorting works again.

Use of AI Tools

Amp assisted with implementation, verification, and review.

Amp thread

dinhtungdu and others added 4 commits October 20, 2026 18:44
Keep the toolbar over the sticky header without adding a table row or cell. Measure the header with the existing resize observer so density and custom header sizes stay aligned.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1590a-46c4-73eb-a596-5d55213f3f75
Co-authored-by: Amp <amp@ampcode.com>
Fade the toolbar over a themed backing so covered column headers cannot show through. Extend delayed-loading browser coverage.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1590a-46c4-73eb-a596-5d55213f3f75
Co-authored-by: Amp <amp@ampcode.com>
Preserve both unreleased DataViews changelog entries.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1590a-46c4-73eb-a596-5d55213f3f75
Co-authored-by: Amp <amp@ampcode.com>
@github-actions github-actions Bot added the [Package] DataViews /packages/dataviews label Oct 21, 2026
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

Preview in WordPress Playground

🎉 Props

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: dinhtungdu <dinhtungdu@git.wordpress.org>
Co-authored-by: oandregal <oandregal@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Updated as activity occurs, without notifying anyone named here. Add the props-bot label to refresh.

dinhtungdu and others added 2 commits October 21, 2026 13:13
Cover fractional scroll bounds, locate bulk actions outside table cells, and link the changelog to this PR.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1590a-46c4-73eb-a596-5d55213f3f75
Co-authored-by: Amp <amp@ampcode.com>
@dinhtungdu
dinhtungdu marked this pull request as ready for review October 21, 2026 13:32
@oandregal oandregal added the [Type] Enhancement A suggestion for improvement. label Oct 23, 2026
oandregal and others added 3 commits October 23, 2026 10:55
The header height lived in React state seeded with 52px and updated from
the resize observer, which reports after the first paint. Toolbars shown
on mount (such as a selection restored from the URL) rendered at 52px for
a frame in every density. Measure the header in a layout effect when the
toolbar shows, and write the height to the overlay directly from the
observer so header resizes no longer re-render the table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The toolbar now sits outside the table, so a sticky offset that consumers
give the header no longer moved it: it stayed at the top of the scroll
area while the header sat lower. Copy the header's computed
inset-block-start onto the toolbar when it is placed.

Its z-index of 2 also lifted it above page elements at z-index 2 (such as
a sticky heading), which the header-contained toolbar stayed under.
Isolate the table so the header's z-index stays inside it, and drop the
toolbar back to z-index 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The story offset the sticky table header by 67px to clear its sticky
heading, but the header sticks inside the layout's own scroll area,
which never scrolls in this story. The header never stuck to the page
and instead sat 67px down, covering the first row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@oandregal

Copy link
Copy Markdown
Member

@dinhtungdu Thanks, moving the toolbar out of the table is a nice cleanup.

While testing it, I found three issues with the sibling placement and pushed fixes for you to look at (feel free to revert them):

  1. Wrong height on first paint. When the table loaded with rows already selected, the bulk actions toolbar briefly showed at the wrong height before resizing. Fixed in 4750208.
  2. It no longer followed an offset header. Inside <thead>, the toolbar moved with any sticky offset a consumer gave the header; as a sibling, it didn't. I reproduced by opening the Free Composition story and clicking Select all: the toolbar sat above the column headers instead of over them. It now uses the same offset as the header as of 4f88620.
  3. It painted over page elements at z-index: 2. For example, it covered the sticky heading in Free Composition when the page scrolled. Fixed in 4f88620 as well.

I also fixed the Free Composition story, which is broken on trunk too: its sticky header offset never worked and just hid the first row. Fixed in f571a2c.

const bulkActions = table.getByRole( 'cell', {
name: /items selected/,
} );
const bulkActions = page.locator( '.dataviews-bulk-actions' );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we'd generally prefer roles over classes, to avoid churning e2e test. Can we get this by role somehow?

) }
ref={ bulkActionsRef }
hidden={ ! showBulkActions }
inert={ inertValue( !! isLoading ) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If the element is inert while loading, do we also need aria-busy?

Comment on lines +239 to +241
expect( toolbar.getBoundingClientRect().width ).toBe(
scroller.clientWidth
);

@oandregal oandregal Oct 23, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We could make this test more robust by allowing some margin here. How about:

Suggested change
expect( toolbar.getBoundingClientRect().width ).toBe(
scroller.clientWidth
);
expect(
Math.abs(
toolbar.getBoundingClientRect().width - scroller.clientWidth
)
).toBeLessThanOrEqual( 0.5 );

@oandregal

Copy link
Copy Markdown
Member

This looks good and I'd approve. Though I want to hear your thoughts first about my suggestions (feel free to revert).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] DataViews /packages/dataviews [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants