Skip to content
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

Try adding layout classnames to inner block wrapper #44600

Merged
merged 15 commits into from
Oct 26, 2022

Conversation

tellthemachines
Copy link
Contributor

@tellthemachines tellthemachines commented Sep 30, 2022

Why?

As a prequel to #43140 we need a way to attach layout-related classnames to the inner block wrapper, instead of the outer (they are not always the same).

How?

Does some searching and matching to work out where the inner wrapper classnames are, and attach layout classes to those.

Testing Instructions

I added layout to the Cover block so we can test this is working. It doesn't have to stay as part of this PR; we can just focus on the mechanism for adding classes and then deal with block-specific stuff later. But for now this can be tested by adding a Cover block with some content and testing its layout controls.

  1. Add Cover block with a couple blocks inside;
  2. Change controls in "Layout" section of block sidebar;
  3. Check that layout classnames are attached to inner block wrapper on both editor and front end;
  4. Check that other blocks with layout (e.g. Group, Buttons, Columns...) are still working correctly.

Screenshots or screencast

@tellthemachines tellthemachines added the [Feature] Layout Layout block support, its UI controls, and style output. label Sep 30, 2022
@andrewserong
Copy link
Contributor

Thanks for digging into this one! It looks like the WP_HTML_Tag_Processor class has been merged as of #42485 — would it be worth trying that out for this case, to avoid adding an additional regex? That class isn't slated to land until 6.2, so assuming that this feature is also for 6.2, I was wondering if it might be a good opportunity to see if the class holds up for this use case 🤔

@tellthemachines
Copy link
Contributor Author

the WP_HTML_Tag_Processor class has been merged as of #42485 — would it be worth trying that out for this case, to avoid adding an additional regex?

I'll look into it! We should be able to use it for the part where we add the layout classes. I doubt if it'll work for identifying the inner wrapper though, as we're going by its position in the $inner_content array. We have no way of knowing what classnames or tags it might have, which means there's nothing for WP_HTML_Tag_Processor to work with there.

@andrewserong
Copy link
Contributor

We have no way of knowing what classnames or tags it might have, which means there's nothing for WP_HTML_Tag_Processor to work with there.

Ah, good point, yes, I forgot about the problem of flagging where the wrapper should ideally be!

@tellthemachines tellthemachines self-assigned this Oct 4, 2022
@tellthemachines tellthemachines force-pushed the try/layout-classnames-on-inner-wrapper branch from f979e16 to d358f0b Compare October 5, 2022 06:40
@tellthemachines
Copy link
Contributor Author

Update: got it working on the editor, but there's some duplication of classnames if the inner wrapper isn't the same as the outer one. I found we can't rely on InnerBlocks to add the classnames for all container blocks, because some don't use it (e.g. Gallery) and others only use it in certain views (e.g. PostContent). But I haven't found any reliable way to remove classes from the outer wrapper if they are applied to the inner already. Ideas appreciated!

Still todo: look into using WP_HTML_Tag_Processor.

@github-actions
Copy link

github-actions bot commented Oct 6, 2022

Size Change: +116 B (0%)

Total Size: 1.28 MB

Filename Size Change
build/block-editor/index.min.js 169 kB +54 B (0%)
build/block-library/index.min.js 192 kB +62 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 982 B
build/annotations/index.min.js 2.76 kB
build/api-fetch/index.min.js 2.26 kB
build/autop/index.min.js 2.14 kB
build/blob/index.min.js 475 B
build/block-directory/index.min.js 7.09 kB
build/block-directory/style-rtl.css 990 B
build/block-directory/style.css 991 B
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-editor/style-rtl.css 15.8 kB
build/block-editor/style.css 15.8 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 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 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 122 B
build/block-library/blocks/audio/style.css 122 B
build/block-library/blocks/audio/theme-rtl.css 126 B
build/block-library/blocks/audio/theme.css 126 B
build/block-library/blocks/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 84 B
build/block-library/blocks/avatar/style.css 84 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 482 B
build/block-library/blocks/button/editor.css 482 B
build/block-library/blocks/button/style-rtl.css 532 B
build/block-library/blocks/button/style.css 532 B
build/block-library/blocks/buttons/editor-rtl.css 337 B
build/block-library/blocks/buttons/editor.css 337 B
build/block-library/blocks/buttons/style-rtl.css 332 B
build/block-library/blocks/buttons/style.css 332 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 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 100 B
build/block-library/blocks/categories/style.css 100 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 121 B
build/block-library/blocks/code/style.css 121 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 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 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-content/style-rtl.css 92 B
build/block-library/blocks/comment-content/style.css 92 B
build/block-library/blocks/comment-template/style-rtl.css 199 B
build/block-library/blocks/comment-template/style.css 198 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 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 840 B
build/block-library/blocks/comments/editor.css 839 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 636 B
build/block-library/blocks/cover/editor-rtl.css 612 B
build/block-library/blocks/cover/editor.css 613 B
build/block-library/blocks/cover/style-rtl.css 1.57 kB
build/block-library/blocks/cover/style.css 1.55 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 410 B
build/block-library/blocks/embed/style.css 410 B
build/block-library/blocks/embed/theme-rtl.css 126 B
build/block-library/blocks/embed/theme.css 126 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 253 B
build/block-library/blocks/file/style.css 254 B
build/block-library/blocks/file/view.min.js 346 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 948 B
build/block-library/blocks/gallery/editor.css 950 B
build/block-library/blocks/gallery/style-rtl.css 1.53 kB
build/block-library/blocks/gallery/style.css 1.53 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 394 B
build/block-library/blocks/group/editor.css 394 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/html/editor-rtl.css 327 B
build/block-library/blocks/html/editor.css 329 B
build/block-library/blocks/image/editor-rtl.css 880 B
build/block-library/blocks/image/editor.css 880 B
build/block-library/blocks/image/style-rtl.css 627 B
build/block-library/blocks/image/style.css 630 B
build/block-library/blocks/image/theme-rtl.css 126 B
build/block-library/blocks/image/theme.css 126 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 213 B
build/block-library/blocks/latest-posts/editor.css 212 B
build/block-library/blocks/latest-posts/style-rtl.css 463 B
build/block-library/blocks/latest-posts/style.css 462 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 507 B
build/block-library/blocks/media-text/style.css 505 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 705 B
build/block-library/blocks/navigation-link/editor.css 703 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 296 B
build/block-library/blocks/navigation-submenu/editor.css 295 B
build/block-library/blocks/navigation/editor-rtl.css 2.02 kB
build/block-library/blocks/navigation/editor.css 2.03 kB
build/block-library/blocks/navigation/style-rtl.css 2.17 kB
build/block-library/blocks/navigation/style.css 2.16 kB
build/block-library/blocks/navigation/view-modal.min.js 2.78 kB
build/block-library/blocks/navigation/view.min.js 443 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 174 B
build/block-library/blocks/paragraph/editor.css 174 B
build/block-library/blocks/paragraph/style-rtl.css 279 B
build/block-library/blocks/paragraph/style.css 281 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 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 493 B
build/block-library/blocks/post-comments-form/style.css 493 B
build/block-library/blocks/post-date/style-rtl.css 61 B
build/block-library/blocks/post-date/style.css 61 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 586 B
build/block-library/blocks/post-featured-image/editor.css 584 B
build/block-library/blocks/post-featured-image/style-rtl.css 315 B
build/block-library/blocks/post-featured-image/style.css 315 B
build/block-library/blocks/post-navigation-link/style-rtl.css 153 B
build/block-library/blocks/post-navigation-link/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 282 B
build/block-library/blocks/post-template/style.css 282 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-title/style-rtl.css 100 B
build/block-library/blocks/post-title/style.css 100 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 135 B
build/block-library/blocks/pullquote/editor.css 135 B
build/block-library/blocks/pullquote/style-rtl.css 326 B
build/block-library/blocks/pullquote/style.css 325 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 282 B
build/block-library/blocks/query-pagination/style.css 278 B
build/block-library/blocks/query-title/style-rtl.css 63 B
build/block-library/blocks/query-title/style.css 63 B
build/block-library/blocks/query/editor-rtl.css 439 B
build/block-library/blocks/query/editor.css 439 B
build/block-library/blocks/quote/style-rtl.css 213 B
build/block-library/blocks/quote/style.css 213 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 409 B
build/block-library/blocks/search/style.css 406 B
build/block-library/blocks/search/theme-rtl.css 114 B
build/block-library/blocks/search/theme.css 114 B
build/block-library/blocks/separator/editor-rtl.css 146 B
build/block-library/blocks/separator/editor.css 146 B
build/block-library/blocks/separator/style-rtl.css 234 B
build/block-library/blocks/separator/style.css 234 B
build/block-library/blocks/separator/theme-rtl.css 194 B
build/block-library/blocks/separator/theme.css 194 B
build/block-library/blocks/shortcode/editor-rtl.css 464 B
build/block-library/blocks/shortcode/editor.css 464 B
build/block-library/blocks/site-logo/editor-rtl.css 490 B
build/block-library/blocks/site-logo/editor.css 490 B
build/block-library/blocks/site-logo/style-rtl.css 203 B
build/block-library/blocks/site-logo/style.css 203 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 116 B
build/block-library/blocks/site-title/editor.css 116 B
build/block-library/blocks/site-title/style-rtl.css 57 B
build/block-library/blocks/site-title/style.css 57 B
build/block-library/blocks/social-link/editor-rtl.css 184 B
build/block-library/blocks/social-link/editor.css 184 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.4 kB
build/block-library/blocks/social-links/style.css 1.39 kB
build/block-library/blocks/spacer/editor-rtl.css 322 B
build/block-library/blocks/spacer/editor.css 322 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/editor-rtl.css 494 B
build/block-library/blocks/table/editor.css 494 B
build/block-library/blocks/table/style-rtl.css 611 B
build/block-library/blocks/table/style.css 609 B
build/block-library/blocks/table/theme-rtl.css 190 B
build/block-library/blocks/table/theme.css 190 B
build/block-library/blocks/tag-cloud/style-rtl.css 251 B
build/block-library/blocks/tag-cloud/style.css 253 B
build/block-library/blocks/template-part/editor-rtl.css 235 B
build/block-library/blocks/template-part/editor.css 235 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 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 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 691 B
build/block-library/blocks/video/editor.css 694 B
build/block-library/blocks/video/style-rtl.css 174 B
build/block-library/blocks/video/style.css 174 B
build/block-library/blocks/video/theme-rtl.css 126 B
build/block-library/blocks/video/theme.css 126 B
build/block-library/classic-rtl.css 162 B
build/block-library/classic.css 162 B
build/block-library/common-rtl.css 1.02 kB
build/block-library/common.css 1.02 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.2 kB
build/block-library/editor.css 11.2 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/style-rtl.css 12.3 kB
build/block-library/style.css 12.3 kB
build/block-library/theme-rtl.css 719 B
build/block-library/theme.css 722 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 49.9 kB
build/components/index.min.js 202 kB
build/components/style-rtl.css 11.3 kB
build/components/style.css 11.3 kB
build/compose/index.min.js 12.2 kB
build/core-data/index.min.js 15.5 kB
build/customize-widgets/index.min.js 11.3 kB
build/customize-widgets/style-rtl.css 1.38 kB
build/customize-widgets/style.css 1.38 kB
build/data-controls/index.min.js 653 B
build/data/index.min.js 8.08 kB
build/date/index.min.js 32.1 kB
build/deprecated/index.min.js 507 B
build/dom-ready/index.min.js 324 B
build/dom/index.min.js 4.7 kB
build/edit-navigation/index.min.js 16.1 kB
build/edit-navigation/style-rtl.css 3.99 kB
build/edit-navigation/style.css 4 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/index.min.js 31.8 kB
build/edit-post/style-rtl.css 7.13 kB
build/edit-post/style.css 7.13 kB
build/edit-site/index.min.js 58 kB
build/edit-site/style-rtl.css 8.37 kB
build/edit-site/style.css 8.35 kB
build/edit-widgets/index.min.js 16.7 kB
build/edit-widgets/style-rtl.css 4.34 kB
build/edit-widgets/style.css 4.34 kB
build/editor/index.min.js 43.4 kB
build/editor/style-rtl.css 3.62 kB
build/editor/style.css 3.61 kB
build/element/index.min.js 4.68 kB
build/escape-html/index.min.js 537 B
build/experiments/index.min.js 868 B
build/format-library/index.min.js 6.95 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.64 kB
build/html-entities/index.min.js 448 B
build/i18n/index.min.js 3.77 kB
build/is-shallow-equal/index.min.js 527 B
build/keyboard-shortcuts/index.min.js 1.78 kB
build/keycodes/index.min.js 1.83 kB
build/list-reusable-blocks/index.min.js 2.13 kB
build/list-reusable-blocks/style-rtl.css 835 B
build/list-reusable-blocks/style.css 835 B
build/media-utils/index.min.js 2.93 kB
build/notices/index.min.js 963 B
build/nux/index.min.js 2.06 kB
build/nux/style-rtl.css 732 B
build/nux/style.css 728 B
build/plugins/index.min.js 1.94 kB
build/preferences-persistence/index.min.js 2.22 kB
build/preferences/index.min.js 1.33 kB
build/primitives/index.min.js 933 B
build/priority-queue/index.min.js 1.58 kB
build/react-i18n/index.min.js 696 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.74 kB
build/reusable-blocks/index.min.js 2.21 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 10.6 kB
build/server-side-render/index.min.js 1.77 kB
build/shortcode/index.min.js 1.53 kB
build/style-engine/index.min.js 1.46 kB
build/token-list/index.min.js 644 B
build/url/index.min.js 3.61 kB
build/vendors/inert-polyfill.min.js 2.48 kB
build/vendors/react-dom.min.js 38.5 kB
build/vendors/react.min.js 4.34 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 268 B
build/widgets/index.min.js 7.21 kB
build/widgets/style-rtl.css 1.18 kB
build/widgets/style.css 1.19 kB
build/wordcount/index.min.js 1.06 kB

compressed-size-action

@@ -231,7 +233,8 @@ function BlockListBlock( {
'is-content-block': hasContentLockedParent && isContentBlock,
},
dataAlign && themeSupportsLayout && `align${ dataAlign }`,
className
className,
layoutClassNames
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to add these here for blocks that don't use InnerBlocks to render their children (the only examples I could find of this in Core are Gallery and PostContent when viewed in non-editable mode in the site editor, but there may be more). Unfortunately that means that if the block's inner wrapper is different from its outer, the layout classnames will be duplicated across both wrappers in the editor. There don't seem to be any consequences to this, as the blocks that currently have multiple wrappers and layout (e.g. Navigation) are already handling the styles being applied to their outer wrapper. But it would be nice to fix if anyone has ideas!

Copy link
Contributor

Choose a reason for hiding this comment

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

Because the constrained layout and root padding features have rules that target the direct child of the layout classname, I think we might need to come up with a way where the layout classes are only applied to the deepest node (that is, the innerBlocks wrapper, or the outer wrapper if no inner wrapper is in use). I'm not quite sure how we ensure that, though! 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only alternative I can see here is to be deliberate about layout not supporting out of the box blocks that don't use InnerBlocks, and add some custom logic to make things work in Gallery and Post Content. I'll look into that!


while ( true === $inner_content_chunk->next_tag() ) {
$inner_wrapper_classnames = $inner_content_chunk->get_attribute( 'class' );
}
Copy link
Member

Choose a reason for hiding this comment

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

a couple thoughts on this function:

we're passing in inner blocks but then trying to get the first one. the additional list-handling here might not be helping the clarity of its purpose. if we passed in a block instead of a list of blocks, or instead of inner content, then it could focus on a more specific purpose.

function get_classes_from_last_tag( $html ) {
	$tags         = new WP_HTML_Tag_Processor( $html );
	$last_classes = '';

	while ( $tags->next_tag() ) {
		$last_classes = $tags->get_attribute( 'class' ) || '';
	}

	return $last_classes;
}

Secondly, while I think the code as-is is fine, we don't need the true === in the while condition because as a boolean value that's already implicit. No problem keeping it in there, but this isn't like functions which return a truthy value; it actually returns true or false.

Third, get_attribute( 'class' ) might return null if the last tag doesn't have a class attribute. The return type in the docblock comment says this function returns a string but unless we add a fallback it will also return null from time to time.

Fourth, a block's $inner_content is not guaranteed to contain only strings. It may contain null values where inner blocks belong. Another benefit to focusing this function on HTML processing and letting the calling code extract the HTML it wants to scan is not having to deal with this confusion here.

$inner_content_classnames = isset( $block['innerContent'][0] ) && 'string' === typeof $block['innerContent'][0]
	? get_classes_from_last_tag( $block['innerContent'][0] )
	: '';

@adamziel I wonder; if we exposed the offset/position in the stream we could allow people to add their own "rewind" tracking and other more complicated parsing. would that be worth the risk it exposes?

$last_tag_at = null;
while ( $tags->next_tag() ) {
	$last_tag_at = $tags->current_position();
}

if ( null === $last_tag_at ) {
	return;
}

$tags->seek( $last_tag_at );
return $tags->get_attribute( 'class' ) || '';

this could expose a lot of functionality it seems people want to do with this, as long as we don't expose the internal pointer, such that the offset points to an old version of the document, before it was changed by set_attribute() calls… 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ooh, that's great feedback, thanks! Yes, the function is a lot neater in your example 😄

Copy link
Contributor

@adamziel adamziel Oct 19, 2022

Choose a reason for hiding this comment

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

@adamziel I wonder; if we exposed the offset/position in the stream we could allow people to add their own "rewind" tracking and other more complicated parsing. would that be worth the risk it exposes?

Hm hm hm. This worries me in the same way as exposing get_classes() worried you in the other thread. The cursor is an implementation detail and I'd rather not have people mess around with it – it would make their core fragile and severely constraint the future updates to the tag processor. For example, seek opens the following can of worms:

$tags->next_tag();
$tag_1_at = $tags->current_position();

$tags->next_tag();
$tag_2_at = $tags->current_position();

$tags->seek( $tag_1_at );
$tags->set_attribute( 'class', 'bold' );

// $tag_2_at no longer points where we expect it to
$tags->seek( $tag_2_at );
$tags->set_attribute( 'class', 'bold' );

I'd rather keep an eye out for the use-cases that come up and see if we can address most of them with case-specific APIs. For example, this loop could benefit from has_next_tag():

while($tags->has_next_tag()) {
    $tags->next_tag();
}
return $tags->get_attribute( 'class' );

$content->add_class( esc_attr( implode( ' ', $class_names ) ) );
} else {
$content->next_tag();
$content->add_class( esc_attr( implode( ' ', $class_names ) ) );
Copy link
Member

@dmsnell dmsnell Oct 12, 2022

Choose a reason for hiding this comment

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

there's no need or benefit to running these through esc_attr() since add_class() already does this (by means of its call to set_attribute())

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, that's good to know!


$content = new WP_HTML_Tag_Processor( $block_content );
if ( $inner_content_classnames ) {
$content->next_tag( array( 'class_name' => $inner_content_classnames ) );
Copy link
Member

Choose a reason for hiding this comment

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

this does what you want it to do, but it surprised me that it did, because we intended to search for individual class names and not the entire list of classes. it would be good to be aware that if we were to chop out one of the classes this would fail because the classes in $inner_content_classnames would no longer be an identical string of the same exact length as the class attribute in the searched HTML

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 think it's safe to take that risk here because we haven't changed anything in $block_content up until this point, and we don't have a better way of targeting the block's inner wrapper. Potentially, if we had a way to know ahead of time which tag is the last, we could add a classname to it instead of storing the existing ones, and then remove it after this step.

Copy link
Contributor

Choose a reason for hiding this comment

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

@tellthemachines you could filter the tag openers manually:

$sought_class_names = $inner_content_classnames;
while( $content->next_tag() ) {
    $tag_class_names = preg_split('/\s+/', $content->get_attribute( 'class' ));
    if ( /* the two overlap */ ) {
      // ...
    }
}

I dislike the preg_split, though. @dmsnell We already have get_attribute() in the processor, any reason not to add get_classes()?

Copy link
Member

Choose a reason for hiding this comment

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

@adamziel my spidey-sense suggests it wouldn't be that much of an added value above get_attribute(). perhaps has_class() though would be.

what I don't want people to do is what we have in your code example. we provide semantic operations to add and remove CSS classes. if we're encouraging people to extract the raw value(s) and perform their own twiddling then we're just pushing them away from a robust framework and towards multiple ad-hoc implementations of the same thing.

it highlights the elephant in the room, which are class names with HTML entities. we may have to come back and revisit those; maybe we could find a way to detect if we think we have an entity (a character is &) and then rewind the class comparison with a cloned and decoded value.

point being is that I think still it's more ideal to focus on intended behaviors instead of internal mechanics. why do we want get_classes() when we can already query by tags containing a class name and get_attribute( 'class' )

Copy link
Contributor

Choose a reason for hiding this comment

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

Such a good point @dmsnell, let's explore adding has_class. I added a note to the overview issue's description so we don't lose track of it.

Copy link
Contributor

Choose a reason for hiding this comment

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

@dmsnell @aristath @getdave here's a PR that adds has_class #46232

Copy link
Member

Choose a reason for hiding this comment

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

@getdave what is the good example in that bugfix link? what was the bug? I'm trying to understand how has_class() would be a help there.

taking a guess, it looks like the goal is to only seek out wp-block-navigation-item__content if there is a current-menu-item in the document, and I'm not sure where has_class() comes in there.

if we want to know if any inner block HTML contains the class, the tag processor already includes this functionality.

$inner_tags = new WP_HTML_Tag_Processor( $inner_blocks_html );
$has_current_menu_item = $inner_tags->next_tag( [ 'class_name' => 'current-menu-item' ] );

Secondly, I don't know that the strpos() check is all that bad. If we're looking to make sure that we don't introduce needless slow-down, then doing a quick string search over the inner HTML for the possibility of finding the class name might be a good way to keep lots of requests fast while only resorting to proper HTML parsing when we have reason to believe the class might exist within.

Copy link
Contributor

@adamziel adamziel Dec 1, 2022

Choose a reason for hiding this comment

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

@dmsnell Using next_tag like that is very clever! And now that I think about it, it's also fully within what it was invented for, it just didn't jump out to me in the first place.

I don't know that the strpos() check is all that bad.

Now that I think about it, the class_name (and tag_name) matchers could run that strpos check before trying to match tags. It would work well except for matching html entities.

Copy link
Member

Choose a reason for hiding this comment

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

@adamziel apart from the HTML entities, which we currently don't match, the strpos speedup might warrant more justification before putting it in the tag processor.

on the one hand, strpos() is going to be faster if the wanted class isn't in plaintext in the document, but on the other hand, it scans up to the end of the document every time, and if we expect to find another tag "soon" after where the current pointer is, there's no need to repeatedly iterate through the document.

in terms of the general case I'd really want measured data showing that the additional complexity is worth it. my guess is that sometimes it would be faster and sometimes it would be slower, but in all cases it would add additional complexity.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

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

This is looking very cool @tellthemachines, thanks for digging in! It's going to add a lot of nice flexibility to the cover block 👍

The server-rendering appeared to be working pretty well in my testing so far, but I ran into an issue with the edit view, though, because of the duplicate output of layout classnames. The constrained and root padding aware rules target the direct children of the layout classnames, and so assume that they're only going to be added on the direct wrapper of child blocks. This isn't so noticeable with the flex layout, but if we set the cover block to constrained, then in the editor view, the cover block's span winds up being constrained unexpectedly since the wp-container-$id class is added to the outer wrapper as well as the inner wrapper. On the site frontend, where the classnames are only injected on the inner wrapper, the issue isn't present:

Editor (layout classnames are on both wrappers) Site frontend (layout classnames are only on inner wrapper
image image

I've added a couple of comments, but from a quick look, I can't quite work out an alternative — from your comment about the the Gallery and PostContent blocks, it sounds like we can't rely on only outputting layoutClassNames in useInnerBlocksProps?

One of the things I was wondering about, is if there are particular blocks that have edge cases — is that something that can be solved for those individual blocks (the Post Content block is already quite an unusual one), or is it better to see if we can ensure the API for this catches all cases?

@@ -231,7 +233,8 @@ function BlockListBlock( {
'is-content-block': hasContentLockedParent && isContentBlock,
},
dataAlign && themeSupportsLayout && `align${ dataAlign }`,
className
className,
layoutClassNames
Copy link
Contributor

Choose a reason for hiding this comment

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

Because the constrained layout and root padding features have rules that target the direct child of the layout classname, I think we might need to come up with a way where the layout classes are only applied to the deepest node (that is, the innerBlocks wrapper, or the outer wrapper if no inner wrapper is in use). I'm not quite sure how we ensure that, though! 😅

Comment on lines 203 to 206
//Dedupe layout classes
const allTheClassNames = `${ props.className } ${ layoutClassNames }`;
const classNameSet = new Set( allTheClassNames.split( ' ' ) );
const dedupedClassNames = Array.from( classNameSet ).join( ' ' );
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than de-duping after the fact, is there a way to ensure that layout classnames are only ever added once? I'm not quite sure how we'd do this, but ideally we'd only be outputting layoutClassNames in one place, with preference for the inner blocks wrapper where present. Since useInnerBlocksProps occurs after useBlockProps, I can't think of a clever way to do that, though 🤔

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 is related to the comment above: the duplication here happens because we're adding layout classnames to BlockListBlock. If we don't do that, this won't be a problem.

I can't think of a clever way to do that, though

Can you think of any non-clever ways? 😅

Copy link
Contributor

@andrewserong andrewserong Oct 13, 2022

Choose a reason for hiding this comment

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

The only non-clever thing I could come up with was to avoid useBlockProps and useInnerBlocksProps altogether, and create a separate hook that grabs layoutClassNames from the block edit context (touched on in this comment).

It would mean saying, something along the lines of, "hey, the other block supports happen automatically, but if you want to use layout, you've got to use the layout hook to grab the stuff you need, and then output it where it should go" 😅 . If it worked, I'm wondering if something like the following pseudo-code might get us in somewhere in that direction:

// layout classnames hook
function useLayoutClassNames( props ) {
  const { layoutClassNames = '' } = useBlockEditContext();
  let mergedClassnames = '';

  // insert code here to merge props.className with layoutClassNames

  return mergedClassnames;
}

// a block's edit component
function MyBlockEdit( props ) {
  const blockProps = useBlockProps();
  const innerBlocksProps = useInnerBlocksProps( blockProps );  

  // The following gets the layout class names and merges them with the classnames from the provided props
  const layoutClassNames = useLayoutClassNames( innerBlocksProps );

  return <div { ...blockProps }>
    <div { ...innerBlocksProps } className={ layoutClassNames } />
  </div>

Not sure if something like that's at all viable, but it means that we'd have a hook for getting / merging layout classnames, and the block's Edit component would then be responsible for where to put them. It would mean that becomes part of the API of the layout support for blocks that want to use it, so probably not something that's all the desirable, because it wouldn't "just work" in the way that we typically expect block supports to work.

But that's all I could think of for now!

@andrewserong
Copy link
Contributor

Just thinking out-loud, but would another potential alternative for the edit view side of things be to create a separate useLayoutClassNames hook that blocks use, and can then pass those classnames directly to where they'd like the layout classnames to be output? Similar to useBlockProps and useInnerBlocksProps, useLayoutClassNames would retrieve the layoutClassNames from useBlockEditContext();, so the edit component would then be able to place the classnames where they like. It could help bypass the problem of the other hooks needing to know whether or not they should be outputting the layout classnames?

The downside is that layout classnames wouldn't be automatically handled by useBlockProps or useInnerBlocksProps, but a potential positive is that blocks' edit components could be explicit about where to place the output.

That might just wind up creating more boilerplate, though, so possibly not be a practical option 🤔

@ramonjd
Copy link
Member

ramonjd commented Oct 13, 2022

Testing this as well. Looking great so far! This is what I'm seeing:

2022-10-13.12.02.11.mp4

This isn't so noticeable with the flex layout, but if we set the cover block to constrained, then in the editor view, the cover block's span winds up being constrained unexpectedly since the wp-container-$id class is added to the outer wrapper as well as the inner wrapper.

I noticed this as well. This is a screenshot of the frontend HTML, where the unique layout class that controls the max-width is not applied to all the cover block's children:

Screen Shot 2022-10-13 at 12 08 30 pm

Does it sound too dodgy to move the unique layout class to the block container in cover/index.php?

We're already doing some magic with the inner container. I was testing it manually in the browser and it seemed to be okay. 😅

Also, if you think it's appropriate in this PR, it looks like there's another opportunity to use the WP_HTML_Tag_Processor class in cover/index.php

Something like:

$p = new WP_HTML_Tag_Processor( $content );
$p->next_tag();
$p->set_attribute( 'style', $styles );

@andrewserong
Copy link
Contributor

andrewserong commented Oct 13, 2022

This is a screenshot of the frontend HTML, where the unique layout class that controls the max-width is not applied to all the cover block's children:

For that one, I thought the server-rendered view looked a bit better, as the span and <img> elements would remain full-width, but only the children of the inner container would get contentSize etc, applied. Whereas in the edit view, it's affecting both levels (container > img/span, as well as children of the inner wrapper) 🤔

@ramonjd
Copy link
Member

ramonjd commented Oct 13, 2022

I thought the server-rendered view looked a bit better, as the span and elements would remain full-width, but only the children of the inner container would get contentSize etc, applied. Whereas in the edit view, it's affecting both levels (container > img/span, as well as children of the inner wrapper) 🤔

Oh good point. My only reaction was that the editor and frontend weren't same when adding a constrained content size, so either way... 😄

@tellthemachines
Copy link
Contributor Author

Thanks everyone for all the feedback and testing!

One of the things I was wondering about, is if there are particular blocks that have edge cases — is that something that can be solved for those individual blocks (the Post Content block is already quite an unusual one), or is it better to see if we can ensure the API for this catches all cases?

I'm starting to think it's better to optimise for blocks that use InnerBlocks, which is the canonical way to handle block nesting, and for edge case situations that require custom nesting logic, it won't be too much of a burden to handle layout manually as well.

Just thinking out-loud, but would another potential alternative for the edit view side of things be to create a separate useLayoutClassNames hook that blocks use, and can then pass those classnames directly to where they'd like the layout classnames to be output?

This could be a useful tool for handling the edge cases! but I wouldn't make it the default solution. At least not if we want to move towards a Layout support that Just Works for the majority of use cases 😅

@tellthemachines
Copy link
Contributor Author

Also, if you think it's appropriate in this PR, it looks like there's another opportunity to use the WP_HTML_Tag_Processor class in cover/index.php

I added layout to Cover in this PR for testing purposes only, and plan to remove it before merging. Mainly because there's a bit of discussion going in #43140 around the best way to handle multiple layout types plus the matrix alignment tool. That sounds like a good candidate for a forthcoming Cover-specific PR though!

@andrewserong
Copy link
Contributor

This could be a useful tool for handling the edge cases! but I wouldn't make it the default solution. At least not if we want to move towards a Layout support that Just Works for the majority of use cases

Ah, we said much the same thing at the same time! 😄

Yes, I agree, it'd be good to see how far we can get with the "just works" approach, and what the edge-casey workarounds look like for the other blocks 👍. In terms of API, perhaps we can think of it as the part that "just works", and then additional tools / hooks, etc, for edge-casey blocks to use?

@tellthemachines
Copy link
Contributor Author

Update: turns out the edge case workarounds were pretty easy, due to layoutClassNames already being passed to BlockEdit.

Copy link
Contributor

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

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

This is testing really well for me @tellthemachines, thanks for honing in on the final changes, it feels nice and clean and delivers on the promise of trying to get as close as possible to a "just works" approach for attaching layout classnames and styles to the inner blocks wrapper, and falling back to the outer wrapper where there is no inner blocks wrapper 👍

It feels like a good step toward a consistent API for stabilising the layout block support, too, so I think this would be a good approach to go with.

I've left a couple of questions on the PHP side of things to make sure I'm following along correctly 🙂

Also, I'd just like to ping @youknowriad if he has time to take a look, too, as I remember many earlier discussions in the development of the layout support, and mentions of one day moving to targeting the innerBlocks wrapper. So it might be good to confidence check that the approach is consistent with the earlier wish-list of how it might work 🙂.

This is very cool, though, I'm really excited about this one! 😀

$block_content,
1
);
$inner_content_classnames = isset( $block['innerContent'][0] ) && 'string' === gettype( $block['innerContent'][0] ) ? gutenberg_get_classnames_from_last_tag( $block['innerContent'][0] ) : '';
Copy link
Contributor

@andrewserong andrewserong Oct 14, 2022

Choose a reason for hiding this comment

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

Is it worth adding a comment to explain what the 0 index of innerContent is, since it's sort of internal to the structure of the block object? E.g. something along the lines of:

// Attempt to extract classnames from last tag in block markup directly preceding first inner blocks marker.
// This identifies the first, deepest nested inner blocks wrapper.

Please correct me, though, if I've misunderstood how this is working, I just looked up the block parser class, so I think that's what it's doing, but could very well be wrong 🙂

From the comment in that class, it sounds like there's the potential for blocks to have another string between null markers (on this line of the comments). I've only ever encountered an opening string, multiple null values for each of the inner blocks, and then a closing string, so I wonder if there are any blocks that do that in practice? If so, I reckon we could look at that if and when we encounter it and then come back to the logic in this function to re-examine it, rather than worry about it now.

For now, looking at the last tag in the opening string sounds good to me!

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's exactly how it works! Good idea to add a comment, as it's not immediately obvious.

@@ -397,7 +413,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
$should_skip_gap_serialization = gutenberg_should_skip_block_supports_serialization( $block_type, 'spacing', 'blockGap' );

$style = gutenberg_get_layout_style(
".$block_classname.$container_class",
".$container_class.$container_class",
Copy link
Contributor

Choose a reason for hiding this comment

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

This change looks good to me. Is the reason we needed to do it because in the JS side of things we won't always have access to the block classname, is that right? Either way, I think the block classname was only there to increase specificity of this selector, so the change sounds good to me.

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 is needed because the block class is attached to the outer wrapper, and because we're now adding the container class to the inner wrapper this style block will no longer work with that combination.

@tellthemachines tellthemachines force-pushed the try/layout-classnames-on-inner-wrapper branch from 61305ff to 4074881 Compare October 26, 2022 02:28
@ndiego
Copy link
Member

ndiego commented Nov 2, 2022

Hi guys, I just discovered that this PR causes an issue for extenders. This docs page details how you can add custom classes to blocks. With this PR, any custom classes are added to both the main container as well as the inner block wrapper.

@andrewserong
Copy link
Contributor

Thanks for the heads-up @ndiego! I think I've found where this was happening and have opened up a proposed fix in #45499.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Layout Layout block support, its UI controls, and style output.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants