diff --git a/blocks/api/registration.js b/blocks/api/registration.js index 5709ca747a6d9..6b4580fa69eb5 100644 --- a/blocks/api/registration.js +++ b/blocks/api/registration.js @@ -62,9 +62,9 @@ export function registerBlockType( name, settings ) { ); return; } - if ( 'edit' in settings && ! isFunction( settings.edit ) ) { + if ( 'edit' in settings && ! isFunction( settings.edit ) && 'string' !== typeof settings.edit ) { console.error( - 'The "edit" property must be a valid function.' + 'The "edit" property must be a valid component or tag name.' ); return; } diff --git a/blocks/api/test/registration.js b/blocks/api/test/registration.js index de02dc61a0427..ac1ac2bc4015d 100644 --- a/blocks/api/test/registration.js +++ b/blocks/api/test/registration.js @@ -82,10 +82,10 @@ describe( 'blocks', () => { expect( block ).toBeUndefined(); } ); - it( 'should reject blocks with an invalid edit function', () => { - const blockType = { save: noop, edit: 'not-a-function', category: 'common', title: 'block title' }, + it( 'should reject blocks with an invalid edit value, must be web component tag name or function', () => { + const blockType = { save: noop, edit: { not: 'valid' }, category: 'common', title: 'block title' }, block = registerBlockType( 'my-plugin/fancy-block-6', blockType ); - expect( console.error ).toHaveBeenCalledWith( 'The "edit" property must be a valid function.' ); + expect( console.error ).toHaveBeenCalledWith( 'The "edit" property must be a valid component or tag name.' ); expect( block ).toBeUndefined(); } ); diff --git a/editor/component-interop/index.js b/editor/component-interop/index.js new file mode 100644 index 0000000000000..58d990067bb4c --- /dev/null +++ b/editor/component-interop/index.js @@ -0,0 +1,79 @@ +/** + * External dependencies + */ +import { isString, includes, forOwn } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +function isWebComponent( tagName ) { + return isString( tagName ) && includes( tagName, '-' ); +} + +class ComponentInterop extends Component { + constructor() { + super( ...arguments ); + + this.bindNode = this.bindNode.bind( this ); + } + + componentDidMount() { + this.setWebComponentProps( this.props ); + } + + componentWillReceiveProps( nextProps ) { + this.setWebComponentProps( nextProps ); + } + + setWebComponentProps( props ) { + const { tagName, attributes, ...properties } = props; + if ( ! isWebComponent( tagName ) ) { + return; + } + + let child = this.node.firstChild; + if ( ! child ) { + child = document.createElement( tagName ); + this.node.appendChild( child ); + } + + forOwn( properties, ( value, key ) => { + child[ key ] = value; + } ); + + forOwn( attributes, ( value, key ) => { + child.setAttribute( key, value ); + } ); + } + + shouldComponentUpdate() { + const { tagName } = this.props; + if ( isWebComponent( tagName ) ) { + return false; + } + + if ( 'function' === typeof tagName && + 'function' === typeof tagName.prototype.shouldComponentUpdate ) { + return tagName.prototype.shouldComponentUpdate.apply( this, arguments ); + } + + return true; + } + + bindNode( node ) { + this.node = node; + } + + render() { + const { tagName: TagName, ...componentProps } = this.props; + if ( isWebComponent( TagName ) ) { + return
; + } + + return ; + } +} + +export default ComponentInterop; diff --git a/editor/modes/visual-editor/block.js b/editor/modes/visual-editor/block.js index c2ca936515e01..fa75936968a3a 100644 --- a/editor/modes/visual-editor/block.js +++ b/editor/modes/visual-editor/block.js @@ -26,6 +26,7 @@ import BlockDropZone from './block-drop-zone'; import BlockMover from '../../block-mover'; import BlockRightMenu from '../../block-settings-menu'; import BlockSwitcher from '../../block-switcher'; +import ComponentInterop from '../../component-interop'; import { updateBlockAttributes, focusBlock, @@ -381,7 +382,8 @@ class VisualEditorBlock extends Component { { isValid ? ( -