-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Governing block settings and interactions #41547
Comments
@dabowman Your idea would be a wonderful advancement to the block editor, especially now that we're trying to use it for more than just
(Forgive me if I'm bolting something alien on to your main point.) In addition to context-specific settings, I would like more ability to set context-specific It would be much more useful for my client teams to be able to, e.g., have certain PHP page templates, custom post types, or The ideal system would be one where a long list of site-wide default block settings are defined, and then "a-la-carte" subsets/adjustments of those blocks and settings are applied to certain editable areas of the site. My only specific suggestion to your example is to make the block slugs more explicit to smoothly allow multiple block settings under one parent. Like this:
|
Hey @dabowman, thanks for chiming in and sharing the detailed use case. The first point — contextually control what tools and presets blocks get access to — is currently captured in the roadmap in two issues: The idea is that theme.json objects are composable, and that any post type, template, or template part can become a provider. That means you could have something akin to a Those issues need some help, including fleshing out the ergonomics — should we default to separate files from the get go? Should the files be stored in Ensuring theme.json objects merge well and in the right order is also paramount for things like #41707. The second point about permissions is a bit trickier in how it can multiply the declarations. It's not clear if granular control over each setting type needs its own capability management or if it can be a wider set of principles (for example, control access to appearance tools in general by role). This needs a lot more thought to balance flexibility and complexity. There's an issue for template permissions that is relevant to take into account: #27597 |
@dabowman, may I ask you how far adding descendant block styles to your container blocks could get you? What would be still missing from what you are trying to accomplish? |
@luisherranz Thank you for sharing! I'm working with @dabowman on some related work and took a look at your changes. I think the main difference is that we're looking to define a set of acceptable options on a hierarchy rather than a specific choice. Using the last example in the description, we'd want As far as I'm aware block styles can only dictate one "correct" style, and this would still be the case in a hierarchy with your changes. A |
Oh, you're right, sorry. This one includes the settings: #42124 |
@luisherranz Thank you, didn't realize that work was being done as well! I'll look more into it tomorrow. Does it support multi-level nesting, something like this? {
"settings": {
"blocks": {
"core/heading": { /* top-level heading settings */},
"custom/page-header": {
"blocks": {
"core/heading": { /* location-specific heading settings */}
}
}
}
}
} |
This is not an enhancement of the <!-- wp:custom/page-header { "settings": { "blocks": { "core/heading": location-specific heading settings } } } -->
<!-- wp:heading -->
<h2>The custom/page-header block overrides this block's settings.</h2>
<!-- /wp:heading -->
<!-- /wp:custom/page-header --> |
@luisherranz Just to clarify, I meant putting the above nested rules into a container. To change the example a bit, image we'd like different options for vanilla headings and those in <!-- wp:group {"settings": {"blocks": {"core/heading": { /* top-level settings... */},"core/media-text": {"blocks": {"core/heading": { /* location-specific settings */}}}}}} -->
<!-- wp:heading -->
<h2>A normal heading with top-level settings</h2>
<!-- /wp:heading -->
<!-- wp:media-text {"mediaType":"image", /* ... */} -->
<div class="wp-block-media-text alignwide is-stacked-on-mobile"><figure class="wp-block-media-text__media"><img src="/*...*/" alt="" class="wp-image-22 size-full"/></figure><div class="wp-block-media-text__content">
<!-- wp:heading -->
<h2>Inner Heading</h2>
<!-- /wp:heading -->
<!-- /wp:media-text -->
<!-- /wp:group --> I’m trying to figure out if it’s a valid strategy to put all of the design system logic into one container block at the top of the page that could specify all of the nested settings logic we’re looking for here using your new feature. I believe we’d need that sort of settings hierarchy to make the system work. If not, can you explain a bit more about what you had in mind? Thank you! |
That's a good example, thanks. Top-level settings are still defined in the If the header settings need to be different inside a specific <!-- wp:group -->
<!-- wp:heading -->
<h2>A normal heading with top-level settings from `theme.json`</h2>
<!-- /wp:heading -->
<!-- wp:media-text {"mediaType":"image", "settings": { "blocks": { "core/heading": { /* localtion-specific settings /* } } } } -->
<div class="wp-block-media-text alignwide is-stacked-on-mobile">...
<!-- wp:heading -->
<h2>Location specific settings from parent `core/media-text`</h2>
<!-- /wp:heading -->
</div>
<!-- /wp:media-text -->
<!-- /wp:group --> From your comment, I guess you want to configure the settings of any heading block inside any media-text block, not only the headings inside a specific media-text instance. Is that correct? |
That's correct! We are looking for a place (in
The nesting-based context in this issue would be very useful, but we're also looking into more generalized ways we can tie post type/current user/surrounding context into the block editor. The overall goal is a way that we can define block editor UI choices for enterprise customers who want a coherent design system enforced over one or more sites. |
Thanks, Alec.
Post type context It would be easy to achieve with descendant block styles/settings just by creating a new template for each post type, adding a Group block at the top, and configuring the Templates themselves could also do Current user context I guess this is something that would need to be baked into the Also, if this is ever considered, I'm not sure how far Gutenberg should go enforcing those permissions in the backend because the WordPress permission system doesn't support such granularity, AFAIK. Surrounding context
|
@luisherranz Thank you for the breakdown about how we might approach different types of enterprise governance needs above. We've been discussing implementation of these block editor settings. It seems like we have two main options:
I'd like to discuss the second option for a bit. This is actually sort of possible to do already with the Governance in codeHere's a example of a nesting hierarchy where we alter function setupGovernance() {
// When a core/media-text contains a core/heading, change to a limited palette
addNestedBlockSettings( 'core/media-text', 'core/heading', () => ( {
color: {
palette: {
theme: getThemeSettings( 'colors', [ 'primary', 'secondary' ]),
},
},
} ) );
}
const addNestedBlockSettings = ( parentBlockName, childBlockName, settingsCallback ) => {
// Enable __experimentalSettings on block to allow for per-block settings on this block type
const enableBlockExperimentalSettings = ( settings, name ) => {
if ( name === childBlockName ) {
settings.supports.__experimentalSettings = true;
}
return settings;
}
wp.hooks.addFilter('blocks.registerBlockType', 'plugin/enable-block-experimental-settings', enableBlockExperimentalSettings);
const withCustomNestingSettings = createHigherOrderComponent( WrappedBlock => {
return ( props ) => {
const isNestedInParentBlockName = useSelect( ( select ) => {
const { getBlockParents, getBlocksByClientId } = select( 'core/block-editor' );
const parentIds = getBlockParents( props.clientId, [ parentBlockName ] );
const parents = getBlocksByClientId( parentIds );
return parents.some( block => parentBlockName === block.name );
} );
useEffect( () => {
if ( props.name === childBlockName && isNestedInParentBlockName ) {
props.setAttributes( {
settings: settingsCallback(),
} );
}
}, [] );
return <WrappedBlock { ...props } />;
};
}, 'withCustomNestingSettings' );
defaultHooks.addFilter('editor.BlockEdit', 'plugin/custom-nesting-settings', withCustomNestingSettings);
};
function getThemeSettings( path, slugs = [] ) {
const settings = select( blockEditorStore ).getSettings();
let settingsAtPath = lodash.get(settings, path, []);
if ( slugs ) {
settingsAtPath = settingsAtPath.filter( setting => slugs.includes(setting.slug) );
}
return settingsAtPath;
}
setupGovernance(); Here's what that code looks like in action: I also don't think this was the intended use of Imperative versus DeclarativeExpressing governance declaratively in code as shown above would be powerful. If extended to a more officially supported use-case, WordPress developer users could define their own rules in a plugin using well-defined hooks. The obvious benefit is we wouldn't need to encode all types of governance in the schema and we'd still give enterprisey users the power to implement their own governance. The idea of adding hooks to support our use-case in code seems very common in PHP WordPress, but to a lesser degree in Gutenberg. From my very limited perspective it seems Gutenberg aims to provide full configuration through If it were possible to add/extend existing hooks in Gutenberg to support our desired block editor governance through plugin code, would this be a welcome addition? Next Steps@ingeniumed and I are looking to help however we can to implement governance with Gutenberg. We want to solve @dabowman's needs, and enterprise use of design systems with Gutenberg more broadly. Nested block governance would be a great starting point. Whether this is by jumping into existing issues and PRs to extend theme.json, or implementing hooks to extend the API in code, we'd like to contribute. We've read through the issues related subissues linked in this discussion but it's difficult to understand a concrete place where we can start pitching in. It might make sense to start a PR to implement nested block settings, work on a lower-level issue like theme.json cascading merge rules, or extend hooks like discussed above. Do you have any recommendations about where we can begin to contribute? |
I'm sorry, but I'm not familiar with that part of the codebase. From what I've seen so far, the best way to bring attention to a topic is to open a PR and ask people to review it. It doesn't have to be the full feature, just a meaningful part or first step. If someone has concerns with that approach, they should say so before merging it, ping more people for opinions, and so on. I can only say that your proposal of nested block settings makes sense to me from a theme DX point of view. I can't imagine a different future need for that nested declaration that would cause a conflict with it and the mental model looks straightforward to me. The only thing I'd pay extra attention to is if it has implications for the cascading algorithm. So I'd also follow the work of @mcsf, @jorgefilipecosta and @oandregal with the descendant styles/settings. |
Thanks for all the feedback everyone! @alecgeatches and I worked on this issue, and we have a PR up to address this here. We have linked back to this issue within that PR, and also included a brief demo video of it in action. |
We had a call with @alecgeatches to better understand the problem and what kind of solutions could come from Core. Below is a summary.
Instead, it seems sensible to consider adding some escape hatches in the form of filters. For admins, as long as their "contract" with end users so allows, this unblocks cascades of governance rules as complex as they need to be. I quickly tried adding a filter to const RESTRICTED_PALETTE = [
{ color: '#1a4548', name: 'Primary', slug: 'primary' },
];
addFilter(
'blockEditor.useSetting.before',
'mcsf/limit-heading-palette-inside-groups',
( blockName, path, ancestors ) => {
if (
path === 'color.palette.theme' &&
blockName === 'core/heading' &&
ancestors.length > 1
) {
const { getBlockName } = wp.data.select( 'core/block-editor' );
if (
ancestors.some(
( ancestorId ) =>
getBlockName( ancestorId ) === 'core/group'
)
) {
return RESTRICTED_PALETTE;
}
}
}
); For completion's sake, here's the diff in the source of diff --git a/packages/block-editor/src/components/use-setting/index.js b/packages/block-editor/src/components/use-setting/index.js
index 3d37a85de1..c4d7f99594 100644
--- a/packages/block-editor/src/components/use-setting/index.js
+++ b/packages/block-editor/src/components/use-setting/index.js
@@ -11,6 +11,7 @@ import {
__EXPERIMENTAL_PATHS_WITH_MERGE as PATHS_WITH_MERGE,
hasBlockSupport,
} from '@wordpress/blocks';
+import { applyFilters } from '@wordpress/hooks';
/**
* Internal dependencies
@@ -127,6 +128,15 @@ export default function useSetting( path ) {
...select( blockEditorStore ).getBlockParents( clientId ),
clientId, // The current block is added last, so it overwrites any ancestor.
];
+
+ result = applyFilters(
+ 'blockEditor.useSetting.before',
+ blockName,
+ normalizedPath,
+ candidates
+ );
+
+ if ( result !== undefined ) {
candidates.forEach( ( candidateClientId ) => {
const candidateBlockName =
select( blockEditorStore ).getBlockName( I think this is worth looking into. We might want to introduce two filters: one suffixed |
@mcsf Thank you for the summary and feedback! We're going to spend some time trying to reimplement the system via a plugin as you've suggested. This is very similar to a previous implementation we suggested with the bonus of a much cleaner hook. One initial problem we've hit is that without the involvement of I have one question about this point above (emphasis mine):
Your team mentioned this in the call as well, and I'm not sure what this refers to. Can you tell me a little more about what you mean by visually representable? In the case of the nested PR, the rules are represented in Thank you! |
@mcsf Building on what @alecgeatches mentioned, there's an approach we have in mind but it requires some confirmation. Essentially, we would want to have a filter that's applied at the end of the sanitize function so that anyone can modify the output of the processed theme.json. This is the specific line that I am referring to. Instead of the regular logic wherein the theme.json is cleaned up based on the schema, there would be the chance for any plugin to add/remove/update the data inside. The purpose for this would be to add in support for unsupported theme.json properties such as nested block settings. They would now be able to be available to the editor, courtesy of any function hooking into this filter. The idea for this came from this existing filter that's used by Gutenberg already to modify the output of WP_Theme_JSON's function. |
I see If the goal is supporting new things that the core engine doesn't, would it work for you to:
|
@oandregal Thanks for the feedback, and for explaining that a bit more! incidentally, the route you are suggesting is what we decided to take after my previous comment. We ended up not going down the filters route. Instead, what we did is:
This way we are able to minimize what we change in the current code, and like you said try to not add in filters that only modify implementation details of our current code. It would be more for enhancing it. The part that I'm working on, that I could use your advise on is: How to get the additional block settings injected as CSS attributes onto the editor? At the moment, we are able to select the setting (change the colour to red for example) from the editor's block panel. But, since the CSS attribute has not been injected into the styles of the page it's not reflected in the appearance. I'm looking at |
What we meant here is that, currently, configurations are easily representable in the Global Styles sidebar: there are general styles accessible via Typography, Colors, Layouts at the root of the sidebar drill-down menu, and then there are block-specific ones. There is room to improve that UX, but it's still straightforward. A system of nested block rules would certainly stress the UI. One would have to drill down nested lists of blocks to determine how a block should be styles in the context of another. While not impossible to represent, this wouldn't be an experience to subject to the majority of users. Adding other signals like user roles to that system would be the nail on its coffin. |
Thanks for the feedback @mcsf and @oandregal! We have posted this PR as a result of it. |
@ingeniumed I am exploring the new |
As far as we're aware, there's still no official way to load CSS into the iframed block editor although there are some workarounds discussed in #38673. Additionally, the work being done in #46496 should make it easier to dynamically pull block selectors and generate the appropriate styling for the block editor. That being said, the |
What problem does this address?
We want to be able to control what block settings are offered to the user in more ways than core currently allows.
I work in the space of building WordPress applications for enterprises and large organizations. For those of us in this space, this makes our end-user a bit different from the individual or small business that might benefit from granular design control over blocks.
Our users have the resources to hand theme design and development over to designers and developers. Our challenge isn't implementing a design in the block editor, but developing blocks that provide more targeted flexibility and options within an existing design system.
Our users aren't designing websites and themes in the block editor. They're assembling websites using blocks that have been designed and customized for them specifically. For the designers and developers making those blocks, governance is a primary concern.
Right now we can define block settings in theme.json like this.
As a simple example, let’s look at color.
Our global styles palette contains several color options. It looks like this:
We use the global styles
--wp--presets--color
object as a “theme” which defines the standard style tokens that our blocks will look for. Colors are named semantically and draw from the tokens of our design system which is housed in the--wp--custom
object for now—although we're working on being able to reference it from it's external npm package.We want to continue to use global styles as a way to give semantic structure to our styles. It mirrors how we think of our application while we're designing it and it makes "re-theming" a lot easier. It gives us a set number of variables that a block needs to support in order to be styled properly by the theme.
However, we don’t want all these colors to be available wherever a user is presented with a palette. We only want certain colors to be available in specific places and the number of available options can be overwhelming in situations where only a couple of options would be appropriate.
Limiting block settings is currently possible on a theme-wide level. You can override the theme palette within a block like this:
Here I’m overriding the palette to limit the colors available within the heading block to the “heading” option. If we had alternate heading colors we could also add those here. This makes blocks a lot easier to use, especially as they become more complex and designers need to consider how users will interact with them in the editor.
The trouble is that this only happens on a theme-wide level. We want to be able to define all possible options that a certain block supports and then narrow down what options are presented to a user in the block editor at any given time based on a few context-based factors:
Where is the block? what is its’ parent? what template is it in? what post type is it on? what block is immediately before and after it? etc.
These questions are ones we consider when designing and the answers to them can impact what options we make available to a user.
Who is interacting with it? what permissions does the user interacting with it have? are they a designer? are they just a copy editor?
This becomes clear with the example of type styles.
Similar to the global styles
--wp--presets--color
object we use the--wp--presets--font-sizes
object as a theme and name the tokens semantically. Here are all the options we currently have in our type system.There are a lot of them and all the text on the site is mapped to a font size here that corresponds to our design system. This is essential for us to be able to maintain control and consistency across all the various applications our type scale needs to serve.
Similar to color, we really don’t want to present all these options to a user every time they are asked to select a font size. Not only would it be overwhelming but it would present a lot of possibility for error and would force our user to either make a design decision or consult a style guide or a designer for help.
Going back to the example of our heading block, it potentially helps to narrow down the options on a theme level like this:
Here, I’m limiting the options to only the heading font sizes. This is an improvement, but still falls short of the user experience we need. Giving a user all the heading options still forces them to make a design decision or consult a style guide or designer. If we could further limit the options here in a way that responds to the questions above we would be able to design a user experience that limits the possibility for design error—one that lets our users focus on content creation instead of design.
In our custom
page-header
block, our heading block is limited to 3 size options. Right now we accomplish this by forgoing the core block and making that block totally custom. This has achieved the experience we need for our users but it comes at the expense of introducing complexity to our codebase and opting simple blocks out of any further improvements that come through core.What is your proposed solution?
Being able to pass a second set of settings for a block that only applies to a specified context would probably do what we need. Doing this in theme.json would be nice.
The theme.json settings for an implementation using core blocks might look something like this:
These are all the font-size options available to the heading block across the theme:
This would be the curated sub-set of font-size options available to the heading block when it is placed within the page-header block:
The text was updated successfully, but these errors were encountered: