Skip to content

Try using responsive images in dataview grid layouts #70493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jul 31, 2025

Conversation

tellthemachines
Copy link
Contributor

@tellthemachines tellthemachines commented Jun 23, 2025

Why?

I noticed that the FeaturedImageView component in dataviews (see wp-admin/site-editor.php?p=%2Fpage) loads the full-sized image regardless of whether it's in list, table or grid mode. This means an unsuspecting user with multiple pages with featured images is potentially loading several megabytes of images even if they're displaying as tiny thumbnails.

This PR

  • Adds a mediaAppearance object prop to MediaField, with a maxImageWidth attribute, so we can pass in an appropriate image width from each layout type;
  • Rewrites the grid layout logic to be responsive based on grid item widths, instead of using container queries to force column counts, so that we can reliably know the maximum image size to pass as maxImageWidth;
  • The above step involved attaching the resizeObserverRef to dataviews context instead of passing it to the layout wrapper, so that it can be passed directly to each layout container as needed (currently only the grid layout needs it, and observing the container gives us a more accurate size than its wrapper, which might include a scrollbar)
  • Changes FeaturedImageView to use the maxImageWidth prop from MediaField to determine the appropriate size of image to serve, using srcset and sizes attributes on the img tag.

Testing Instructions

  1. Go to wp-admin/site-editor.php?p=%2Fpage
  2. Check that all views load correctly sized images. An interesting test is to try different device pixel ratios in the browser's responsive tools; in grid view different image sizes may load for different ratios.
  3. Try adjusting the preview size in grid view and check that it still resizes correctly.

Testing Instructions for Keyboard

Screenshots or screencast

Screenshot 2025-06-23 at 6 30 04 pm Screenshot 2025-06-23 at 6 31 18 pm

Copy link

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Type-related labels to choose from: [Type] Automated Testing, [Type] Breaking Change, [Type] Bug, [Type] Build Tooling, [Type] Code Quality, [Type] Copy, [Type] Developer Documentation, [Type] Enhancement, [Type] Experimental, [Type] Feature, [Type] New API, [Type] Task, [Type] Technical Prototype, [Type] Performance, [Type] Project Management, [Type] Regression, [Type] Security, [Type] WP Core Ticket, Backport from WordPress Core, Gutenberg Plugin.
  • Labels found: .

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

@tellthemachines tellthemachines added [Feature] DataViews Work surrounding upgrading and evolving views in the site editor and beyond [Type] Enhancement A suggestion for improvement. labels Jun 23, 2025
media?.media_details?.sizes?.thumbnail?.source_url || media?.source_url;
const url = view ? media?.source_url : thumbnailURL;

// @ts-ignore layout exists on ViewGrid; not sure what the problem is here.
Copy link
Member

Choose a reason for hiding this comment

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

It looks like it's because only ViewGrid has the previewSize prop.

I guess something like this would work:

	const columnNumberSetting =
		'grid' === view.type ? view.layout?.previewSize || 0 : 0;

@ramonjd
Copy link
Member

ramonjd commented Jun 24, 2025

This makes a big difference to my network traffic, by megabytes!

Before After
Screenshot 2025-06-24 at 11 29 01 am Screenshot 2025-06-24 at 11 31 26 am

I think it's a good change. I'd be keen to hear from folks better versed in Dataviews

Copy link

github-actions bot commented Jun 24, 2025

Flaky tests detected in 29cc5de.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/16614506144
📝 Reported issues:

@tellthemachines
Copy link
Contributor Author

I've updated this to have all dataview layouts pass a "sizes" string to the mediaField. That way, no field has to be responsible for knowing images sizes per breakpoint, and these can change within the dataviews package without causing breaking changes. It also means new layout types will work out of the box with any media fields that provide responsive image markup.

@tellthemachines tellthemachines marked this pull request as ready for review June 24, 2025 03:29
Copy link

github-actions bot commented Jun 24, 2025

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 props-bot label.

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: tellthemachines <isabel_brison@git.wordpress.org>
Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>
Co-authored-by: ellatrix <ellatrix@git.wordpress.org>
Co-authored-by: oandregal <oandregal@git.wordpress.org>
Co-authored-by: talldan <talldanwp@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>

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

sizes = `(max-width: 480px) 100vw, (max-width: 782px) ${
100 / usedPreviewSize
}vw, calc( (100vw - 400px) / ${ usedPreviewSize } )`;
}
Copy link
Member

Choose a reason for hiding this comment

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

Where do these calculations come from?
What happens for grid items other than images?
Why can't we use a resize observer, and then calculate sizes from that?

Copy link
Contributor Author

@tellthemachines tellthemachines Jun 25, 2025

Choose a reason for hiding this comment

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

Where do these calculations come from?

The image widths at each breakpoint are based on the layout grid CSS and take the UI into account as mentioned in the comments above.

What happens for grid items other than images?

Nothing 😄 this functionality only exists for img tags.

Why can't we use a resize observer, and then calculate sizes from that?

