@atlaskit/editor-plugin-block-controls
Version:
Block controls plugin for @atlaskit/editor-core
486 lines (477 loc) • 24.7 kB
JavaScript
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
import { expandedState } from '@atlaskit/editor-common/expand';
import { blockControlsMessages } from '@atlaskit/editor-common/messages';
import { expandSelectionBounds, GapCursorSelection } from '@atlaskit/editor-common/selection';
import { transformSliceNestedExpandToExpand } from '@atlaskit/editor-common/transforms';
import { DIRECTION } from '@atlaskit/editor-common/types';
import { isEmptyParagraph } from '@atlaskit/editor-common/utils';
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
import { NodeSelection, Selection } from '@atlaskit/editor-prosemirror/state';
import { Mapping, StepMap } from '@atlaskit/editor-prosemirror/transform';
import { findChildrenByType, findParentNodeOfType, findParentNodeOfTypeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
import { findTable, isInTable, isTableSelected } from '@atlaskit/editor-tables/utils';
import { fg } from '@atlaskit/platform-feature-flags';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { key } from '../pm-plugins/main';
import { attachMoveNodeAnalytics, getMultiSelectAnalyticsAttributes } from '../pm-plugins/utils/analytics';
import { getNestedNodePosition } from '../pm-plugins/utils/getNestedNodePosition';
import { selectNode, setCursorPositionAtMovedNode } from '../pm-plugins/utils/getSelection';
import { removeFromSource } from '../pm-plugins/utils/remove-from-source';
import { getSelectedSlicePosition } from '../pm-plugins/utils/selection';
import { getInsertLayoutStep, updateSelection } from '../pm-plugins/utils/update-selection';
import { canMoveNodeToIndex, isInsideTable, transformFragmentExpandToNestedExpand, transformSliceExpandToNestedExpand } from '../pm-plugins/utils/validation';
import { getPosWhenMoveNodeDown, getPosWhenMoveNodeUp } from './utils/move-node-utils';
/**
* This function transforms the slice to move
* @param nodeCopy The slice contains the node to be moved
* @param destType The type of the destiation node
* @returns transformed slice or null if unable to
*/
function transformSourceSlice(nodeCopy, destType) {
const srcNode = nodeCopy.content.firstChild;
const schema = srcNode === null || srcNode === void 0 ? void 0 : srcNode.type.schema;
if (!schema) {
return nodeCopy;
}
const {
doc,
layoutColumn
} = schema.nodes;
const destTypeInTable = isInsideTable(destType);
const destTypeInDocOrLayoutCol = [doc, layoutColumn].includes(destType);
// No need to loop over slice content if destination requires no transformations
if (!destTypeInTable && !destTypeInDocOrLayoutCol) {
return nodeCopy;
}
let containsExpand = false;
let containsNestedExpand = false;
for (let i = 0; i < nodeCopy.content.childCount; i++) {
const node = nodeCopy.content.child(i);
if (node.type === schema.nodes.expand) {
containsExpand = true;
} else if (node.type === schema.nodes.nestedExpand) {
containsNestedExpand = true;
}
if (containsExpand && containsNestedExpand) {
break;
}
}
if (containsExpand && destTypeInTable) {
return transformSliceExpandToNestedExpand(nodeCopy);
} else if (containsNestedExpand && destTypeInDocOrLayoutCol) {
return transformSliceNestedExpandToExpand(nodeCopy, schema);
}
return nodeCopy;
}
const nodesSupportDragLayoutColumnInto = ['tableCell', 'tableHeader', 'panel', 'expand', 'nestedExpand'];
const isDragLayoutColumnIntoSupportedNodes = ($from, $to) => {
var _$from$nodeAfter;
const isTopLevel = $to.depth === 0;
const isDragIntoNodes = nodesSupportDragLayoutColumnInto.includes($to.parent.type.name);
const supportedCondition = isDragIntoNodes || isTopLevel;
return ((_$from$nodeAfter = $from.nodeAfter) === null || _$from$nodeAfter === void 0 ? void 0 : _$from$nodeAfter.type.name) === 'layoutColumn' && $from.parent.type.name === 'layoutSection' && supportedCondition;
};
/**
*
* @returns the start position of a node if the node can be moved, otherwise -1
*/
const getCurrentNodePos = state => {
const {
selection
} = state;
let currentNodePos = -1;
// There are 3 cases when a node can be moved
const focusedHandle = getFocusedHandle(state);
if (focusedHandle) {
// 1. drag handle of the node is focused
currentNodePos = focusedHandle.pos;
} else if (isInTable(state)) {
if (isTableSelected(selection)) {
var _findTable$pos, _findTable;
// We only move table node if it's fully selected
// to avoid shortcut collision with table drag and drop
currentNodePos = (_findTable$pos = (_findTable = findTable(selection)) === null || _findTable === void 0 ? void 0 : _findTable.pos) !== null && _findTable$pos !== void 0 ? _findTable$pos : currentNodePos;
}
} else if (!(state.selection instanceof GapCursorSelection)) {
// 2. caret cursor is inside the node
// 3. the start of the selection is inside the node
currentNodePos = selection.$from.before(1);
if (selection.$from.depth > 0) {
currentNodePos = getNestedNodePosition({
selection,
schema: state.schema,
resolve: state.doc.resolve.bind(state.doc)
});
}
}
return currentNodePos;
};
const getFocusedHandle = state => {
var _activeNode$handleOpt;
const {
activeNode
} = key.getState(state) || {};
return activeNode && (_activeNode$handleOpt = activeNode.handleOptions) !== null && _activeNode$handleOpt !== void 0 && _activeNode$handleOpt.isFocused ? activeNode : undefined;
};
export const moveNodeViaShortcut = (api, direction, formatMessage) => {
return state => {
var _hoistedPos;
const {
selection
} = state;
const isParentNodeOfTypeLayout = !!findParentNodeOfType([state.schema.nodes.layoutSection])(state.selection);
const isMultiSelectEnabled = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true);
const expandedSelection = expandSelectionBounds(selection.$anchor, selection.$head);
const expandedAnchor = expandedSelection.$anchor.pos;
const expandedHead = expandedSelection.$head.pos;
let hoistedPos;
const from = Math.min(expandedAnchor, expandedHead);
// Nodes like lists nest within themselves, we need to find the top most position
if (isParentNodeOfTypeLayout) {
const LAYOUT_COL_DEPTH = 3;
hoistedPos = state.doc.resolve(from).before(LAYOUT_COL_DEPTH);
}
const currentNodePos = isMultiSelectEnabled && !getFocusedHandle(state) && !selection.empty ? (_hoistedPos = hoistedPos) !== null && _hoistedPos !== void 0 ? _hoistedPos : from : getCurrentNodePos(state);
if (currentNodePos > -1) {
var _state$doc$nodeAt;
const $currentNodePos = state.doc.resolve(currentNodePos);
const nodeAfterPos = isMultiSelectEnabled && !getFocusedHandle(state) ? Math.max(expandedAnchor, expandedHead) : $currentNodePos.posAtIndex($currentNodePos.index() + 1);
const isTopLevelNode = $currentNodePos.depth === 0;
let moveToPos = -1;
const isLayoutColumnSelected = selection instanceof NodeSelection && selection.node.type.name === 'layoutColumn';
if (direction === DIRECTION.LEFT) {
if (isTopLevelNode && editorExperiment('advanced_layouts', true)) {
var _api$core, _api$core2;
const nodeBefore = $currentNodePos.nodeBefore;
if (nodeBefore) {
moveToPos = currentNodePos - nodeBefore.nodeSize;
}
if (moveToPos < 0) {
return false;
}
api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
tr
}) => {
var _api$blockControls, _api$blockControls$co;
api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : (_api$blockControls$co = _api$blockControls.commands) === null || _api$blockControls$co === void 0 ? void 0 : _api$blockControls$co.moveToLayout(currentNodePos, moveToPos, {
moveToEnd: true,
moveNodeAtCursorPos: true
})({
tr
});
const insertColumnStep = getInsertLayoutStep(tr);
const mappedTo = insertColumnStep === null || insertColumnStep === void 0 ? void 0 : insertColumnStep.from;
updateSelection(tr, mappedTo, true);
return tr;
});
api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.focus();
return true;
} else if (isLayoutColumnSelected) {
var _$currentNodePos$node, _api$core3, _api$blockControls2, _api$blockControls2$c;
moveToPos = selection.from - (((_$currentNodePos$node = $currentNodePos.nodeBefore) === null || _$currentNodePos$node === void 0 ? void 0 : _$currentNodePos$node.nodeSize) || 1);
api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : (_api$blockControls2$c = _api$blockControls2.commands) === null || _api$blockControls2$c === void 0 ? void 0 : _api$blockControls2$c.moveToLayout(currentNodePos, moveToPos, {
selectMovedNode: true
}));
return true;
} else {
if ($currentNodePos.depth < 2 || !isParentNodeOfTypeLayout) {
return false;
}
// get the previous layoutSection node
const index = $currentNodePos.index($currentNodePos.depth - 1);
const grandParent = $currentNodePos.node($currentNodePos.depth - 1);
const previousNode = grandParent ? grandParent.maybeChild(index - 1) : null;
moveToPos = $currentNodePos.start() - ((previousNode === null || previousNode === void 0 ? void 0 : previousNode.nodeSize) || 1);
}
} else if (direction === DIRECTION.RIGHT) {
if (isTopLevelNode && editorExperiment('advanced_layouts', true)) {
var _api$core4, _api$core5;
const endOfDoc = $currentNodePos.end();
moveToPos = $currentNodePos.posAtIndex($currentNodePos.index() + 1);
if (moveToPos >= endOfDoc) {
return false;
}
api === null || api === void 0 ? void 0 : (_api$core4 = api.core) === null || _api$core4 === void 0 ? void 0 : _api$core4.actions.execute(({
tr
}) => {
var _api$blockControls3, _api$blockControls3$c;
api === null || api === void 0 ? void 0 : (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 ? void 0 : (_api$blockControls3$c = _api$blockControls3.commands) === null || _api$blockControls3$c === void 0 ? void 0 : _api$blockControls3$c.moveToLayout(currentNodePos, moveToPos, {
moveNodeAtCursorPos: true
})({
tr
});
const insertColumnStep = getInsertLayoutStep(tr);
const mappedTo = insertColumnStep === null || insertColumnStep === void 0 ? void 0 : insertColumnStep.from;
updateSelection(tr, mappedTo);
return tr;
});
api === null || api === void 0 ? void 0 : (_api$core5 = api.core) === null || _api$core5 === void 0 ? void 0 : _api$core5.actions.focus();
return true;
} else if (isLayoutColumnSelected) {
var _api$core6, _api$blockControls4, _api$blockControls4$c;
const index = $currentNodePos.index($currentNodePos.depth);
const parent = $currentNodePos.node($currentNodePos.depth);
// get the next layoutColumn node
const nextNode = parent ? parent.maybeChild(index + 1) : null;
// if the current node is the last node, don't do anything
if (index >= parent.childCount - 1) {
// prevent event propagation to avoid moving the cursor and still select the node
return true;
}
const moveToEnd = index === parent.childCount - 2;
moveToPos = moveToEnd ? $currentNodePos.before() : selection.to + ((nextNode === null || nextNode === void 0 ? void 0 : nextNode.nodeSize) || 1);
api === null || api === void 0 ? void 0 : (_api$core6 = api.core) === null || _api$core6 === void 0 ? void 0 : _api$core6.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 ? void 0 : (_api$blockControls4$c = _api$blockControls4.commands) === null || _api$blockControls4$c === void 0 ? void 0 : _api$blockControls4$c.moveToLayout(currentNodePos, moveToPos, {
moveToEnd,
selectMovedNode: true
}));
return true;
} else {
if ($currentNodePos.depth < 2 || !isParentNodeOfTypeLayout) {
return false;
}
moveToPos = $currentNodePos.after($currentNodePos.depth) + 1;
}
} else if (direction === DIRECTION.UP) {
if (isLayoutColumnSelected) {
moveToPos = $currentNodePos.start() - 1;
} else {
moveToPos = getPosWhenMoveNodeUp($currentNodePos, currentNodePos);
}
} else {
const endOfDoc = $currentNodePos.end();
if (nodeAfterPos > endOfDoc) {
return false;
}
if (isLayoutColumnSelected) {
moveToPos = state.selection.$from.end() + 1;
} else {
moveToPos = getPosWhenMoveNodeDown({
$currentNodePos,
nodeAfterPos,
tr: state.tr
});
}
}
const nodeType = (_state$doc$nodeAt = state.doc.nodeAt(currentNodePos)) === null || _state$doc$nodeAt === void 0 ? void 0 : _state$doc$nodeAt.type.name;
let shouldMoveNode = false;
if (moveToPos > -1) {
const isDestDepthSameAsSource = $currentNodePos.depth === state.doc.resolve(moveToPos).depth;
const isSourceLayoutColumn = nodeType === 'layoutColumn';
shouldMoveNode = isDestDepthSameAsSource || isSourceLayoutColumn;
}
const {
$anchor: $newAnchor,
$head: $newHead
} = expandSelectionBounds($currentNodePos, selection.$to);
if (shouldMoveNode) {
var _api$core7;
api === null || api === void 0 ? void 0 : (_api$core7 = api.core) === null || _api$core7 === void 0 ? void 0 : _api$core7.actions.execute(({
tr
}) => {
api === null || api === void 0 ? void 0 : api.blockControls.commands.setMultiSelectPositions($newAnchor.pos, $newHead.pos)({
tr
});
moveNode(api)(currentNodePos, moveToPos, INPUT_METHOD.SHORTCUT, formatMessage)({
tr
});
tr.scrollIntoView();
return tr;
});
return true;
} else if (nodeType && !isMultiSelectEnabled) {
var _api$core8;
// If the node is first/last one, only select the node
api === null || api === void 0 ? void 0 : (_api$core8 = api.core) === null || _api$core8 === void 0 ? void 0 : _api$core8.actions.execute(({
tr
}) => {
selectNode(tr, currentNodePos, nodeType, api);
tr.scrollIntoView();
return tr;
});
return true;
} else if (isMultiSelectEnabled) {
var _api$core9;
api === null || api === void 0 ? void 0 : (_api$core9 = api.core) === null || _api$core9 === void 0 ? void 0 : _api$core9.actions.execute(({
tr
}) => {
api === null || api === void 0 ? void 0 : api.blockControls.commands.setMultiSelectPositions($newAnchor.pos, $newHead.pos)({
tr
});
tr.scrollIntoView();
return tr;
});
return true;
}
}
return false;
};
};
export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_DROP, formatMessage) => ({
tr
}) => {
var _api$blockControls$sh, _convertedNodeSlice, _api$accessibilityUti;
if (!api || start < 0 || to < 0) {
return tr;
}
const handleNode = tr.doc.nodeAt(start);
if (!handleNode) {
return tr;
}
let sliceFrom = start;
let sliceTo;
let sourceNodeTypes, hasSelectedMultipleNodes;
const isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true);
if (fg('platform_editor_ease_of_use_metrics')) {
var _api$metrics;
api === null || api === void 0 ? void 0 : (_api$metrics = api.metrics) === null || _api$metrics === void 0 ? void 0 : _api$metrics.commands.setContentMoved()({
tr
});
}
const preservedSelection = editorExperiment('platform_editor_block_menu', true) ? api === null || api === void 0 ? void 0 : (_api$blockControls$sh = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.preservedSelection : undefined;
if (preservedSelection) {
const $from = tr.doc.resolve(Math.min(start, preservedSelection.from));
const expandedRange = $from.blockRange(preservedSelection.$to);
sliceFrom = expandedRange ? expandedRange.start : preservedSelection.from;
sliceTo = expandedRange ? expandedRange.end : preservedSelection.to;
const attributes = getMultiSelectAnalyticsAttributes(tr, sliceFrom, sliceTo);
hasSelectedMultipleNodes = attributes.hasSelectedMultipleNodes;
sourceNodeTypes = attributes.nodeTypes;
} else if (isMultiSelect) {
const slicePosition = getSelectedSlicePosition(start, tr, api);
sliceFrom = slicePosition.from;
sliceTo = slicePosition.to;
const attributes = getMultiSelectAnalyticsAttributes(tr, sliceFrom, sliceTo);
hasSelectedMultipleNodes = attributes.hasSelectedMultipleNodes;
sourceNodeTypes = attributes.nodeTypes;
} else {
var _handleNode$nodeSize;
const size = (_handleNode$nodeSize = handleNode === null || handleNode === void 0 ? void 0 : handleNode.nodeSize) !== null && _handleNode$nodeSize !== void 0 ? _handleNode$nodeSize : 1;
sliceTo = sliceFrom + size;
}
const {
expand,
nestedExpand
} = tr.doc.type.schema.nodes;
const $to = tr.doc.resolve(to);
const $handlePos = tr.doc.resolve(start);
const nodeCopy = tr.doc.slice(sliceFrom, sliceTo, false); // cut the content
const destNode = $to.node();
const destType = destNode.type;
const destParent = $to.node($to.depth);
const sourceNode = $handlePos.nodeAfter;
//TODO: ED-26959 - Does this need to be updated with new selection logic above? ^
// Move a layout column to top level, or table cell, or panel, or expand, only moves the content into them
if (sourceNode && isDragLayoutColumnIntoSupportedNodes($handlePos, $to)) {
// need update after we support single column layout.
const layoutColumnContent = sourceNode.content;
let fragment;
// if drop into table, and layout column contains expand, transform it to nestedExpand
if (['tableCell', 'tableHeader'].includes($to.parent.type.name)) {
const contentContainsExpand = findChildrenByType(sourceNode, expand).length > 0;
fragment = contentContainsExpand ? transformFragmentExpandToNestedExpand(Fragment.from(layoutColumnContent)) : Fragment.from(layoutColumnContent);
if (!fragment) {
return tr;
}
} else {
fragment = Fragment.from(layoutColumnContent);
}
removeFromSource(tr, $handlePos, $handlePos.pos + sourceNode.nodeSize);
const mappedTo = tr.mapping.map(to);
tr.insert(mappedTo, fragment).setSelection(Selection.near(tr.doc.resolve(mappedTo))).scrollIntoView();
return tr;
}
if (!canMoveNodeToIndex(destParent, $to.index(), $handlePos.node().child($handlePos.index()), $to)) {
return tr;
}
let convertedNodeSlice = transformSourceSlice(nodeCopy, destType);
let convertedNode = (_convertedNodeSlice = convertedNodeSlice) === null || _convertedNodeSlice === void 0 ? void 0 : _convertedNodeSlice.content;
if (!convertedNode) {
return tr;
}
// Currently we don't support breakout mark for children nodes of bodiedSyncBlock node
// Hence strip out the mark for now
if (destNode.type.name === 'bodiedSyncBlock' && editorExperiment('platform_synced_block', true)) {
var _convertedNodeSlice2;
const nodes = [];
(_convertedNodeSlice2 = convertedNodeSlice) === null || _convertedNodeSlice2 === void 0 ? void 0 : _convertedNodeSlice2.content.forEach(node => {
nodes.push(node.mark(node.marks.filter(mark => mark.type.name !== 'breakout')));
});
convertedNodeSlice = new Slice(Fragment.from(nodes), 0, 0);
convertedNode = convertedNodeSlice.content;
}
// delete the content from the original position
tr.delete(sliceFrom, sliceTo);
const mappedTo = tr.mapping.map(to);
const isDestNestedLoneEmptyParagraph = destParent.type.name !== 'doc' && destParent.childCount === 1 && isEmptyParagraph($to.nodeAfter);
if (convertedNodeSlice && isDestNestedLoneEmptyParagraph) {
// if only a single empty paragraph within container, replace it
tr.replace(mappedTo, mappedTo + 1, convertedNodeSlice);
} else {
// otherwise just insert the content at the new position
tr.insert(mappedTo, convertedNode);
}
const sliceSize = sliceTo - sliceFrom;
if (inputMethod === INPUT_METHOD.DRAG_AND_DROP) {
tr = setCursorPositionAtMovedNode(tr, mappedTo, api);
} else if (preservedSelection) {
const currMeta = tr.getMeta(key);
const nodeMovedOffset = mappedTo - sliceFrom;
tr.setMeta(key, {
...currMeta,
preservedSelectionMapping: new Mapping([new StepMap([0, 0, nodeMovedOffset])])
});
} else if (isMultiSelect) {
var _api$blockControls$co2;
tr = (_api$blockControls$co2 = api === null || api === void 0 ? void 0 : api.blockControls.commands.setMultiSelectPositions(mappedTo, mappedTo + sliceSize)({
tr
})) !== null && _api$blockControls$co2 !== void 0 ? _api$blockControls$co2 : tr;
} else {
tr = selectNode(tr, mappedTo, handleNode.type.name, api);
}
const currMeta = tr.getMeta(key);
tr.setMeta(key, {
...currMeta,
nodeMoved: true
});
if (
// when move node via block menu, we need to keep the focus on block menu popup, so don't move focus to editor in this scenario
!(inputMethod === INPUT_METHOD.BLOCK_MENU && editorExperiment('platform_editor_block_menu', true))) {
api === null || api === void 0 ? void 0 : api.core.actions.focus();
}
const $mappedTo = tr.doc.resolve(mappedTo);
const expandAncestor = findParentNodeOfTypeClosestToPos($to, [expand, nestedExpand]);
if (expandAncestor) {
const wasExpandExpanded = expandedState.get(expandAncestor.node);
const updatedExpandAncestor = findParentNodeOfTypeClosestToPos($mappedTo, [expand, nestedExpand]);
if (wasExpandExpanded !== undefined && updatedExpandAncestor) {
expandedState.set(updatedExpandAncestor.node, wasExpandExpanded);
}
}
if (editorExperiment('advanced_layouts', true)) {
attachMoveNodeAnalytics(tr, inputMethod, $handlePos.depth, handleNode.type.name, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.depth, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.parent.type.name, $handlePos.sameParent($mappedTo), api, sourceNodeTypes, hasSelectedMultipleNodes);
} else {
var _api$analytics;
api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.attachAnalyticsEvent({
eventType: EVENT_TYPE.TRACK,
action: ACTION.MOVED,
actionSubject: ACTION_SUBJECT.ELEMENT,
actionSubjectId: ACTION_SUBJECT_ID.ELEMENT_DRAG_HANDLE,
attributes: {
nodeDepth: $handlePos.depth,
nodeType: handleNode.type.name,
destinationNodeDepth: $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.depth,
destinationNodeType: $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.parent.type.name,
inputMethod,
...(isMultiSelect && {
sourceNodeTypes,
hasSelectedMultipleNodes
})
}
})(tr);
}
const movedMessage = to > sliceFrom ? blockControlsMessages.movedDown : blockControlsMessages.movedup;
api === null || api === void 0 ? void 0 : (_api$accessibilityUti = api.accessibilityUtils) === null || _api$accessibilityUti === void 0 ? void 0 : _api$accessibilityUti.actions.ariaNotify(formatMessage ? formatMessage(movedMessage) : movedMessage.defaultMessage, {
priority: 'important'
});
return tr;
};