diff --git a/packages/block-editor/src/components/block-tools/selected-block-popover.js b/packages/block-editor/src/components/block-tools/selected-block-popover.js index 294813b1f34a15..db321d6c882c03 100644 --- a/packages/block-editor/src/components/block-tools/selected-block-popover.js +++ b/packages/block-editor/src/components/block-tools/selected-block-popover.js @@ -35,6 +35,7 @@ function selector( select ) { return { editorMode: __unstableGetEditorMode(), + hasMultiSelection: hasMultiSelection(), isMultiSelecting: isMultiSelecting(), isTyping: isTyping(), isBlockInterfaceHidden: isBlockInterfaceHidden(), @@ -57,6 +58,7 @@ function SelectedBlockPopover( { } ) { const { editorMode, + hasMultiSelection, isMultiSelecting, isTyping, isBlockInterfaceHidden, @@ -89,7 +91,8 @@ function SelectedBlockPopover( { const showEmptyBlockSideInserter = ! isTyping && editorMode === 'edit' && isEmptyDefaultBlock; const shouldShowBreadcrumb = - editorMode === 'navigation' || editorMode === 'zoom-out'; + ! hasMultiSelection && + ( editorMode === 'navigation' || editorMode === 'zoom-out' ); const shouldShowContextualToolbar = editorMode === 'edit' && ! hasFixedToolbar && @@ -129,86 +132,79 @@ function SelectedBlockPopover( { clientId, } ); - if ( - ! shouldShowBreadcrumb && - ! shouldShowContextualToolbar && - ! showEmptyBlockSideInserter - ) { - return null; + if ( showEmptyBlockSideInserter ) { + return ( + +
+ +
+
+ ); } - return ( - <> - { showEmptyBlockSideInserter && ( - + { shouldShowContextualToolbar && showContents && ( + -
- -
-
- ) } - { ( shouldShowBreadcrumb || shouldShowContextualToolbar ) && ( - - { shouldShowContextualToolbar && showContents && ( - { - initialToolbarItemIndexRef.current = index; - } } - // Resets the index whenever the active block changes so - // this is not persisted. See https://github.com/WordPress/gutenberg/pull/25760#issuecomment-717906169 - key={ clientId } - /> - ) } - { shouldShowBreadcrumb && ( - - ) } - - ) } - - ); + __experimentalOnIndexChange={ ( index ) => { + initialToolbarItemIndexRef.current = index; + } } + // Resets the index whenever the active block changes so + // this is not persisted. See https://github.com/WordPress/gutenberg/pull/25760#issuecomment-717906169 + key={ clientId } + /> + ) } + { shouldShowBreadcrumb && ( + + ) } + + ); + } + + return null; } function wrapperSelector( select ) { diff --git a/packages/block-editor/src/components/rich-text/use-before-input-rules.js b/packages/block-editor/src/components/rich-text/use-before-input-rules.js index 762a8e8d990bf5..f6f036f039b504 100644 --- a/packages/block-editor/src/components/rich-text/use-before-input-rules.js +++ b/packages/block-editor/src/components/rich-text/use-before-input-rules.js @@ -77,9 +77,17 @@ export function useBeforeInputRules( props ) { const { defaultView } = ownerDocument; const newEvent = new defaultView.InputEvent( 'input', init ); - // Dispatch an input event with the new data. This will trigger the + // Dispatch an `input` event with the new data. This will trigger the // input rules. - event.target.dispatchEvent( newEvent ); + // Postpone the `input` to the next event loop tick so that the dispatch + // doesn't happen synchronously in the middle of `beforeinput` dispatch. + // This is closer to how native `input` event would be timed, and also + // makes sure that the `input` event is dispatched only after the `onChange` + // call few lines above has fully updated the data store state and rerendered + // all affected components. + window.queueMicrotask( () => { + event.target.dispatchEvent( newEvent ); + } ); event.preventDefault(); } diff --git a/packages/components/src/slot-fill/bubbles-virtually/fill.js b/packages/components/src/slot-fill/bubbles-virtually/fill.js index c18d745ca94688..d13359eed7fb52 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/fill.js +++ b/packages/components/src/slot-fill/bubbles-virtually/fill.js @@ -15,6 +15,7 @@ function useForceUpdate() { const mounted = useRef( true ); useEffect( () => { + mounted.current = true; return () => { mounted.current = false; }; diff --git a/packages/components/src/slot-fill/slot.js b/packages/components/src/slot-fill/slot.js index 50ff143bf227e5..a314047600bd32 100644 --- a/packages/components/src/slot-fill/slot.js +++ b/packages/components/src/slot-fill/slot.js @@ -34,7 +34,7 @@ class SlotComponent extends Component { componentDidMount() { const { registerSlot } = this.props; - + this.isUnmounted = false; registerSlot( this.props.name, this ); } diff --git a/packages/components/src/slot-fill/use-slot.js b/packages/components/src/slot-fill/use-slot.js index 99e7138e120259..fce7c651495d5b 100644 --- a/packages/components/src/slot-fill/use-slot.js +++ b/packages/components/src/slot-fill/use-slot.js @@ -2,7 +2,7 @@ /** * WordPress dependencies */ -import { useContext, useState, useEffect } from '@wordpress/element'; +import { useContext, useSyncExternalStore } from '@wordpress/element'; /** * Internal dependencies @@ -17,21 +17,11 @@ import SlotFillContext from './context'; */ const useSlot = ( name ) => { const { getSlot, subscribe } = useContext( SlotFillContext ); - const [ slot, setSlot ] = useState( getSlot( name ) ); - - useEffect( () => { - setSlot( getSlot( name ) ); - const unsubscribe = subscribe( () => { - setSlot( getSlot( name ) ); - } ); - - return unsubscribe; - // Ignore reason: Modifying this dep array could introduce unexpected changes in behavior, - // so we'll leave it as=is until the hook can be properly refactored for exhaustive-deps. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ name ] ); - - return slot; + return useSyncExternalStore( + subscribe, + () => getSlot( name ), + () => getSlot( name ) + ); }; export default useSlot; diff --git a/packages/compose/src/hooks/use-async-list/index.ts b/packages/compose/src/hooks/use-async-list/index.ts index 732f9d92c8e971..0e88ae2a562ce6 100644 --- a/packages/compose/src/hooks/use-async-list/index.ts +++ b/packages/compose/src/hooks/use-async-list/index.ts @@ -44,7 +44,7 @@ function useAsyncList< T >( config: AsyncListConfig = { step: 1 } ): T[] { const { step = 1 } = config; - const [ current, setCurrent ] = useState( [] as T[] ); + const [ current, setCurrent ] = useState< T[] >( [] ); useEffect( () => { // On reset, we keep the first items that were previously rendered. @@ -55,10 +55,9 @@ function useAsyncList< T >( ); } setCurrent( firstItems ); - let nextIndex = firstItems.length; const asyncQueue = createQueue(); - const append = () => { + const append = ( nextIndex: number ) => () => { if ( list.length <= nextIndex ) { return; } @@ -66,10 +65,9 @@ function useAsyncList< T >( ...state, ...list.slice( nextIndex, nextIndex + step ), ] ); - nextIndex += step; - asyncQueue.add( {}, append ); + asyncQueue.add( {}, append( nextIndex + step ) ); }; - asyncQueue.add( {}, append ); + asyncQueue.add( {}, append( firstItems.length ) ); return () => asyncQueue.reset(); }, [ list ] ); diff --git a/packages/e2e-tests/specs/editor/various/rich-text.test.js b/packages/e2e-tests/specs/editor/various/rich-text.test.js index 36d85e98cae1dc..009abf121e351f 100644 --- a/packages/e2e-tests/specs/editor/various/rich-text.test.js +++ b/packages/e2e-tests/specs/editor/various/rich-text.test.js @@ -515,11 +515,15 @@ describe( 'RichText', () => { // text in the DOM directly, setting selection in the right place, and // firing `compositionend`. // See https://github.com/puppeteer/puppeteer/issues/4981. - await page.evaluate( () => { + await page.evaluate( async () => { document.activeElement.textContent = '`a`'; const selection = window.getSelection(); + // The `selectionchange` and `compositionend` events should run in separate event + // loop ticks to process all data store updates in time. Native events would be + // scheduled the same way. selection.selectAllChildren( document.activeElement ); selection.collapseToEnd(); + await new Promise( ( r ) => setTimeout( r, 0 ) ); document.activeElement.dispatchEvent( new CompositionEvent( 'compositionend' ) ); diff --git a/packages/edit-post/README.md b/packages/edit-post/README.md index 048db51d88cee7..d9452423be8f31 100644 --- a/packages/edit-post/README.md +++ b/packages/edit-post/README.md @@ -478,17 +478,7 @@ _Returns_ ### reinitializeEditor -Reinitializes the editor after the user chooses to reboot the editor after -an unhandled error occurs, replacing previously mounted editor element using -an initial state from prior to the crash. - -_Parameters_ - -- _postType_ `Object`: Post type of the post to edit. -- _postId_ `Object`: ID of the post to edit. -- _target_ `Element`: DOM node in which editor is rendered. -- _settings_ `?Object`: Editor settings object. -- _initialEdits_ `Object`: Programmatic edits to apply initially, to be considered as non-user-initiated (bypass for unsaved changes prompt). +Used to reinitialize the editor after an error. Now it's a deprecated noop function. ### store diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index 7fa7f33e9ef6e6..03620a5021b4a2 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -9,7 +9,7 @@ import { store as editorStore, experiments as editorExperiments, } from '@wordpress/editor'; -import { StrictMode, useMemo } from '@wordpress/element'; +import { useMemo } from '@wordpress/element'; import { SlotFillProvider } from '@wordpress/components'; import { store as coreStore } from '@wordpress/core-data'; import { ShortcutProvider } from '@wordpress/keyboard-shortcuts'; @@ -25,14 +25,7 @@ import { unlock } from './experiments'; const { ExperimentalEditorProvider } = unlock( editorExperiments ); -function Editor( { - postId, - postType, - settings, - initialEdits, - onError, - ...props -} ) { +function Editor( { postId, postType, settings, initialEdits, ...props } ) { const { hasFixedToolbar, focusMode, @@ -180,28 +173,24 @@ function Editor( { } return ( - - - - - - - - - - - - - + + + + + + + + + + + ); } diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 2ee4dcdab17049..d2889c407690a7 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -6,7 +6,8 @@ import { registerCoreBlocks, __experimentalRegisterExperimentalCoreBlocks, } from '@wordpress/block-library'; -import { render, unmountComponentAtNode } from '@wordpress/element'; +import deprecated from '@wordpress/deprecated'; +import { createRoot } from '@wordpress/element'; import { dispatch, select } from '@wordpress/data'; import { addFilter } from '@wordpress/hooks'; import { store as preferencesStore } from '@wordpress/preferences'; @@ -20,49 +21,6 @@ import './plugins'; import Editor from './editor'; import { store as editPostStore } from './store'; -/** - * Reinitializes the editor after the user chooses to reboot the editor after - * an unhandled error occurs, replacing previously mounted editor element using - * an initial state from prior to the crash. - * - * @param {Object} postType Post type of the post to edit. - * @param {Object} postId ID of the post to edit. - * @param {Element} target DOM node in which editor is rendered. - * @param {?Object} settings Editor settings object. - * @param {Object} initialEdits Programmatic edits to apply initially, to be - * considered as non-user-initiated (bypass for - * unsaved changes prompt). - */ -export function reinitializeEditor( - postType, - postId, - target, - settings, - initialEdits -) { - unmountComponentAtNode( target ); - const reboot = reinitializeEditor.bind( - null, - postType, - postId, - target, - settings, - initialEdits - ); - - render( - , - target - ); -} - /** * Initializes and returns an instance of Editor. * @@ -82,14 +40,7 @@ export function initializeEditor( initialEdits ) { const target = document.getElementById( id ); - const reboot = reinitializeEditor.bind( - null, - postType, - postId, - target, - settings, - initialEdits - ); + const root = createRoot( target ); dispatch( preferencesStore ).setDefaults( 'core/edit-post', { editorMode: 'visual', @@ -187,16 +138,26 @@ export function initializeEditor( window.addEventListener( 'dragover', ( e ) => e.preventDefault(), false ); window.addEventListener( 'drop', ( e ) => e.preventDefault(), false ); - render( + root.render( , - target + /> ); + + return root; +} + +/** + * Used to reinitialize the editor after an error. Now it's a deprecated noop function. + */ +export function reinitializeEditor() { + deprecated( 'wp.editPost.reinitializeEditor', { + since: '6.2', + version: '6.3', + } ); } export { default as PluginBlockSettingsMenuItem } from './components/block-settings-menu/plugin-block-settings-menu-item'; diff --git a/packages/edit-site/src/components/app/index.js b/packages/edit-site/src/components/app/index.js index c86743bc598955..479aebf0d5361e 100644 --- a/packages/edit-site/src/components/app/index.js +++ b/packages/edit-site/src/components/app/index.js @@ -15,7 +15,7 @@ import { PluginArea } from '@wordpress/plugins'; import { Routes } from '../routes'; import Layout from '../layout'; -export default function App( { reboot } ) { +export default function App() { const { createErrorNotice } = useDispatch( noticesStore ); function onPluginAreaError( name ) { @@ -37,7 +37,7 @@ export default function App( { reboot } ) { - + diff --git a/packages/edit-site/src/components/error-boundary/index.js b/packages/edit-site/src/components/error-boundary/index.js index 9e07afafd04a4e..88b04ae0b40e34 100644 --- a/packages/edit-site/src/components/error-boundary/index.js +++ b/packages/edit-site/src/components/error-boundary/index.js @@ -14,8 +14,6 @@ export default class ErrorBoundary extends Component { constructor() { super( ...arguments ); - this.reboot = this.reboot.bind( this ); - this.state = { error: null, }; @@ -29,13 +27,8 @@ export default class ErrorBoundary extends Component { return { error }; } - reboot() { - this.props.onError(); - } - render() { - const { error } = this.state; - if ( ! error ) { + if ( ! this.state.error ) { return this.props.children; } @@ -44,8 +37,7 @@ export default class ErrorBoundary extends Component { message={ __( 'The editor has encountered an unexpected error.' ) } - error={ error } - reboot={ this.reboot } + error={ this.state.error } /> ); } diff --git a/packages/edit-site/src/components/error-boundary/warning.js b/packages/edit-site/src/components/error-boundary/warning.js index 4abf84f224c7ab..af51b43be3431e 100644 --- a/packages/edit-site/src/components/error-boundary/warning.js +++ b/packages/edit-site/src/components/error-boundary/warning.js @@ -15,41 +15,12 @@ function CopyButton( { text, children } ) { ); } -export default function ErrorBoundaryWarning( { - message, - error, - reboot, - dashboardLink, -} ) { - const actions = []; - - if ( reboot ) { - actions.push( - - ); - } - - if ( error ) { - actions.push( - - { __( 'Copy Error' ) } - - ); - } - - if ( dashboardLink ) { - actions.push( - - ); - } +export default function ErrorBoundaryWarning( { message, error } ) { + const actions = [ + + { __( 'Copy Error' ) } + , + ]; return ( diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 1ff34899ca121f..b50b8d23ab0765 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -52,7 +52,7 @@ const emptyResizeHandleStyles = { left: undefined, }; -export default function Layout( { onError } ) { +export default function Layout() { // This ensures the edited entity id and type are initialized properly. useInitEditedEntityFromURL(); useSyncCanvasModeWithURL(); @@ -314,7 +314,7 @@ export default function Layout( { onError } ) { ease: 'easeOut', } } > - + { isEditorPage && } { isListPage && } diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index d2cbb2f4aa04d2..390d9d314d4906 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -7,7 +7,8 @@ import { __experimentalRegisterExperimentalCoreBlocks, } from '@wordpress/block-library'; import { dispatch } from '@wordpress/data'; -import { render, unmountComponentAtNode } from '@wordpress/element'; +import deprecated from '@wordpress/deprecated'; +import { createRoot } from '@wordpress/element'; import { __experimentalFetchLinkSuggestions as fetchLinkSuggestions, __experimentalFetchUrlData as fetchUrlData, @@ -26,14 +27,27 @@ import { store as editSiteStore } from './store'; import App from './components/app'; /** - * Reinitializes the editor after the user chooses to reboot the editor after - * an unhandled error occurs, replacing previously mounted editor element using - * an initial state from prior to the crash. + * Initializes the site editor screen. * - * @param {Element} target DOM node in which editor is rendered. - * @param {?Object} settings Editor settings object. + * @param {string} id ID of the root element to render the screen in. + * @param {Object} settings Editor settings. */ -export function reinitializeEditor( target, settings ) { +export function initializeEditor( id, settings ) { + const target = document.getElementById( id ); + const root = createRoot( target ); + + settings.__experimentalFetchLinkSuggestions = ( search, searchOptions ) => + fetchLinkSuggestions( search, searchOptions, settings ); + settings.__experimentalFetchRichUrlData = fetchUrlData; + + dispatch( blocksStore ).__experimentalReapplyBlockTypeFilters(); + registerCoreBlocks(); + registerLegacyWidgetBlock( { inserter: false } ); + if ( process.env.IS_GUTENBERG_PLUGIN ) { + __experimentalRegisterExperimentalCoreBlocks( { + enableFSEBlocks: true, + } ); + } /* * Prevent adding the Clasic block in the site editor. * Only add the filter when the site editor is initialized, not imported. @@ -54,70 +68,48 @@ export function reinitializeEditor( target, settings ) { } ); - // This will be a no-op if the target doesn't have any React nodes. - unmountComponentAtNode( target ); - const reboot = reinitializeEditor.bind( null, target, settings ); - // We dispatch actions and update the store synchronously before rendering // so that we won't trigger unnecessary re-renders with useEffect. - { - dispatch( preferencesStore ).setDefaults( 'core/edit-site', { - editorMode: 'visual', - fixedToolbar: false, - focusMode: false, - keepCaretInsideBlock: false, - welcomeGuide: true, - welcomeGuideStyles: true, - showListViewByDefault: false, - } ); + dispatch( preferencesStore ).setDefaults( 'core/edit-site', { + editorMode: 'visual', + fixedToolbar: false, + focusMode: false, + keepCaretInsideBlock: false, + welcomeGuide: true, + welcomeGuideStyles: true, + showListViewByDefault: false, + } ); - dispatch( interfaceStore ).setDefaultComplementaryArea( - 'core/edit-site', - 'edit-site/template' - ); + dispatch( interfaceStore ).setDefaultComplementaryArea( + 'core/edit-site', + 'edit-site/template' + ); - dispatch( editSiteStore ).updateSettings( settings ); + dispatch( editSiteStore ).updateSettings( settings ); - // Keep the defaultTemplateTypes in the core/editor settings too, - // so that they can be selected with core/editor selectors in any editor. - // This is needed because edit-site doesn't initialize with EditorProvider, - // which internally uses updateEditorSettings as well. - dispatch( editorStore ).updateEditorSettings( { - defaultTemplateTypes: settings.defaultTemplateTypes, - defaultTemplatePartAreas: settings.defaultTemplatePartAreas, - } ); - } + // Keep the defaultTemplateTypes in the core/editor settings too, + // so that they can be selected with core/editor selectors in any editor. + // This is needed because edit-site doesn't initialize with EditorProvider, + // which internally uses updateEditorSettings as well. + dispatch( editorStore ).updateEditorSettings( { + defaultTemplateTypes: settings.defaultTemplateTypes, + defaultTemplatePartAreas: settings.defaultTemplatePartAreas, + } ); // Prevent the default browser action for files dropped outside of dropzones. window.addEventListener( 'dragover', ( e ) => e.preventDefault(), false ); window.addEventListener( 'drop', ( e ) => e.preventDefault(), false ); - render( , target ); -} - -/** - * Initializes the site editor screen. - * - * @param {string} id ID of the root element to render the screen in. - * @param {Object} settings Editor settings. - */ -export function initializeEditor( id, settings ) { - settings.__experimentalFetchLinkSuggestions = ( search, searchOptions ) => - fetchLinkSuggestions( search, searchOptions, settings ); - settings.__experimentalFetchRichUrlData = fetchUrlData; + root.render( ); - const target = document.getElementById( id ); - - dispatch( blocksStore ).__experimentalReapplyBlockTypeFilters(); - registerCoreBlocks(); - registerLegacyWidgetBlock( { inserter: false } ); - if ( process.env.IS_GUTENBERG_PLUGIN ) { - __experimentalRegisterExperimentalCoreBlocks( { - enableFSEBlocks: true, - } ); - } + return root; +} - reinitializeEditor( target, settings ); +export function reinitializeEditor() { + deprecated( 'wp.editSite.reinitializeEditor', { + since: '6.2', + version: '6.3', + } ); } export { default as PluginSidebar } from './components/sidebar-edit-mode/plugin-sidebar'; diff --git a/packages/edit-widgets/src/components/error-boundary/index.js b/packages/edit-widgets/src/components/error-boundary/index.js index 8b941630b347c0..5bb7a44fea4135 100644 --- a/packages/edit-widgets/src/components/error-boundary/index.js +++ b/packages/edit-widgets/src/components/error-boundary/index.js @@ -17,51 +17,49 @@ function CopyButton( { text, children } ) { ); } +function ErrorBoundaryWarning( { message, error } ) { + const actions = [ + + { __( 'Copy Error' ) } + , + ]; + + return ( + + { message } + + ); +} + export default class ErrorBoundary extends Component { constructor() { super( ...arguments ); - this.reboot = this.reboot.bind( this ); - this.state = { error: null, }; } componentDidCatch( error ) { - this.setState( { error } ); - doAction( 'editor.ErrorBoundary.errorLogged', error ); } - reboot() { - this.props.onError(); + static getDerivedStateFromError( error ) { + return { error }; } render() { - const { error } = this.state; - if ( ! error ) { + if ( ! this.state.error ) { return this.props.children; } return ( - - { __( 'Attempt Recovery' ) } - , - - { __( 'Copy Error' ) } - , - ] } - > - { __( 'The editor has encountered an unexpected error.' ) } - + ); } } diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index 08c432664c4d3f..3a73bcda1a2660 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -17,7 +17,7 @@ import Interface from './interface'; import UnsavedChangesWarning from './unsaved-changes-warning'; import WelcomeGuide from '../welcome-guide'; -function Layout( { blockEditorSettings, onError } ) { +function Layout( { blockEditorSettings } ) { const { createErrorNotice } = useDispatch( noticesStore ); function onPluginAreaError( name ) { @@ -33,7 +33,7 @@ function Layout( { blockEditorSettings, onError } ) { } return ( - + diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index 9fa62f7909f163..bae80786b64140 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -8,7 +8,8 @@ import { store as blocksStore, } from '@wordpress/blocks'; import { dispatch } from '@wordpress/data'; -import { render, unmountComponentAtNode } from '@wordpress/element'; +import deprecated from '@wordpress/deprecated'; +import { createRoot } from '@wordpress/element'; import { registerCoreBlocks, __experimentalGetCoreBlocks, @@ -42,32 +43,16 @@ const disabledBlocks = [ ...( ALLOW_REUSABLE_BLOCKS ? [] : [ 'core/block' ] ), ]; -/** - * Reinitializes the editor after the user chooses to reboot the editor after - * an unhandled error occurs, replacing previously mounted editor element using - * an initial state from prior to the crash. - * - * @param {Element} target DOM node in which editor is rendered. - * @param {?Object} settings Editor settings object. - */ -export function reinitializeEditor( target, settings ) { - unmountComponentAtNode( target ); - const reboot = reinitializeEditor.bind( null, target, settings ); - render( - , - target - ); -} - /** * Initializes the block editor in the widgets screen. * * @param {string} id ID of the root element to render the screen in. * @param {Object} settings Block editor settings. */ -export function initialize( id, settings ) { +export function initializeEditor( id, settings ) { const target = document.getElementById( id ); - const reboot = reinitializeEditor.bind( null, target, settings ); + const root = createRoot( target ); + const coreBlocks = __experimentalGetCoreBlocks().filter( ( block ) => { return ! ( disabledBlocks.includes( block.name ) || @@ -105,10 +90,22 @@ export function initialize( id, settings ) { // do this will result in errors in the default block parser. // see: https://github.com/WordPress/gutenberg/issues/33097 setFreeformContentHandlerName( 'core/html' ); - render( - , - target - ); + + root.render( ); + + return root; +} + +/** + * Compatibility export under the old `initialize` name. + */ +export const initialize = initializeEditor; + +export function reinitializeEditor() { + deprecated( 'wp.editWidgets.reinitializeEditor', { + since: '6.2', + version: '6.3', + } ); } /** diff --git a/packages/editor/src/components/error-boundary/index.js b/packages/editor/src/components/error-boundary/index.js index 72e6a7680421f3..0b5158ff7b9e55 100644 --- a/packages/editor/src/components/error-boundary/index.js +++ b/packages/editor/src/components/error-boundary/index.js @@ -14,6 +14,18 @@ import { doAction } from '@wordpress/hooks'; */ import { store as editorStore } from '../../store'; +function getContent() { + try { + // While `select` in a component is generally discouraged, it is + // used here because it (a) reduces the chance of data loss in the + // case of additional errors by performing a direct retrieval and + // (b) avoids the performance cost associated with unnecessary + // content serialization throughout the lifetime of a non-erroring + // application. + return select( editorStore ).getEditedPostContent(); + } catch ( error ) {} +} + function CopyButton( { text, children } ) { const ref = useCopyToClipboard( text ); return ( @@ -27,34 +39,17 @@ class ErrorBoundary extends Component { constructor() { super( ...arguments ); - this.reboot = this.reboot.bind( this ); - this.getContent = this.getContent.bind( this ); - this.state = { error: null, }; } componentDidCatch( error ) { - this.setState( { error } ); - doAction( 'editor.ErrorBoundary.errorLogged', error ); } - reboot() { - this.props.onError(); - } - - getContent() { - try { - // While `select` in a component is generally discouraged, it is - // used here because it (a) reduces the chance of data loss in the - // case of additional errors by performing a direct retrieval and - // (b) avoids the performance cost associated with unnecessary - // content serialization throughout the lifetime of a non-erroring - // application. - return select( editorStore ).getEditedPostContent(); - } catch ( error ) {} + static getDerivedStateFromError( error ) { + return { error }; } render() { @@ -63,25 +58,17 @@ class ErrorBoundary extends Component { return this.props.children; } + const actions = [ + + { __( 'Copy Post Text' ) } + , + + { __( 'Copy Error' ) } + , + ]; + return ( - - { __( 'Attempt Recovery' ) } - , - - { __( 'Copy Post Text' ) } - , - - { __( 'Copy Error' ) } - , - ] } - > + { __( 'The editor has encountered an unexpected error.' ) } ); diff --git a/test/e2e/specs/editor/various/font-size-picker.spec.js b/test/e2e/specs/editor/various/font-size-picker.spec.js index 69ffa68e602cf6..09896a34c13fe0 100644 --- a/test/e2e/specs/editor/various/font-size-picker.spec.js +++ b/test/e2e/specs/editor/various/font-size-picker.spec.js @@ -8,6 +8,16 @@ test.describe( 'Font Size Picker', () => { await admin.createNewPost(); } ); + test.afterEach( async ( { page } ) => { + const closeButton = page.locator( + 'role=region[name="Editor settings"i] >> role=button[name^="Close settings"i]' + ); + + if ( await closeButton.isVisible() ) { + await closeButton.click(); + } + } ); + test.describe( 'Common', () => { test( 'should apply a named font size using the font size input', async ( { editor, @@ -183,6 +193,7 @@ test.describe( 'Font Size Picker', () => { await page.click( 'role=button[name="Typography options"i]' ); await page.click( 'role=menuitem[name="Reset Font size"i]' ); + await page.keyboard.press( 'Escape' ); // Close the menu await expect.poll( editor.getEditedPostContent ) .toBe( ` @@ -263,6 +274,7 @@ test.describe( 'Font Size Picker', () => { await page.click( 'role=button[name="Typography options"i]' ); await page.click( 'role=menuitem[name="Reset Font size"i]' ); + await page.keyboard.press( 'Escape' ); // Close the menu await expect.poll( editor.getEditedPostContent ) .toBe( `