-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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 a generic block editor module #13088
Changes from 1 commit
150e0ed
44ca3aa
f6a0e17
3e0fc58
25772de
2d06c4f
ba7b92d
30d4f81
d904406
f1dff88
97bdb82
3a0ee11
5aa7de7
01a154c
d463228
14e2dfa
3ebc2c6
724b38f
bd355c5
9aecc91
184807b
123f7b2
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 |
---|---|---|
|
@@ -3,14 +3,37 @@ | |
*/ | ||
import { Component } from '@wordpress/element'; | ||
import { DropZoneProvider, SlotFillProvider } from '@wordpress/components'; | ||
import { withDispatch, withSelect } from '@wordpress/data'; | ||
import { compose } from '@wordpress/compose'; | ||
import { withDispatch, RegistryConsumer } from '@wordpress/data'; | ||
import { createHigherOrderComponent, compose } from '@wordpress/compose'; | ||
|
||
/** | ||
* Higher-order component which renders the original component with the current | ||
* registry context passed as its `registry` prop. | ||
* | ||
* @param {WPComponent} OriginalComponent Original component. | ||
* | ||
* @return {WPComponent} Enhanced component. | ||
*/ | ||
const withRegistry = createHigherOrderComponent( | ||
( OriginalComponent ) => ( props ) => ( | ||
<RegistryConsumer> | ||
{ ( registry ) => ( | ||
<OriginalComponent | ||
{ ...props } | ||
registry={ registry } | ||
/> | ||
) } | ||
</RegistryConsumer> | ||
), | ||
'withRegistry' | ||
); | ||
|
||
class BlockEditorProvider extends Component { | ||
componentDidMount() { | ||
this.isSyncingBlockValue = true; | ||
this.props.updateEditorSettings( this.props.settings ); | ||
this.isSyncingIncomingValue = true; | ||
this.props.resetBlocks( this.props.value ); | ||
this.attachChangeObserver( this.props.registry ); | ||
} | ||
|
||
componentDidUpdate( prevProps ) { | ||
|
@@ -19,32 +42,88 @@ class BlockEditorProvider extends Component { | |
updateEditorSettings, | ||
value, | ||
resetBlocks, | ||
blocks, | ||
onInput, | ||
onChange, | ||
isLastBlockChangePersistent, | ||
registry, | ||
} = this.props; | ||
|
||
if ( settings !== prevProps.settings ) { | ||
updateEditorSettings( settings ); | ||
} | ||
|
||
if ( this.isSyncingBlockValue ) { | ||
this.isSyncingBlockValue = false; | ||
} else if ( blocks !== prevProps.blocks ) { | ||
this.isSyncingBlockValue = true; | ||
if ( registry !== prevProps.registry ) { | ||
this.attachChangeObserver( registry ); | ||
} | ||
|
||
if ( isLastBlockChangePersistent ) { | ||
onChange( blocks ); | ||
} else { | ||
onInput( blocks ); | ||
} | ||
if ( this.isSyncingOutcomingValue ) { | ||
this.isSyncingOutcomingValue = false; | ||
} else if ( value !== prevProps.value ) { | ||
this.isSyncingBlockValue = true; | ||
this.isSyncingIncomingValue = true; | ||
resetBlocks( value ); | ||
} | ||
} | ||
|
||
componentWillUnmount() { | ||
if ( this.unsubscribe ) { | ||
this.unsubscribe(); | ||
} | ||
} | ||
|
||
/** | ||
* Given a registry object, overrides the default dispatch behavior for the | ||
* `core/block-editor` store to interpret a state change and decide whether | ||
* we should call `onChange` or `onInput` depending on whether the change | ||
* is persistent or not. | ||
* | ||
* This needs to be done synchronously after state changes (instead of using | ||
* `componentDidUpdate`) in order to avoid batching these changes. | ||
* | ||
* @param {WPDataRegistry} registry Registry from which block editor | ||
* dispatch is to be overriden. | ||
*/ | ||
attachChangeObserver( registry ) { | ||
if ( this.unsubscribe ) { | ||
this.unsubscribe(); | ||
} | ||
|
||
const { | ||
getBlocks, | ||
isLastBlockChangePersistent, | ||
} = registry.select( 'core/block-editor' ); | ||
|
||
let blocks = getBlocks(); | ||
let isPersistent = isLastBlockChangePersistent(); | ||
|
||
this.unsubscribe = registry.subscribe( () => { | ||
const { | ||
onChange, | ||
onInput, | ||
} = this.props; | ||
const newBlocks = getBlocks(); | ||
const newIsPersistent = isLastBlockChangePersistent(); | ||
if ( newBlocks !== blocks && this.isSyncingIncomingValue ) { | ||
this.isSyncingIncomingValue = false; | ||
blocks = newBlocks; | ||
isPersistent = newIsPersistent; | ||
return; | ||
} | ||
|
||
if ( | ||
newBlocks !== blocks || | ||
// This happens when a previous input is explicitely marked as persistent. | ||
( newIsPersistent && ! isPersistent ) | ||
) { | ||
blocks = newBlocks; | ||
isPersistent = newIsPersistent; | ||
|
||
this.isSyncingOutcomingValue = true; | ||
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. Something I was starting to not like about my previous implementation was having a hard assumption that the parent component was rendering 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. I agree with you and this is what was causing the e2e tests without the last commit but it was specific to the initial call. If we want it to be less fragile, we'd have to call |
||
if ( isPersistent ) { | ||
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. This being an Depending how much we care to align to the default DOM input behaviors (important for setting expectations), a changed input fires both https://codepen.io/aduth/pen/exboNb Edit: Or I guess, a better way to put it, in the DOM, a 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. This was essentially my way of making the value change and the history level fire on the same |
||
onChange( blocks ); | ||
} else { | ||
onInput( blocks ); | ||
} | ||
} | ||
} ); | ||
} | ||
|
||
render() { | ||
const { children } = this.props; | ||
|
||
|
@@ -59,17 +138,6 @@ class BlockEditorProvider extends Component { | |
} | ||
|
||
export default compose( [ | ||
withSelect( ( select ) => { | ||
const { | ||
getBlocks, | ||
isLastBlockChangePersistent, | ||
} = select( 'core/block-editor' ); | ||
|
||
return { | ||
blocks: getBlocks(), | ||
isLastBlockChangePersistent: isLastBlockChangePersistent(), | ||
}; | ||
} ), | ||
withDispatch( ( dispatch ) => { | ||
const { | ||
updateEditorSettings, | ||
|
@@ -81,4 +149,5 @@ export default compose( [ | |
resetBlocks, | ||
}; | ||
} ), | ||
withRegistry, | ||
] )( BlockEditorProvider ); |
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.
Aside: It seems a common enough pattern that we should consider one or both of opting for
useContext
hook from React 16.8.0, and/or our own implementation of awithContext
higher-order component which would behave similarly.The main difference with a higher-order component is that we'd need to instruct the prop name to which the context value should be assigned. One or both of:
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.
Maybe we should try hooks for a moment and see whether adding higher order components still make sense or not.