最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Gutenberg: Custom Block Plugin with Custom SlotFill

programmeradmin8浏览0评论

I'm working within a Block Library plugin and have a simple block plugin (standard build with blocks.registerBlockType, editor.BlockEdit, blocks.getSaveContent.extraProps, and editor.BlockListBlock) to handle DeviceVisibility that adds a panel to every block that can populate a few attributes (e.g. hide on mobile, hide on desktop) on the wrapperProps. This works perfectly.

However, I've run into a few situations where it would be useful to allow several of the blocks I've created to add controls unique to the block to the DeviceVisibility plugin's Panel, for example when a block should "Stack children on mobile" in addition to the default controls added by the plugin.

SlotFill seems like the obvious answer.

The problem is that when I create a SlotFill (using createSlotFill) instead of controls for the current block being added to the Slot, any controls added to a <DeviceVisibilityControls/> component in any block are all added to every block. Here's the code of the SlotFill:

// DeviceVisibilityControls/index.js

import {createSlotFill} from '@wordpress/components';

const {Fill, Slot} = createSlotFill('DeviceVisibilityControls');
const DeviceVisibilityControls = ({children, clientId}) => {
    return (
        <Fill>
            {children}
        </Fill>
    );
};

DeviceVisibilityControls.Slot = Slot;
export default DeviceVisibilityControls;

and the implementation in the plugin's inspector:

// plugin/inspector.js

<InspectorControls>
  <PanelBody>
   // other controls
   <DeviceVisibilityControls.Slot/>
  </PanelBody>
<InspectorControls>

This feels like one of those situations where I'm missing something simple -- some method of forwarding context or isolating the controls sent for the active control by clientId. Something. Been searching Gutenberg's source, but the solutions there are spread out enough that it's hard to connect the dots (or maybe I've been looking at this too long).

Could use an assist. In reward, I will name my next child after anyone who helps with a solution.

I'm working within a Block Library plugin and have a simple block plugin (standard build with blocks.registerBlockType, editor.BlockEdit, blocks.getSaveContent.extraProps, and editor.BlockListBlock) to handle DeviceVisibility that adds a panel to every block that can populate a few attributes (e.g. hide on mobile, hide on desktop) on the wrapperProps. This works perfectly.

However, I've run into a few situations where it would be useful to allow several of the blocks I've created to add controls unique to the block to the DeviceVisibility plugin's Panel, for example when a block should "Stack children on mobile" in addition to the default controls added by the plugin.

SlotFill seems like the obvious answer.

The problem is that when I create a SlotFill (using createSlotFill) instead of controls for the current block being added to the Slot, any controls added to a <DeviceVisibilityControls/> component in any block are all added to every block. Here's the code of the SlotFill:

// DeviceVisibilityControls/index.js

import {createSlotFill} from '@wordpress/components';

const {Fill, Slot} = createSlotFill('DeviceVisibilityControls');
const DeviceVisibilityControls = ({children, clientId}) => {
    return (
        <Fill>
            {children}
        </Fill>
    );
};

DeviceVisibilityControls.Slot = Slot;
export default DeviceVisibilityControls;

and the implementation in the plugin's inspector:

// plugin/inspector.js

<InspectorControls>
  <PanelBody>
   // other controls
   <DeviceVisibilityControls.Slot/>
  </PanelBody>
<InspectorControls>

This feels like one of those situations where I'm missing something simple -- some method of forwarding context or isolating the controls sent for the active control by clientId. Something. Been searching Gutenberg's source, but the solutions there are spread out enough that it's hard to connect the dots (or maybe I've been looking at this too long).

Could use an assist. In reward, I will name my next child after anyone who helps with a solution.

Share Improve this question edited Apr 7 at 8:05 fuxia 107k39 gold badges255 silver badges459 bronze badges asked Apr 4 at 14:15 JerJer 5114 silver badges6 bronze badges 2
  • 1 are you sure that component children isn't the better solution? A slot fill seems like overkill for something that should be handled by basic react semantics, especially for handling descendents. Slot fills are more for when the slot you need to fill is somewhere completely different in the DOM, e.g. the top toolbar, the <head> etc. This is how inspector controls works because the editor sidebar isn't inside the blocks HTML markup, and while you in theory could slot into that, that's not how that API tends to be used – Tom J Nowell Commented Apr 4 at 16:29
  • @TomJNowell Yup. As I mentioned, the InspectorControls is in a block plugin uses editor.BlockEdit to add a panel to every block (that opts in via supports). A few of these blocks have additional controls that logically should appear here. However, because the InspectorControls is in the plugin, the block cannot add children. Think InspectorAdvancedControls -- I'm trying to replicate that pattern. – Jer Commented Apr 5 at 23:24
Add a comment  | 

1 Answer 1

Reset to default 3

I cannot believe I missed this...

A solution (not necessarily the solution) is to retrieve the current block context using useBlockEditContext and compare the ?.clientId to the currently selected block (found with getSelectedBlockClientId) within the Fill.

useBlockEditContext returns information about the block where the hook is being used -- so where the custom Fill wrapper is instantiated.

Here's a working example:

import { useSelect } from '@wordpress/data';
import { store as blockEditorStore, useBlockEditContext } from '@wordpress/block-editor';
import {createSlotFill} from '@wordpress/components';

const {Fill, Slot} = createSlotFill('DeviceVisibilityControls');
const DeviceVisibilityControls = ({children}) => {
    const context = useBlockEditContext();
    const selectedClientId = useSelect(
        ( select ) => select( blockEditorStore ).getSelectedBlockClientId(),
        []
    );
    if( selectedClientId !== context?.clientId){
        return null;
    }
    return (
        <Fill>
            {children}
        </Fill>
    );
};

DeviceVisibilityControls.Slot = Slot;
export default DeviceVisibilityControls;

I'm still not sure this is the best solution, but again, it does appear to be a working solution.

发布评论

评论列表(0)

  1. 暂无评论