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( `