-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Description
When working on lazy loading block edit
functions in #55585, I am dealing with a situation where a block already exists (is part of the block tree in the core/block-editor
store), but its <Edit>
UI is not yet mounted, because the edit
component is being lazy loaded. I am trying to come up with a reasonable "universal edit placeholder" that could be displayed while the real edit
is loading, and that could handle keyboard typing in the interim period.
While working on this, one of the surprising findings was that the edit
component doesn't only provide UI for editing, but also defines some basic block behavior. In MVC language, it's not only "view", but also implements part of the "model". Architecturally, that's bad. Some examples:
You can't insert an inner block without edit UI mounted: If you dispatch the insertBlocks
or replaceBlocks
action in the core/block-editor
store, and the edit UI is not mounted, these actions will fail, they won't insert any new blocks. Because the canInsertBlockType
permission check will return false
-- it determines whether the target block supports inner blocks, and it detects that by checking for presence of getBlockListSettings
for that block. But block list settings are set by the edit
React UI, namely the InnerBlocks
component, which calls updateBlockListSettings
on mount.
Default templates are not inserted. Many blocks, when inserted from the slash inserter, will not insert just the block itself, but will also create some default inner structure. List has one inner List Item. Quote and Cover has a Paragraph. Social Links have a set of four default social icons. These are inserted only when the Edit
UI is mounted. Because the insertion is done by an on-mount effect inside useInnerBlockTemplateSync
called in InnerBlocks
.
Inserting blocks with prefix transforms (*
-> list, >
-> quote, ...) does the same job differently -- creating the inner blocks is directly inside the transform functions. Therefore, prefix transforms work correctly when headless.
Splitting and merging list items: List items have custom merge and split behavior, mostly to account for nested sub-lists, but store actions like mergeBlocks
don't know about it because it's defined inside the edit
UI and passed as props to RichText
. Ideally, all this behavior should be present in the merge
function defined on the block type registration.
Then the merging and splitting could be done headlessly, without edit
UI mounted. After all, the core/block-editor
store fully owns and manages the selection, i.e., it knows where the text cursor is, and doesn't need any edit
UI for that. The edit
UI should only bind the split/merge operations to keys like Enter or Backspace.
Block bindings: We are in the middle of developing block attribute bindings, but are we really doing it right? Block bindings should be 100% a "data problem" -- a block attribute is not stored in the block markup as usual, but is sourced from some other data source... That should be happening solely somewhere inside the core/block-editor
store, shouldn't it? The actual edit
UI shouldn't need to be aware where the data come from. And yet we have React components for data synchronization like BlockBindingBridge
or BindingConnector
. That looks suspicious.