: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @param {string} attributeKey The selected block attribute key.
* @param {number} startOffset The start offset.
* @param {number} endOffset The end offset.
* @return {Object} Action object.
function selectionChange(clientId, attributeKey, startOffset, endOffset) {
if (typeof clientId === 'string') {
type: 'SELECTION_CHANGE',
type: 'SELECTION_CHANGE',
* Action that adds a new block of the default type to the block list.
* @param {?Object} attributes Optional attributes of the block to assign.
* @param {?string} rootClientId Optional root client ID of block list on which
* @param {?number} index Optional index where to insert the default block.
const insertDefaultBlock = (attributes, rootClientId, index) => ({
// Abort if there is no default block type (if it has been unregistered).
const defaultBlockName = (0,external_wp_blocks_namespaceObject.getDefaultBlockName)();
const block = (0,external_wp_blocks_namespaceObject.createBlock)(defaultBlockName, attributes);
return dispatch.insertBlock(block, index, rootClientId);
* @typedef {Object< string, Object >} SettingsByClientId
* Action that changes the nested settings of the given block(s).
* @param {string | SettingsByClientId} clientId Client ID of the block whose
* nested setting are being
* received, or object of settings
* @param {Object} settings Object with the new settings
* @return {Object} Action object
function updateBlockListSettings(clientId, settings) {
type: 'UPDATE_BLOCK_LIST_SETTINGS',
* Action that updates the block editor settings.
* @param {Object} settings Updated settings
* @return {Object} Action object
function updateSettings(settings) {
return __experimentalUpdateSettings(settings, {
stripExperimentalSettings: true
* Action that signals that a temporary reusable block has been saved
* in order to switch its temporary id with the real id.
* @param {string} id Reusable block's id.
* @param {string} updatedId Updated block's id.
* @return {Object} Action object.
function __unstableSaveReusableBlock(id, updatedId) {
type: 'SAVE_REUSABLE_BLOCK_SUCCESS',
* Action that marks the last block change explicitly as persistent.
* @return {Object} Action object.
function __unstableMarkLastChangeAsPersistent() {
type: 'MARK_LAST_CHANGE_AS_PERSISTENT'
* Action that signals that the next block change should be marked explicitly as not persistent.
* @return {Object} Action object.
function __unstableMarkNextChangeAsNotPersistent() {
type: 'MARK_NEXT_CHANGE_AS_NOT_PERSISTENT'
* Action that marks the last block change as an automatic change, meaning it was not
* performed by the user, and can be undone using the `Escape` and `Backspace` keys.
* This action must be called after the change was made, and any actions that are a
* consequence of it, so it is recommended to be called at the next idle period to ensure all
* selection changes have been recorded.
const __unstableMarkAutomaticChange = () => ({
type: 'MARK_AUTOMATIC_CHANGE'
requestIdleCallback = cb => setTimeout(cb, 100)
requestIdleCallback(() => {
type: 'MARK_AUTOMATIC_CHANGE_FINAL'
* Action that enables or disables the navigation mode.
* @param {boolean} isNavigationMode Enable/Disable navigation mode.
const setNavigationMode = (isNavigationMode = true) => ({
dispatch.__unstableSetEditorMode(isNavigationMode ? 'navigation' : 'edit');
* Action that sets the editor mode
* @param {string} mode Editor mode
const __unstableSetEditorMode = mode => ({
// When switching to zoom-out mode, we need to select the parent section
if (mode === 'zoom-out') {
const firstSelectedClientId = select.getBlockSelectionStart();
} = unlock(registry.select(STORE_NAME).getSettings());
if (firstSelectedClientId) {
if (sectionRootClientId) {
const sectionClientIds = select.getBlockOrder(sectionRootClientId);
sectionClientId = select.getBlockParents(firstSelectedClientId).find(parent => sectionClientIds.includes(parent));
sectionClientId = select.getBlockHierarchyRootClientId(firstSelectedClientId);
dispatch.selectBlock(sectionClientId);
dispatch.clearSelectedBlock();
if (mode === 'navigation') {
(0,external_wp_a11y_namespaceObject.speak)((0,external_wp_i18n_namespaceObject.__)('You are currently in navigation mode. Navigate blocks using the Tab key and Arrow keys. Use Left and Right Arrow keys to move between nesting levels. To exit navigation mode and edit the selected block, press Enter.'));
} else if (mode === 'edit') {
(0,external_wp_a11y_namespaceObject.speak)((0,external_wp_i18n_namespaceObject.__)('You are currently in edit mode. To return to the navigation mode, press Escape.'));
} else if (mode === 'zoom-out') {
(0,external_wp_a11y_namespaceObject.speak)((0,external_wp_i18n_namespaceObject.__)('You are currently in zoom-out mode.'));
* Action that enables or disables the block moving mode.
* @param {string|null} hasBlockMovingClientId Enable/Disable block moving mode.
const setBlockMovingClientId = (hasBlockMovingClientId = null) => ({
type: 'SET_BLOCK_MOVING_MODE',
if (hasBlockMovingClientId) {
(0,external_wp_a11y_namespaceObject.speak)((0,external_wp_i18n_namespaceObject.__)('Use the Tab key and Arrow keys to choose new block location. Use Left and Right Arrow keys to move between nesting levels. Once location is selected press Enter or Space to move the block.'));
* Action that duplicates a list of blocks.
* @param {string[]} clientIds
* @param {boolean} updateSelection
const duplicateBlocks = (clientIds, updateSelection = true) => ({
if (!clientIds || !clientIds.length) {
// Return early if blocks don't exist.
const blocks = select.getBlocksByClientId(clientIds);
if (blocks.some(block => !block)) {
// Return early if blocks don't support multiple usage.
const blockNames = blocks.map(block => block.name);
if (blockNames.some(blockName => !(0,external_wp_blocks_namespaceObject.hasBlockSupport)(blockName, 'multiple', true))) {
const rootClientId = select.getBlockRootClientId(clientIds[0]);
const clientIdsArray = actions_castArray(clientIds);
const lastSelectedIndex = select.getBlockIndex(clientIdsArray[clientIdsArray.length - 1]);
const clonedBlocks = blocks.map(block => (0,external_wp_blocks_namespaceObject.__experimentalCloneSanitizedBlock)(block));
dispatch.insertBlocks(clonedBlocks, lastSelectedIndex + 1, rootClientId, updateSelection);
if (clonedBlocks.length > 1 && updateSelection) {
dispatch.multiSelect(clonedBlocks[0].clientId, clonedBlocks[clonedBlocks.length - 1].clientId);
return clonedBlocks.map(block => block.clientId);
* Action that inserts a default block before a given block.
* @param {string} clientId
const insertBeforeBlock = clientId => ({
const rootClientId = select.getBlockRootClientId(clientId);
const isLocked = select.getTemplateLock(rootClientId);
const blockIndex = select.getBlockIndex(clientId);
const directInsertBlock = rootClientId ? select.getDirectInsertBlock(rootClientId) : null;
if (!directInsertBlock) {
return dispatch.insertDefaultBlock({}, rootClientId, blockIndex);
const copiedAttributes = {};
if (directInsertBlock.attributesToCopy) {
const attributes = select.getBlockAttributes(clientId);
directInsertBlock.attributesToCopy.forEach(key => {
copiedAttributes[key] = attributes[key];
const block = (0,external_wp_blocks_namespaceObject.createBlock)(directInsertBlock.name, {
...directInsertBlock.attributes,
return dispatch.insertBlock(block, blockIndex, rootClientId);
* Action that inserts a default block after a given block.
* @param {string} clientId
const insertAfterBlock = clientId => ({
const rootClientId = select.getBlockRootClientId(clientId);
const isLocked = select.getTemplateLock(rootClientId);
const blockIndex = select.getBlockIndex(clientId);
const directInsertBlock = rootClientId ? select.getDirectInsertBlock(rootClientId) : null;
if (!directInsertBlock) {
return dispatch.insertDefaultBlock({}, rootClientId, blockIndex + 1);
const copiedAttributes = {};
if (directInsertBlock.attributesToCopy) {
const attributes = select.getBlockAttributes(clientId);
directInsertBlock.attributesToCopy.forEach(key => {
copiedAttributes[key] = attributes[key];
const block = (0,external_wp_blocks_namespaceObject.createBlock)(directInsertBlock.name, {
...directInsertBlock.attributes,
return dispatch.insertBlock(block, blockIndex + 1, rootClientId);
* Action that toggles the highlighted block state.
* @param {string} clientId The block's clientId.
* @param {boolean} isHighlighted The highlight state.
function toggleBlockHighlight(clientId, isHighlighted) {
type: 'TOGGLE_BLOCK_HIGHLIGHT',
* Action that "flashes" the block with a given `clientId` by rhythmically highlighting it.
* @param {string} clientId Target block client ID.
const flashBlock = clientId => async ({
dispatch(toggleBlockHighlight(clientId, true));
await new Promise(resolve => setTimeout(resolve, 150));
dispatch(toggleBlockHighlight(clientId, false));
* Action that sets whether a block has controlled inner blocks.
* @param {string} clientId The block's clientId.
* @param {boolean} hasControlledInnerBlocks True if the block's inner blocks are controlled.
function setHasControlledInnerBlocks(clientId, hasControlledInnerBlocks) {
type: 'SET_HAS_CONTROLLED_INNER_BLOCKS',
hasControlledInnerBlocks,
* Action that sets whether given blocks are visible on the canvas.
* @param {Record<string,boolean>} updates For each block's clientId, its new visibility setting.
function setBlockVisibility(updates) {
type: 'SET_BLOCK_VISIBILITY',
* Action that sets whether a block is being temporarily edited as blocks.
* DO-NOT-USE in production.
* This action is created for internal/experimental only usage and may be
* removed anytime without any warning, causing breakage on any plugin or theme invoking it.
* @param {?string} temporarilyEditingAsBlocks The block's clientId being temporarily edited as blocks.
* @param {?string} focusModeToRevert The focus mode to revert after temporarily edit as blocks finishes.
function __unstableSetTemporarilyEditingAsBlocks(temporarilyEditingAsBlocks, focusModeToRevert) {
type: 'SET_TEMPORARILY_EDITING_AS_BLOCKS',
temporarilyEditingAsBlocks,
* Interface for inserter media requests.
* @typedef {Object} InserterMediaRequest
* @property {number} per_page How many items to fetch per page.
* @property {string} search The search term to use for filtering the results.
* Interface for inserter media responses. Any media resource should
* map their response to this interface, in order to create the core
* WordPress media blocks (image, video, audio).
* @typedef {Object} InserterMediaItem
* @property {string} title The title of the media item.
* @property {string} url The source url of the media item.
* @property {string} [previewUrl] The preview source url of the media item to display in the media list.
* @property {number} [id] The WordPress id of the media item.
* @property {number|string} [sourceId] The id of the media item from external source.
* @property {string} [alt] The alt text of the media item.
* @property {string} [caption] The caption of the media item.
* Registers a new inserter media category. Once registered, the media category is
* available in the inserter's media tab.
* The following interfaces are used:
* - _InserterMediaRequest_ `Object`: Interface for inserter media requests.
* - _per_page_ `number`: How many items to fetch per page.
* - _search_ `string`: The search term to use for filtering the results.
* - _InserterMediaItem_ `Object`: Interface for inserter media responses. Any media resource should
* map their response to this interface, in order to create the core
* WordPress media blocks (image, video, audio).
* - _title_ `string`: The title of the media item.
* - _url_ `string: The source url of the media item.
* - _previewUrl_ `[string]`: The preview source url of the media item to display in the media list.
* - _id_ `[number]`: The WordPress id of the media item.
* - _sourceId_ `[number|string]`: The id of the media item from external source.
* - _alt_ `[string]`: The alt text of the media item.
* - _caption_ `[string]`: The caption of the media item.
* @param {InserterMediaCategory} category The inserter media category to register.
* wp.data.dispatch('core/block-editor').registerInserterMediaCategory( {
* search_items: 'Search Openverse',
* async fetch( query = {} ) {
* excluded_source: 'flickr,inaturalist,wikimedia',
* const finalQuery = { ...query, ...defaultArgs };
* // Sometimes you might need to map the supported request params according to `InserterMediaRequest`.
* // interface. In this example the `search` query param is named `q`.
* const mapFromInserterMediaRequest = {
* const url = new URL( 'https://api.openverse.org/v1/images/' );
* Object.entries( finalQuery ).forEach( ( [ key, value ] ) => {
* const queryKey = mapFromInserterMediaRequest[ key ] || key;
* url.searchParams.set( queryKey, value );
* const response = await window.fetch( url, {
* 'User-Agent': 'WordPress/inserter-media-fetch',
* const jsonResponse = await response.json();
* const results = jsonResponse.results;
* return results.map( ( result ) => ( {
* // If your response result includes an `id` prop that you want to access later, it should
* // be mapped to `InserterMediaItem`'s `sourceId` prop. This can be useful if you provide
* // a report URL getter.
* // Additionally you should always clear the `id` value of your response results because
* // it is used to identify WordPress media items.
* caption: result.caption,
* previewUrl: result.thumbnail,