In principle I think it's better to avoid throwing a ton of JS at a problem that's been solved in native HTML already. Also, if we have to wait for the resize observer (which means waiting for layout to happen on screen) it's too late for the browser to fetch the right image at that point - either it has to load a backup image first (adding unnecessary downloads) or it'll be slow to appear on screen which is also not an optimal experience.

}: GridItemProps< Item > ) {
const { showTitle = true, showMedia = true, showDescription = true } = view;
const hasBulkAction = useHasAPossibleBulkAction( actions, item );
const id = getItemId( item );
const instanceId = useInstanceId( GridItem );
const isSelected = selection.includes( id );
const renderedMediaField = mediaField?.render ? (
<mediaField.render item={ item } />
<mediaField.render item={ item } sizes={ sizes } />
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for looking into this.

One advantage of using this approach instead of passing the view object is that we can use information that may be private to the view to calculate the size.

One downside is that we need to figure out is how to support any kind of media field (or the most common ones). That I know of, I think we've used images and iframes as media fields. Perhaps video would be useful as well, though I'm not convinced. We'd need to understand what each of them needs, so we can support them.

To discriminate among the different types of media fields, we need to evolve the field types available. For example, introducing image, iframe, etc. field types. That could signal which props to use.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One downside is that we need to figure out is how to support any kind of media field (or the most common ones). That I know of, I think we've used images and iframes as media fields. Perhaps video would be useful as well, though I'm not convinced. We'd need to understand what each of them needs, so we can support them.

Afaik the HTML spec only provides tools for serving images responsively, so the "sizes" attribute won't be of use to other media types. I think it's fine to implement this behaviour only for images though. Other media fields can just ignore the attribute.

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps video would be useful as well, though I'm not convinced. We'd need to understand what each of them needs, so we can support them.

I was thinking about this for the admin redesign, and was wondering about the possibility of a single, generic media props object. Example:

interface MediaFieldProps {
  sizes?: string; // for images
  aspectRatio?: string; // for videos, iframes
  maxWidth?: string | number; // for any media type
  maxHeight?: string | number;
  objectFit?: 'cover' | 'contain' | 'fill'; // for any media type
}

I think it'd scale better for different media types, e.g., can be expanded without breaking changes.

Then the media field render func can destructure only the props it needs.

It's also self-documenting through its TS interface

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I think the idea of making it more generic is a good one and it'll be more flexible. Maybe it could be called mediaAppearance.

I also personally prefer more specific types like VideoAppearance / ImageAppearance / BlockPreviewAppearance, as then the user can't specify an invalid combination of props, but this discussion has also come up on other PRs and there are mixed opinions about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that could work!

Copy link

github-actions bot commented Jun 25, 2025

Size Change: -148 B (-0.01%)

Total Size: 1.91 MB

Filename Size Change
build/edit-site/index.min.js 234 kB -105 B (-0.04%)
build/edit-site/posts-rtl.css 8.58 kB -24 B (-0.28%)
build/edit-site/posts.css 8.59 kB -23 B (-0.27%)
build/edit-site/style-rtl.css 14.7 kB -35 B (-0.24%)
build/edit-site/style.css 14.7 kB -34 B (-0.23%)
build/editor/index.min.js 126 kB +73 B (+0.06%)
ℹ️ View Unchanged
Filename Size
build-module/a11y/index.min.js 482 B
build-module/block-library/file/view.min.js 447 B
build-module/block-library/form/view.min.js 533 B
build-module/block-library/image/view.min.js 1.78 kB
build-module/block-library/navigation/view.min.js 1.19 kB
build-module/block-library/query/view.min.js 767 B
build-module/block-library/search/view.min.js 639 B
build-module/interactivity-router/full-page.min.js 565 B
build-module/interactivity-router/index.min.js 11.4 kB
build-module/interactivity/debug.min.js 17.5 kB
build-module/interactivity/index.min.js 13.9 kB
build/a11y/index.min.js 952 B
build/annotations/index.min.js 2.13 kB
build/api-fetch/index.min.js 2.4 kB
build/autop/index.min.js 2.12 kB
build/blob/index.min.js 579 B
build/block-directory/index.min.js 7.18 kB
build/block-directory/style-rtl.css 1.03 kB
build/block-directory/style.css 1.03 kB
build/block-editor/content-rtl.css 4.43 kB
build/block-editor/content.css 4.42 kB
build/block-editor/default-editor-styles-rtl.css 392 B
build/block-editor/default-editor-styles.css 392 B
build/block-editor/index.min.js 264 kB
build/block-editor/style-rtl.css 15.9 kB
build/block-editor/style.css 15.9 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 61 B
build/block-library/blocks/archives/style-rtl.css 90 B
build/block-library/blocks/archives/style.css 90 B
build/block-library/blocks/audio/editor-rtl.css 149 B
build/block-library/blocks/audio/editor.css 151 B
build/block-library/blocks/audio/style-rtl.css 132 B
build/block-library/blocks/audio/style.css 132 B
build/block-library/blocks/audio/theme-rtl.css 134 B
build/block-library/blocks/audio/theme.css 134 B
build/block-library/blocks/avatar/editor-rtl.css 115 B
build/block-library/blocks/avatar/editor.css 115 B
build/block-library/blocks/avatar/style-rtl.css 104 B
build/block-library/blocks/avatar/style.css 104 B
build/block-library/blocks/button/editor-rtl.css 265 B
build/block-library/blocks/button/editor.css 265 B
build/block-library/blocks/button/style-rtl.css 554 B
build/block-library/blocks/button/style.css 554 B
build/block-library/blocks/buttons/editor-rtl.css 291 B
build/block-library/blocks/buttons/editor.css 291 B
build/block-library/blocks/buttons/style-rtl.css 349 B
build/block-library/blocks/buttons/style.css 349 B
build/block-library/blocks/calendar/style-rtl.css 239 B
build/block-library/blocks/calendar/style.css 239 B
build/block-library/blocks/categories/editor-rtl.css 132 B
build/block-library/blocks/categories/editor.css 131 B
build/block-library/blocks/categories/style-rtl.css 152 B
build/block-library/blocks/categories/style.css 152 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 139 B
build/block-library/blocks/code/style.css 139 B
build/block-library/blocks/code/theme-rtl.css 122 B
build/block-library/blocks/code/theme.css 122 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 420 B
build/block-library/blocks/columns/style.css 420 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 124 B
build/block-library/blocks/comment-author-avatar/editor.css 124 B
build/block-library/blocks/comment-author-name/style-rtl.css 72 B
build/block-library/blocks/comment-author-name/style.css 72 B
build/block-library/blocks/comment-content/style-rtl.css 120 B
build/block-library/blocks/comment-content/style.css 120 B
build/block-library/blocks/comment-date/style-rtl.css 65 B
build/block-library/blocks/comment-date/style.css 65 B
build/block-library/blocks/comment-edit-link/style-rtl.css 70 B
build/block-library/blocks/comment-edit-link/style.css 70 B
build/block-library/blocks/comment-reply-link/style-rtl.css 71 B
build/block-library/blocks/comment-reply-link/style.css 71 B
build/block-library/blocks/comment-template/style-rtl.css 191 B
build/block-library/blocks/comment-template/style.css 191 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 168 B
build/block-library/blocks/comments-pagination/editor.css 168 B
build/block-library/blocks/comments-pagination/style-rtl.css 201 B
build/block-library/blocks/comments-pagination/style.css 201 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 842 B
build/block-library/blocks/comments/editor.css 842 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 637 B
build/block-library/blocks/cover/editor-rtl.css 631 B
build/block-library/blocks/cover/editor.css 631 B
build/block-library/blocks/cover/style-rtl.css 1.7 kB
build/block-library/blocks/cover/style.css 1.69 kB
build/block-library/blocks/details/editor-rtl.css 65 B
build/block-library/blocks/details/editor.css 65 B
build/block-library/blocks/details/style-rtl.css 86 B
build/block-library/blocks/details/style.css 86 B
build/block-library/blocks/embed/editor-rtl.css 331 B
build/block-library/blocks/embed/editor.css 331 B
build/block-library/blocks/embed/style-rtl.css 419 B
build/block-library/blocks/embed/style.css 419 B
build/block-library/blocks/embed/theme-rtl.css 133 B
build/block-library/blocks/embed/theme.css 133 B
build/block-library/blocks/file/editor-rtl.css 326 B
build/block-library/blocks/file/editor.css 326 B
build/block-library/blocks/file/style-rtl.css 278 B
build/block-library/blocks/file/style.css 278 B
build/block-library/blocks/footnotes/style-rtl.css 198 B
build/block-library/blocks/footnotes/style.css 197 B
build/block-library/blocks/form-input/editor-rtl.css 229 B
build/block-library/blocks/form-input/editor.css 229 B
build/block-library/blocks/form-input/style-rtl.css 349 B
build/block-library/blocks/form-input/style.css 349 B
build/block-library/blocks/form-submission-notification/editor-rtl.css 344 B
build/block-library/blocks/form-submission-notification/editor.css 341 B
build/block-library/blocks/form-submit-button/style-rtl.css 69 B
build/block-library/blocks/form-submit-button/style.css 69 B
build/block-library/blocks/freeform/editor-rtl.css 2.59 kB
build/block-library/blocks/freeform/editor.css 2.59 kB
build/block-library/blocks/gallery/editor-rtl.css 615 B
build/block-library/blocks/gallery/editor.css 616 B
build/block-library/blocks/gallery/style-rtl.css 1.84 kB
build/block-library/blocks/gallery/style.css 1.84 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 334 B
build/block-library/blocks/group/editor.css 334 B
build/block-library/blocks/group/style-rtl.css 103 B
build/block-library/blocks/group/style.css 103 B
build/block-library/blocks/group/theme-rtl.css 79 B
build/block-library/blocks/group/theme.css 79 B
build/block-library/blocks/heading/style-rtl.css 188 B
build/block-library/blocks/heading/style.css 188 B
build/block-library/blocks/html/editor-rtl.css 353 B
build/block-library/blocks/html/editor.css 354 B
build/block-library/blocks/image/editor-rtl.css 763 B
build/block-library/blocks/image/editor.css 763 B
build/block-library/blocks/image/style-rtl.css 1.6 kB
build/block-library/blocks/image/style.css 1.59 kB
build/block-library/blocks/image/theme-rtl.css 137 B
build/block-library/blocks/image/theme.css 137 B
build/block-library/blocks/latest-comments/style-rtl.css 355 B
build/block-library/blocks/latest-comments/style.css 354 B
build/block-library/blocks/latest-posts/editor-rtl.css 139 B
build/block-library/blocks/latest-posts/editor.css 138 B
build/block-library/blocks/latest-posts/style-rtl.css 520 B
build/block-library/blocks/latest-posts/style.css 520 B
build/block-library/blocks/list/style-rtl.css 107 B
build/block-library/blocks/list/style.css 107 B
build/block-library/blocks/loginout/style-rtl.css 61 B
build/block-library/blocks/loginout/style.css 61 B
build/block-library/blocks/media-text/editor-rtl.css 321 B
build/block-library/blocks/media-text/editor.css 320 B
build/block-library/blocks/media-text/style-rtl.css 543 B
build/block-library/blocks/media-text/style.css 542 B
build/block-library/blocks/more/editor-rtl.css 427 B
build/block-library/blocks/more/editor.css 427 B
build/block-library/blocks/navigation-link/editor-rtl.css 566 B
build/block-library/blocks/navigation-link/editor.css 568 B
build/block-library/blocks/navigation-link/style-rtl.css 192 B
build/block-library/blocks/navigation-link/style.css 191 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 295 B
build/block-library/blocks/navigation-submenu/editor.css 294 B
build/block-library/blocks/navigation/editor-rtl.css 2.23 kB
build/block-library/blocks/navigation/editor.css 2.24 kB
build/block-library/blocks/navigation/style-rtl.css 2.27 kB
build/block-library/blocks/navigation/style.css 2.26 kB
build/block-library/blocks/nextpage/editor-rtl.css 392 B
build/block-library/blocks/nextpage/editor.css 392 B
build/block-library/blocks/page-list/editor-rtl.css 356 B
build/block-library/blocks/page-list/editor.css 356 B
build/block-library/blocks/page-list/style-rtl.css 192 B
build/block-library/blocks/page-list/style.css 192 B
build/block-library/blocks/paragraph/editor-rtl.css 251 B
build/block-library/blocks/paragraph/editor.css 251 B
build/block-library/blocks/paragraph/style-rtl.css 341 B
build/block-library/blocks/paragraph/style.css 340 B
build/block-library/blocks/post-author-biography/style-rtl.css 74 B
build/block-library/blocks/post-author-biography/style.css 74 B
build/block-library/blocks/post-author-name/style-rtl.css 69 B
build/block-library/blocks/post-author-name/style.css 69 B
build/block-library/blocks/post-author/style-rtl.css 188 B
build/block-library/blocks/post-author/style.css 189 B
build/block-library/blocks/post-comments-count/style-rtl.css 72 B
build/block-library/blocks/post-comments-count/style.css 72 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 527 B
build/block-library/blocks/post-comments-form/style.css 528 B
build/block-library/blocks/post-comments-link/style-rtl.css 71 B
build/block-library/blocks/post-comments-link/style.css 71 B
build/block-library/blocks/post-content/style-rtl.css 61 B
build/block-library/blocks/post-content/style.css 61 B
build/block-library/blocks/post-date/style-rtl.css 62 B
build/block-library/blocks/post-date/style.css 62 B
build/block-library/blocks/post-excerpt/editor-rtl.css 71 B
build/block-library/blocks/post-excerpt/editor.css 71 B
build/block-library/blocks/post-excerpt/style-rtl.css 155 B
build/block-library/blocks/post-excerpt/style.css 155 B
build/block-library/blocks/post-featured-image/editor-rtl.css 722 B
build/block-library/blocks/post-featured-image/editor.css 720 B
build/block-library/blocks/post-featured-image/style-rtl.css 347 B
build/block-library/blocks/post-featured-image/style.css 347 B
build/block-library/blocks/post-navigation-link/style-rtl.css 215 B
build/block-library/blocks/post-navigation-link/style.css 214 B
build/block-library/blocks/post-template/style-rtl.css 414 B
build/block-library/blocks/post-template/style.css 414 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-time-to-read/style-rtl.css 70 B
build/block-library/blocks/post-time-to-read/style.css 70 B
build/block-library/blocks/post-title/style-rtl.css 162 B
build/block-library/blocks/post-title/style.css 162 B
build/block-library/blocks/preformatted/style-rtl.css 125 B
build/block-library/blocks/preformatted/style.css 125 B
build/block-library/blocks/pullquote/editor-rtl.css 133 B
build/block-library/blocks/pullquote/editor.css 133 B
build/block-library/blocks/pullquote/style-rtl.css 365 B
build/block-library/blocks/pullquote/style.css 365 B
build/block-library/blocks/pullquote/theme-rtl.css 176 B
build/block-library/blocks/pullquote/theme.css 176 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 121 B
build/block-library/blocks/query-pagination-numbers/editor.css 118 B
build/block-library/blocks/query-pagination/editor-rtl.css 154 B
build/block-library/blocks/query-pagination/editor.css 154 B
build/block-library/blocks/query-pagination/style-rtl.css 237 B
build/block-library/blocks/query-pagination/style.css 237 B
build/block-library/blocks/query-title/style-rtl.css 64 B
build/block-library/blocks/query-title/style.css 64 B
build/block-library/blocks/query-total/style-rtl.css 64 B
build/block-library/blocks/query-total/style.css 64 B
build/block-library/blocks/query/editor-rtl.css 404 B
build/block-library/blocks/query/editor.css 404 B
build/block-library/blocks/quote/style-rtl.css 238 B
build/block-library/blocks/quote/style.css 238 B
build/block-library/blocks/quote/theme-rtl.css 233 B
build/block-library/blocks/quote/theme.css 236 B
build/block-library/blocks/read-more/style-rtl.css 131 B
build/block-library/blocks/read-more/style.css 131 B
build/block-library/blocks/rss/editor-rtl.css 126 B
build/block-library/blocks/rss/editor.css 126 B
build/block-library/blocks/rss/style-rtl.css 284 B
build/block-library/blocks/rss/style.css 283 B
build/block-library/blocks/search/editor-rtl.css 199 B
build/block-library/blocks/search/editor.css 199 B
build/block-library/blocks/search/style-rtl.css 674 B
build/block-library/blocks/search/style.css 671 B
build/block-library/blocks/search/theme-rtl.css 113 B
build/block-library/blocks/search/theme.css 113 B
build/block-library/blocks/separator/editor-rtl.css 100 B
build/block-library/blocks/separator/editor.css 100 B
build/block-library/blocks/separator/style-rtl.css 248 B
build/block-library/blocks/separator/style.css 248 B
build/block-library/blocks/separator/theme-rtl.css 195 B
build/block-library/blocks/separator/theme.css 195 B
build/block-library/blocks/shortcode/editor-rtl.css 286 B
build/block-library/blocks/shortcode/editor.css 286 B
build/block-library/blocks/site-logo/editor-rtl.css 773 B
build/block-library/blocks/site-logo/editor.css 770 B
build/block-library/blocks/site-logo/style-rtl.css 218 B
build/block-library/blocks/site-logo/style.css 218 B
build/block-library/blocks/site-tagline/editor-rtl.css 87 B
build/block-library/blocks/site-tagline/editor.css 87 B
build/block-library/blocks/site-tagline/style-rtl.css 65 B
build/block-library/blocks/site-tagline/style.css 65 B
build/block-library/blocks/site-title/editor-rtl.css 85 B
build/block-library/blocks/site-title/editor.css 85 B
build/block-library/blocks/site-title/style-rtl.css 143 B
build/block-library/blocks/site-title/style.css 143 B
build/block-library/blocks/social-link/editor-rtl.css 314 B
build/block-library/blocks/social-link/editor.css 314 B
build/block-library/blocks/social-links/editor-rtl.css 339 B
build/block-library/blocks/social-links/editor.css 338 B
build/block-library/blocks/social-links/style-rtl.css 1.51 kB
build/block-library/blocks/social-links/style.css 1.51 kB
build/block-library/blocks/spacer/editor-rtl.css 346 B
build/block-library/blocks/spacer/editor.css 346 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table-of-contents/style-rtl.css 83 B
build/block-library/blocks/table-of-contents/style.css 83 B
build/block-library/blocks/table/editor-rtl.css 394 B
build/block-library/blocks/table/editor.css 394 B
build/block-library/blocks/table/style-rtl.css 640 B
build/block-library/blocks/table/style.css 639 B
build/block-library/blocks/table/theme-rtl.css 152 B
build/block-library/blocks/table/theme.css 152 B
build/block-library/blocks/tag-cloud/editor-rtl.css 92 B
build/block-library/blocks/tag-cloud/editor.css 92 B
build/block-library/blocks/tag-cloud/style-rtl.css 248 B
build/block-library/blocks/tag-cloud/style.css 248 B
build/block-library/blocks/template-part/editor-rtl.css 368 B
build/block-library/blocks/template-part/editor.css 368 B
build/block-library/blocks/template-part/theme-rtl.css 113 B
build/block-library/blocks/template-part/theme.css 113 B
build/block-library/blocks/term-description/style-rtl.css 126 B
build/block-library/blocks/term-description/style.css 126 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 165 B
build/block-library/blocks/text-columns/style.css 165 B
build/block-library/blocks/verse/style-rtl.css 98 B
build/block-library/blocks/verse/style.css 98 B
build/block-library/blocks/video/editor-rtl.css 413 B
build/block-library/blocks/video/editor.css 414 B
build/block-library/blocks/video/style-rtl.css 202 B
build/block-library/blocks/video/style.css 202 B
build/block-library/blocks/video/theme-rtl.css 134 B
build/block-library/blocks/video/theme.css 134 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/common-rtl.css 1.08 kB
build/block-library/common.css 1.08 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 11.4 kB
build/block-library/editor.css 11.4 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 229 kB
build/block-library/reset-rtl.css 472 B
build/block-library/reset.css 472 B
build/block-library/style-rtl.css 15 kB
build/block-library/style.css 15 kB
build/block-library/theme-rtl.css 715 B
build/block-library/theme.css 719 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/blocks/index.min.js 52.6 kB
build/commands/index.min.js 16.2 kB
build/commands/style-rtl.css 956 B
build/commands/style.css 953 B
build/components/index.min.js 249 kB
build/components/style-rtl.css 13.6 kB
build/components/style.css 13.6 kB
build/compose/index.min.js 12.8 kB
build/core-commands/index.min.js 3.09 kB
build/core-data/index.min.js 74.4 kB
build/customize-widgets/index.min.js 11 kB
build/customize-widgets/style-rtl.css 1.43 kB
build/customize-widgets/style.css 1.43 kB
build/data-controls/index.min.js 641 B
build/data/index.min.js 8.67 kB
build/date/index.min.js 18 kB
build/deprecated/index.min.js 458 B
build/dom-ready/index.min.js 325 B
build/dom/index.min.js 4.68 kB
build/edit-post/classic-rtl.css 577 B
build/edit-post/classic.css 578 B
build/edit-post/index.min.js 13.5 kB
build/edit-post/style-rtl.css 2.77 kB
build/edit-post/style.css 2.77 kB
build/edit-widgets/index.min.js 17.7 kB
build/edit-widgets/style-rtl.css 4.05 kB
build/edit-widgets/style.css 4.06 kB
build/editor/style-rtl.css 9.13 kB
build/editor/style.css 9.14 kB
build/element/index.min.js 4.82 kB
build/escape-html/index.min.js 537 B
build/format-library/index.min.js 8.17 kB
build/format-library/style-rtl.css 472 B
build/format-library/style.css 472 B
build/hooks/index.min.js 1.65 kB
build/html-entities/index.min.js 467 B
build/i18n/index.min.js 2.23 kB
build/is-shallow-equal/index.min.js 526 B
build/keyboard-shortcuts/index.min.js 1.31 kB
build/keycodes/index.min.js 1.46 kB
build/list-reusable-blocks/index.min.js 2.13 kB
build/list-reusable-blocks/style-rtl.css 847 B
build/list-reusable-blocks/style.css 848 B
build/media-utils/index.min.js 3.69 kB
build/notices/index.min.js 946 B
build/nux/index.min.js 1.62 kB
build/nux/style-rtl.css 767 B
build/nux/style.css 763 B
build/patterns/index.min.js 7.36 kB
build/patterns/style-rtl.css 687 B
build/patterns/style.css 685 B
build/plugins/index.min.js 1.86 kB
build/preferences-persistence/index.min.js 2.06 kB
build/preferences/index.min.js 2.9 kB
build/preferences/style-rtl.css 562 B
build/preferences/style.css 562 B
build/primitives/index.min.js 829 B
build/priority-queue/index.min.js 1.54 kB
build/private-apis/index.min.js 978 B
build/react-i18n/index.min.js 630 B
build/react-refresh-entry/index.min.js 9.47 kB
build/react-refresh-runtime/index.min.js 6.76 kB
build/redux-routine/index.min.js 2.7 kB
build/reusable-blocks/index.min.js 2.53 kB
build/reusable-blocks/style-rtl.css 255 B
build/reusable-blocks/style.css 255 B
build/rich-text/index.min.js 12.2 kB
build/router/index.min.js 5.44 kB
build/server-side-render/index.min.js 1.6 kB
build/shortcode/index.min.js 1.4 kB
build/style-engine/index.min.js 2.04 kB
build/token-list/index.min.js 581 B
build/url/index.min.js 3.97 kB
build/vendors/react-dom.min.js 41.7 kB
build/vendors/react-jsx-runtime.min.js 556 B
build/vendors/react.min.js 4.02 kB
build/viewport/index.min.js 965 B
build/vips/index.min.js 36.2 kB
build/warning/index.min.js 250 B
build/widgets/index.min.js 7.16 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.04 kB

compressed-size-action

@@ -193,6 +193,7 @@ export type DataFormControlProps< Item > = {

export type DataViewRenderFieldProps< Item > = {
item: Item;
sizes?: string;
Copy link
Member

Choose a reason for hiding this comment

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

It is worth adding a comment here as an explainer? I see the other types are documented.

}: GridItemProps< Item > ) {
const { showTitle = true, showMedia = true, showDescription = true } = view;
const hasBulkAction = useHasAPossibleBulkAction( actions, item );
const id = getItemId( item );
const instanceId = useInstanceId( GridItem );
const isSelected = selection.includes( id );
const renderedMediaField = mediaField?.render ? (
<mediaField.render item={ item } />
<mediaField.render item={ item } sizes={ sizes } />
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps video would be useful as well, though I'm not convinced. We'd need to understand what each of them needs, so we can support them.

I was thinking about this for the admin redesign, and was wondering about the possibility of a single, generic media props object. Example:

interface MediaFieldProps {
  sizes?: string; // for images
  aspectRatio?: string; // for videos, iframes
  maxWidth?: string | number; // for any media type
  maxHeight?: string | number;
  objectFit?: 'cover' | 'contain' | 'fill'; // for any media type
}

I think it'd scale better for different media types, e.g., can be expanded without breaking changes.

Then the media field render func can destructure only the props it needs.

It's also self-documenting through its TS interface

@@ -117,27 +117,15 @@
}

.dataviews-view-grid.dataviews-view-grid {
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
Copy link
Member

Choose a reason for hiding this comment

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

I think this is creating a regression in the default width of the images.

The default value of usedPreviewSize is undefined, but if you change it using the slider, you can't get back to the default state.

Kapture.2025-06-26.at.11.50.33.mp4

I guess it's no big deal, but it's odd not to be able to return to the original width as loaded.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm yeah I'll look into it.
I guess we could try changing usedPreviewSize to work with min-widths instead of number of columns, or we could just unset the value if it equates to default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess we could try changing usedPreviewSize to work with min-widths instead of number of columns

I just tried this and although it drastically simplifies the code I don't much like the experience of the un-stepped range control, because results feel a bit arbitrary. I'll play with this a bit further to see if I can keep the columns logic while using image widths in the background.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok this is now fixed and I've been able to retain most of the drastic simplification 😄

@tellthemachines tellthemachines force-pushed the try/dataviews-responsive-image-grid branch from b000498 to c482754 Compare July 1, 2025 02:06
className="dataviews-wrapper"
ref={ useMergeRefs( [ containerRef, resizeObserverRef ] ) }
>
<div className="dataviews-wrapper" ref={ containerRef }>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe that containerRef needs to be on this wrapper because its use is related to the scrolling behaviour of the wrapper.

The fact that this is the scrollable container for the layout is what makes it unsuitable to use with the resizeObserver, because the presence of a scrollbar will throw off the size of that actual layout. This is why I'm moving resizeObserverRef to context so it can be used directly in the grid layout.

const usedPreviewSize = view.layout?.previewSize;

// Calculate possible media sizes in grid for responsive images.
let sizes = '400px';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm now wondering whether it's really worth switching the value of sizes for different grid sizes, because it means that changing the grid size in the UI can download a whole new set of images 😅

We could just go with a fixed maximum size of 900px here, because with grid-template-columns using auto-fill, even the biggest grid size on a really big screen won't go over that.

Copy link
Member

Choose a reason for hiding this comment

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

Good rationale 👍

aria-busy={ isLoading }
ref={ resizeObserverRef }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned above, we need this ref here so DataViews can set the containerWidth in its context to the actual grid width, instead of the width of its wrapper that may or may not include scrollbars.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(Also I changed this to use a div instead of Grid so I could pass it the ref)

},
{
value: 430,
breakpoint: 588, // at minimum image width, 2 images display at this container size
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This tries to emulate the current behaviour, where setting the grid at its biggest size on a small container and resizing the window preserves the biggest size setting, even when more size options appear. This is probably not too important a point in user-facing terms though, because people don't go around resizing windows that much 😄

@container (max-width: 480px) {
padding-left: $grid-unit-30;
padding-right: $grid-unit-30;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I haven't changed this (only moved it around in the file) but this padding logic could probably be improved to be fully independent of sidebar width (which is specific to the site editor) and gutenberg media queries breakpoints.

<mediaField.render
item={ item }
field={ mediaField }
mediaAppearance={ mediaAppearance }
Copy link
Contributor

Choose a reason for hiding this comment

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

To be honest for me, right now, this is way too advanced/complex for a small gain. I'd rather pass the "view" object here as a "temporary API". I can't imagine this "mediaAppearance" API sticking forever either.

I would like to understand better what other kind of "rendering config" we need per field before having a more formal/complex API.

Copy link
Contributor

Choose a reason for hiding this comment

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

But now, that I think about itrender is also called on DataForm which doesn't have the concept of "view". So my question here, what would be the simplest common denominator thing we could pass here that allow us to achieve the use-case without complexity. For instance I want to avoid resize observers as much as possible personally, can we instead having

config={ config }

with the following type

interface config {
   size?: "small" | "medium" | "large"
}

and each view could decide to pass the right "size" as needed.

  • For instance the "media" field in table would always pass "small"
  • The "media" field in grid would always pass "medium"
  • DataForm would probably pass "small" too

I realize this is close to what you're proposing but there are two changes:

  • Simpler because there's no need for resize observers
  • More generic because it's not just about "media", later we might want other kind of "rendering config" for fields.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Simpler because there's no need for resize observers

The simpler config won't fix the need for resize observer, because that's already being used to build the grid layout. I just moved it to a different element because it isn't giving accurate numbers in its current location (the layout wrapper).

I did simplify the grid logic as much as I could so it's less dependent on breakpoints, but unfortunately we do still need to know the container size in order to display the right options in the preview size picker.

interface config {
size?: "small" | "medium" | "large"
}

small, medium, large won't really work here because the responsive image logic requires actual numeric values for its sizes attribute. The layout is responsible for setting the image size, so we should get that size from it. Otherwise we're putting the burden on the consumer to figure out what those sizes mean in px and produce a suitable number themselves, which might break if we ever change the layout.

We can call the object something more generic than mediaAppearance; it could be config or something else. But we do need those numeric widths for the responsive images to work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've updated to a generic config with a size property that takes a string with the size in px.

@tellthemachines tellthemachines force-pushed the try/dataviews-responsive-image-grid branch from c8fe2d1 to 0237139 Compare July 25, 2025 00:54
@tellthemachines
Copy link
Contributor Author

Update: I rebased and tested this against the Grouped Grid Layout that has since been merged and it seems to be working as expected.

@tellthemachines tellthemachines force-pushed the try/dataviews-responsive-image-grid branch from 0237139 to dfcc42d Compare July 29, 2025 07:13
.join( ', ' )
: undefined
}
sizes={ config?.size || '100vw' }
Copy link
Member

Choose a reason for hiding this comment

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

Given the usage we expect for this prop, I'd probably call it config.sizes, like the img attribute name — even if for now we just want to pass the target source size.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated!

@@ -30,6 +32,17 @@ export const FeaturedImageView = ( {
className="fields-controls__featured-image-image"
src={ url }
alt=""
srcSet={
Copy link
Member

Choose a reason for hiding this comment

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

In the Edit function for the FeaturedImage field we could also use this technique to prevent the full image being requested for such small needs 24px (note the quick edit button for media):

Screenshot 2025-07-29 at 20 18 30

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I totally missed the quick edit experiment! I think I'd rather address that in a follow-up though, because it's not clear what the best approach might be.

Arguably, we could just pass in the thumbnail URL directly instead of the full image, because FeaturedImageEdit defines its own image size. Whether we do that or use srcset and sizes, there's a side-effect we need to be aware of: the image will no longer show at its true aspect ratio, but in a square crop.

An alternative that preserves the aspect ratio is to use the medium size URL if it exists and default to full if not.

Copy link
Member

@oandregal oandregal left a comment

Choose a reason for hiding this comment

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

Hey, the public API changes in this PR (adding a config.sizes prop to field's render function) looks good to me.

Each layout provides a fixed target size for the image, and the field decides what to do with that info. I've tested that this works by setting a feature image (600kb) to a page and verifying that:

  • only the smallest image available from the srcset is downloaded in list and table layouts (150x150 in my case)
  • a larger image, but not the largest, is downloaded for the grid

so that looks good and it's working as expected.

I haven't been able to review today the other internal changes to resize observer, but I understand they're unrelated and aim to fix a bug.

@tellthemachines
Copy link
Contributor Author

Thanks for reviewing, André!

I haven't been able to review today the other internal changes to resize observer, but I understand they're unrelated and aim to fix a bug.

The changes to resize observer are actually related to the responsive image feature: it's a bug that the current grid logic doesn't use the correct size, but these changes also ensure that the max image size never goes over 900px. For clarity, I wasn't aiming specifically for that number; it just happens to be the max size that the images can get to after I adjusted the grid to be more responsive at super-wide screen sizes. What matters is that we can know what the max size is, so we can use it in the responsive image logic.

},
{
value: 350,
breakpoint: 1636, // at minimum image width, 6 images display at this container size
Copy link
Member

@oandregal oandregal Jul 30, 2025

Choose a reason for hiding this comment

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

I've tried this on a large screen (2560px), way past the max breakpoint (1636). With these changes, the images aren't that big and the space looks more balanced. Of course, I'm not designer, so others may have different thoughts 😅

In any case, I like that the mapping from a minimum breakpoint to a minimum image size is clearer and easier to tweak.

Before After
Min width (6 images before, 8 after) Screenshot 2025-07-30 at 10 20 43 Screenshot 2025-07-30 at 10 20 30
Max width (3 images before, 5 after) Screenshot 2025-07-30 at 10 20 17 Screenshot 2025-07-30 at 10 20 02

: {};
const usedPreviewSize = view.layout?.previewSize;
// This is the maximum width that an image can achieve in the grid.
const size = '900px';
Copy link
Member

Choose a reason for hiding this comment

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

As a gift to our future selves, would you expand why this is the maximum width an image can achieve in the grid?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure!

@tellthemachines tellthemachines merged commit b860887 into trunk Jul 31, 2025
60 checks passed
@tellthemachines tellthemachines deleted the try/dataviews-responsive-image-grid branch July 31, 2025 00:07
@github-actions github-actions bot added this to the Gutenberg 21.4 milestone Jul 31, 2025
@youknowriad
Copy link
Contributor

I would love CHANGELOG and README updates about this PR.

adamsilverstein pushed a commit to adamsilverstein/gutenberg that referenced this pull request Jul 31, 2025
* Try using responsive images in dataview grid layouts

* Move determination of sizes to dataviews

* Simplify grid logic and sizes

* use range of image widths for grid size picker

* Make picker discrete and improve breakpoints

* remove unused prop

* ref no worky again

* smol improvement

* Update to use an object instead of sizes string and fix grid size at max

* Adjust table max img width

* Change `mediaAppearance` to the more generic `config`

* Change `size` to `sizes`

* Explain magic number in comment.
tellthemachines added a commit that referenced this pull request Aug 1, 2025
@tellthemachines
Copy link
Contributor Author

Sorry folks, I forgot about the props in the commit message 🤦

oandregal pushed a commit that referenced this pull request Aug 1, 2025
oandregal added a commit that referenced this pull request Aug 1, 2025
Co-authored-by: tellthemachines <isabel_brison@git.wordpress.org>
Co-authored-by: oandregal <oandregal@git.wordpress.org>
Co-authored-by: talldan <talldanwp@git.wordpress.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] DataViews Work surrounding upgrading and evolving views in the site editor and beyond [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants