-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Add layout styles from Post Content block to post editor #44258
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
Changes from 1 commit
95cb34a
84cddef
0128131
b8d56b8
58a2580
602c7df
7102ea3
1b29729
08fe809
eeb03e3
f2f7e25
67c829d
bcdeda8
fb51a82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,7 +49,7 @@ const layoutBlockSupportKey = '__experimentalLayout'; | |
| * | ||
| * @return { Array } Array of CSS classname strings. | ||
| */ | ||
| function useLayoutClasses( layout, layoutDefinitions ) { | ||
| export function useLayoutClasses( layout, layoutDefinitions ) { | ||
| const rootPaddingAlignment = useSelect( ( select ) => { | ||
| const { getSettings } = select( blockEditorStore ); | ||
| return getSettings().__experimentalFeatures | ||
|
|
@@ -89,6 +89,24 @@ function useLayoutClasses( layout, layoutDefinitions ) { | |
| return layoutClassnames; | ||
| } | ||
|
|
||
| export function useLayoutStyles( block = {}, selector ) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice abstraction here! |
||
| const { attributes = {}, name } = block; | ||
| const { layout = {}, style = {} } = attributes; | ||
| const fullLayoutType = getLayoutType( layout?.type || 'default' ); | ||
| const defaultThemeLayout = useSetting( 'layout' ) || {}; | ||
| const blockGapSupport = useSetting( 'spacing.blockGap' ); | ||
| const hasBlockGapSupport = blockGapSupport !== null; | ||
| const css = fullLayoutType?.getLayoutStyle?.( { | ||
| blockName: name, | ||
| selector, | ||
| layout, | ||
| layoutDefinitions: defaultThemeLayout?.definitions, | ||
| style, | ||
| hasBlockGapSupport, | ||
| } ); | ||
| return css; | ||
| } | ||
|
|
||
| function LayoutPanel( { setAttributes, attributes, name: blockName } ) { | ||
| const { layout } = attributes; | ||
| const defaultThemeLayout = useSetting( 'layout' ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -28,13 +28,16 @@ import { | |||||
| __unstableUseMouseMoveTypingReset as useMouseMoveTypingReset, | ||||||
| __unstableIframe as Iframe, | ||||||
| __experimentalRecursionProvider as RecursionProvider, | ||||||
| useLayoutClasses, | ||||||
| useLayoutStyles, | ||||||
| } from '@wordpress/block-editor'; | ||||||
| import { useEffect, useRef, useMemo } from '@wordpress/element'; | ||||||
| import { Button, __unstableMotion as motion } from '@wordpress/components'; | ||||||
| import { useSelect, useDispatch } from '@wordpress/data'; | ||||||
| import { useMergeRefs } from '@wordpress/compose'; | ||||||
| import { arrowLeft } from '@wordpress/icons'; | ||||||
| import { __ } from '@wordpress/i18n'; | ||||||
| import { parse } from '@wordpress/blocks'; | ||||||
|
|
||||||
| /** | ||||||
| * Internal dependencies | ||||||
|
|
@@ -82,18 +85,31 @@ function MaybeIframe( { | |||||
| ); | ||||||
| } | ||||||
|
|
||||||
| function findPostContent( blocks ) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we help our future selves here and add a JS doc comment? |
||||||
| for ( let i = 0; i < blocks.length; i++ ) { | ||||||
| if ( blocks[ i ].name === 'core/post-content' ) { | ||||||
| return blocks[ i ]; | ||||||
| } | ||||||
| if ( blocks[ i ].innerBlocks.length ) { | ||||||
| return findPostContent( blocks[ i ].innerBlocks ); | ||||||
|
||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| export default function VisualEditor( { styles } ) { | ||||||
| const { | ||||||
| deviceType, | ||||||
| isWelcomeGuideVisible, | ||||||
| isTemplateMode, | ||||||
| templateContent = '', | ||||||
| wrapperBlockName, | ||||||
| wrapperUniqueId, | ||||||
| } = useSelect( ( select ) => { | ||||||
| const { | ||||||
| isFeatureActive, | ||||||
| isEditingTemplate, | ||||||
| __experimentalGetPreviewDeviceType, | ||||||
| getEditedPostTemplate, | ||||||
| } = select( editPostStore ); | ||||||
| const { getCurrentPostId, getCurrentPostType } = select( editorStore ); | ||||||
| const _isTemplateMode = isEditingTemplate(); | ||||||
|
|
@@ -109,6 +125,7 @@ export default function VisualEditor( { styles } ) { | |||||
| deviceType: __experimentalGetPreviewDeviceType(), | ||||||
| isWelcomeGuideVisible: isFeatureActive( 'welcomeGuide' ), | ||||||
| isTemplateMode: _isTemplateMode, | ||||||
| templateContent: getEditedPostTemplate()?.content, | ||||||
| wrapperBlockName: _wrapperBlockName, | ||||||
| wrapperUniqueId: getCurrentPostId(), | ||||||
| }; | ||||||
|
|
@@ -122,16 +139,13 @@ export default function VisualEditor( { styles } ) { | |||||
| themeHasDisabledLayoutStyles, | ||||||
| themeSupportsLayout, | ||||||
| assets, | ||||||
| useRootPaddingAwareAlignments, | ||||||
| isFocusMode, | ||||||
| } = useSelect( ( select ) => { | ||||||
| const _settings = select( blockEditorStore ).getSettings(); | ||||||
| return { | ||||||
| themeHasDisabledLayoutStyles: _settings.disableLayoutStyles, | ||||||
| themeSupportsLayout: _settings.supportsLayout, | ||||||
| assets: _settings.__unstableResolvedAssets, | ||||||
| useRootPaddingAwareAlignments: | ||||||
| _settings.__experimentalFeatures?.useRootPaddingAwareAlignments, | ||||||
| isFocusMode: _settings.focusMode, | ||||||
| }; | ||||||
| }, [] ); | ||||||
|
|
@@ -197,11 +211,25 @@ export default function VisualEditor( { styles } ) { | |||||
| return { type: 'default' }; | ||||||
| }, [ isTemplateMode, themeSupportsLayout, defaultLayout ] ); | ||||||
|
|
||||||
| const blockListLayoutClass = classnames( { | ||||||
| 'is-layout-constrained': themeSupportsLayout, | ||||||
| 'is-layout-flow': ! themeSupportsLayout, | ||||||
| 'has-global-padding': useRootPaddingAwareAlignments, | ||||||
| } ); | ||||||
| const templateBlocks = parse( templateContent ); | ||||||
| const postContentBlock = findPostContent( templateBlocks ); | ||||||
| const postContentLayoutClasses = useLayoutClasses( | ||||||
| postContentBlock?.attributes?.layout, | ||||||
| defaultLayout?.definitions | ||||||
| ); | ||||||
|
|
||||||
| const blockListLayoutClass = classnames( | ||||||
| { | ||||||
| 'is-layout-flow': ! themeSupportsLayout, | ||||||
| 'wp-container-visual-editor': themeSupportsLayout, | ||||||
| }, | ||||||
| postContentLayoutClasses | ||||||
| ); | ||||||
|
|
||||||
| const postContentLayoutStyles = useLayoutStyles( | ||||||
| postContentBlock, | ||||||
| '.wp-container-visual-editor' | ||||||
|
||||||
| Current PR (TwentyTwentyTwo's margins override the left content justification) | When updating to the higher specificity selector |
|---|---|
![]() |
![]() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really! Must be my subconscious drive to lower specificity everywhere 😅
It's a good idea to use the selector we had before though.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will we ultimately want to wrap this in a useMemo so that we only run it when templateContent changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question! I never know when it's best to useMemo or not 😅 Another option would be to return null from useLayoutStyles if there's no block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option would be to return null from useLayoutStyles if there's no block.
Actually I don't think we can do that because it's using hooks underneath 😕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I never know when it's best to useMemo or not 😅
Me either, it's often a bit tricky to work out when they're useful. In principle if we're doing potentially expensive things like block parsing on a component that might get rendered multiple times, I think it makes sense to add a useMemo. If I add a wrapper findPostContentWrapper function that calls findPostContent and log out the number of times it's called, in my local environment it looks like we call it 9 times (not sure how valid that is, since there's only 5 log lines there, but it at least suggests we might benefit from the useMemo caching 🤔):
From memory we can't call a hook from within another hook, so the optimisation might only be to wrap the block containing the two lines parse and findPostContent in a useMemo, so that we only parse / find blocks when the template content changes?
But this can totally be something to look at in the final stages of the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I tried what you recommended and it seems to be working well! We can still see a jump in layout when the page first loads, I think because the initial data from useSelect takes a while to appear, but I don't think there's much to be done about that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think because the initial data from useSelect takes a while to appear, but I don't think there's much to be done about that.
Probably outside the scope of this PR, but I wonder if there's a way to preload the data for this selector? I see that the Navigation block has a preloading function here and sets apiFetch to use it here. There's similar logic in gutenberg_initialise_editor here, so I wonder if there's a PHP filter of some kind to add in a desired API endpoint to pre-fetch or something? Might be a bit of a rabbithole 😅
I don't at all understand the logic there, but just in case something along those lines sparks any ideas 🙂




There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feedback is probably low priority, but just jotting down some thoughts:
When this function was originally written, I believe we were thinking that the style engine might be responsible for more of the layout support that we do now (I think this function was written prior to the layout refactor landing). It might be worth us updating the description here, as I believe it probably makes sense for
useLayoutClassesto be the entry point for getting layout classnames — if in some point in the future we do use the style engine for constructing the classnames, we might wind up doing it as an internal implementation detail, so we'd still probably want this as the main function for getting those classnames?In terms of stability / API signature, the function currently accepts the layout object, and a layoutDefinitions object. However, in
useLayoutStylesthe signature is a bit different in that it accepts a block object and a selector. Would it be worth coming up with a signature for both of these hooks that is pretty much the same, to create some consistency there? I like the use of the block object in the latter — we might wind up needing to access other aspects of the block object inuseLayoutClassesin the future, too, so it could be worth making some changes there?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for thinking this through! I'll update the description to remove references to hypothetical future style engine work 😅
We wouldn't need the selector for
useLayoutClassesthough, would we? Other than that I'm happy to change it to take the block object.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point. Yeah, I can't think of a circumstance where we'd need the selector for getting the classnames. We might have use for it eventually for where we output the classnames, but not for retrieving them in the first place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I don't think we'd ever need the selector in
useLayoutClasses.I'm also struggling to think of a use for the whole block in this function; we only really need the layout object. We could potentially change it to get the layout definitions from
useSetting( 'layout' )instead of passing them as a parameter. Would that work everywhere?In
useLayoutStyleswe need bothlayoutandstylesfrom attributes, as well as the block name to pass to the layout type'suseLayoutStylesfunction, so it can work out whether the block skips serialisation or not.It's not looking like we can make the signatures very consistent here 😅
Regarding the doc comment for
useLayoutClasses, could we simply shorten it to "Generates the utility classnames for the given blocks layout attributes.", or is there any additional info that might be useful here?Also, I'm thinking we should make these experimental given Layout itself has that status.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now that's definitely the case. My only thought was if we wind up eventually needing to use parts of the
spacingobject for potential future classnames (e.g. if we needed to add ahas-gapclassname further down the track, which I think was discussed a little in the spacing presets discussions), then it'd be easier if we already had the object there.That very well could. I think passing it around is probably a result of some of these functions originally being pure functions instead of React hooks? Now that they're hooks, it's probably better to keep the API signature simpler instead of passing stuff around 🤔
Sounds good to me!
Good idea, there's still plenty that we're moving around, so would be good to maintain that flexibility 👍