@atlaskit/editor-plugin-expand
Version:
Expand plugin for @atlaskit/editor-core
694 lines (690 loc) • 27.9 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import React from 'react';
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
import uuid from 'uuid/v4';
import { keyName } from 'w3c-keyname';
import { expandedState, isExpandCollapsed } from '@atlaskit/editor-common/expand';
import { GapCursorSelection, RelativeSelectionPos, Side } from '@atlaskit/editor-common/selection';
import { expandClassNames } from '@atlaskit/editor-common/styles';
import { closestElement, isEmptyNode } from '@atlaskit/editor-common/utils';
import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
import { NodeSelection, Selection } from '@atlaskit/editor-prosemirror/state';
import { redo, undo } from '@atlaskit/prosemirror-history';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { renderExpandButton } from '../../ui/renderExpandButton';
import { deleteExpand, setSelectionInsideExpand, toggleExpandExpanded, updateExpandTitle } from '../commands';
import { ExpandButton } from '../ui/ExpandButton';
import { buildExpandClassName, toDOM } from '../ui/NodeView';
import { findReplaceExpandDecorations } from '../utils';
export class ExpandNodeView {
constructor(_node, view, getPos, getIntl, isMobile, selectNearNode, api, nodeViewPortalProviderAPI, allowInteractiveExpand = true, __livePage = false, cleanUpEditorDisabledOnChange, isExpanded = {
expanded: false
}) {
var _api$editorDisabled, _api$editorDisabled$s, _this$api8;
_defineProperty(this, "allowInteractiveExpand", true);
_defineProperty(this, "isMobile", false);
_defineProperty(this, "focusTitle", () => {
if (this.input) {
const {
state,
dispatch
} = this.view;
if (this.selectNearNode) {
const tr = this.selectNearNode({
selectionRelativeToNode: RelativeSelectionPos.Start
})(state);
if (dispatch) {
dispatch(tr);
}
}
const pos = this.getPos();
if (typeof pos === 'number') {
setSelectionInsideExpand(pos)(state, dispatch, this.view);
}
this.input.focus();
}
});
_defineProperty(this, "handleIconKeyDown", event => {
switch (keyName(event)) {
case 'Tab':
event.preventDefault();
this.focusTitle();
break;
case 'Enter':
event.preventDefault();
this.handleClick(event);
break;
}
});
_defineProperty(this, "handleClick", event => {
const pos = this.getPos();
if (typeof pos !== 'number') {
return;
}
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
const target = event.target;
if (closestElement(target, `.${expandClassNames.icon}`)) {
var _this$api, _this$api$analytics;
if (!this.allowInteractiveExpand) {
return;
}
event.stopPropagation();
// We blur the editorView, to prevent any keyboard showing on mobile
// When we're interacting with the expand toggle
if (this.view.dom instanceof HTMLElement) {
this.view.dom.blur();
}
toggleExpandExpanded({
editorAnalyticsAPI: (_this$api = this.api) === null || _this$api === void 0 ? void 0 : (_this$api$analytics = _this$api.analytics) === null || _this$api$analytics === void 0 ? void 0 : _this$api$analytics.actions,
pos,
node: this.node
})(this.view.state, this.view.dispatch);
this.updateExpandToggleIcon(this.node);
this.updateDisplayStyle(this.node);
return;
}
if (target === this.input) {
event.stopPropagation();
this.focusTitle();
return;
}
});
_defineProperty(this, "handleInput", event => {
const pos = this.getPos();
if (typeof pos !== 'number') {
return;
}
const target = event.target;
if (target === this.input) {
event.stopPropagation();
const {
state,
dispatch
} = this.view;
updateExpandTitle({
title: target.value,
pos,
nodeType: this.node.type
})(state, dispatch);
}
});
_defineProperty(this, "handleFocus", event => {
event.stopImmediatePropagation();
});
_defineProperty(this, "handleInputFocus", () => {
var _this$api2, _this$api2$selectionM, _this$api2$selectionM2;
this.decorationCleanup = (_this$api2 = this.api) === null || _this$api2 === void 0 ? void 0 : (_this$api2$selectionM = _this$api2.selectionMarker) === null || _this$api2$selectionM === void 0 ? void 0 : (_this$api2$selectionM2 = _this$api2$selectionM.actions) === null || _this$api2$selectionM2 === void 0 ? void 0 : _this$api2$selectionM2.hideDecoration();
});
_defineProperty(this, "handleBlur", () => {
var _this$decorationClean;
(_this$decorationClean = this.decorationCleanup) === null || _this$decorationClean === void 0 ? void 0 : _this$decorationClean.call(this);
});
_defineProperty(this, "handleTitleKeydown", event => {
// Handle Ctrl+Shift+H to select the expand node for drag handle
// Note: If changing this implementation in singlePlayer expand, please also update the implementation in legacyExpand
if (expValEquals('platform_editor_dnd_accessibility_fixes_expand', 'isEnabled', true) && (event.ctrlKey || event.metaKey) && event.shiftKey && (event.key === 'H' || event.key === 'h')) {
event.preventDefault();
const pos = this.getPos();
if (typeof pos === 'number') {
// Blur the input first to remove focus from the title
if (this.input) {
this.input.blur();
}
// Use requestAnimationFrame to ensure blur completes before setting selection
requestAnimationFrame(() => {
var _this$api3;
const {
state
} = this.view;
this.view.focus();
(_this$api3 = this.api) === null || _this$api3 === void 0 ? void 0 : _this$api3.core.actions.execute(({
tr
}) => {
tr.setSelection(NodeSelection.create(state.doc, pos));
// Show the drag handle on the selected expand node
const node = state.doc.nodeAt(pos);
if (node) {
// Find the anchor name from the DOM
const dom = this.view.nodeDOM(pos);
if (dom instanceof HTMLElement) {
const anchorName = expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? dom.getAttribute('data-node-anchor') : dom.getAttribute('data-drag-handler-anchor-name');
// Only proceed if we found a valid anchor name
if (anchorName) {
var _this$api4, _this$api4$blockContr;
const command = (_this$api4 = this.api) === null || _this$api4 === void 0 ? void 0 : (_this$api4$blockContr = _this$api4.blockControls) === null || _this$api4$blockContr === void 0 ? void 0 : _this$api4$blockContr.commands.showDragHandleAt(pos, anchorName, node.type.name, {
isFocused: true
});
if (command) {
return command({
tr
});
}
}
}
}
return null;
});
});
}
return;
}
switch (keyName(event)) {
case 'Enter':
this.toggleExpand();
break;
case 'Tab':
case 'ArrowDown':
this.moveToOutsideOfTitle(event);
break;
case 'ArrowRight':
this.handleArrowRightFromTitle(event);
break;
case 'ArrowLeft':
this.handleArrowLeftFromTitle(event);
break;
case 'ArrowUp':
if (expValEquals('platform_editor_lovability_navigation_fixes', 'isEnabled', true)) {
this.moveToPreviousLine(event);
} else {
this.setLeftGapCursor(event);
}
break;
case 'Backspace':
this.deleteEmptyExpand();
break;
}
// 'Ctrl-y', 'Mod-Shift-z');
if (event.ctrlKey && event.key === 'y' || (event.ctrlKey || event.metaKey) && event.shiftKey && event.key === 'z') {
this.handleRedoFromTitle(event);
return;
}
// 'Mod-z'
if ((event.ctrlKey || event.metaKey) && event.key === 'z') {
this.handleUndoFromTitle(event);
return;
}
if (expValEquals('platform_editor_breakout_resizing', 'isEnabled', true)) {
if ((event.ctrlKey || event.metaKey) && event.altKey && (event.code === 'BracketLeft' || event.code === 'BracketRight')) {
this.view.dispatchEvent(new KeyboardEvent('keydown', {
key: event.key,
code: event.code,
metaKey: event.metaKey,
ctrlKey: event.ctrlKey,
altKey: event.altKey
}));
}
}
});
_defineProperty(this, "deleteEmptyExpand", () => {
const {
state
} = this.view;
const expandNode = this.node;
if (!this.input) {
return;
}
const {
selectionStart,
selectionEnd
} = this.input;
if (selectionStart !== selectionEnd || selectionStart !== 0) {
return;
}
if (expandNode && isEmptyNode(state.schema)(expandNode)) {
var _this$api5, _this$api5$analytics;
deleteExpand((_this$api5 = this.api) === null || _this$api5 === void 0 ? void 0 : (_this$api5$analytics = _this$api5.analytics) === null || _this$api5$analytics === void 0 ? void 0 : _this$api5$analytics.actions)(state, this.view.dispatch);
}
});
_defineProperty(this, "toggleExpand", () => {
const pos = this.getPos();
if (typeof pos !== 'number') {
return;
}
if (this.allowInteractiveExpand) {
var _this$api6, _this$api6$analytics;
const {
state,
dispatch
} = this.view;
toggleExpandExpanded({
editorAnalyticsAPI: (_this$api6 = this.api) === null || _this$api6 === void 0 ? void 0 : (_this$api6$analytics = _this$api6.analytics) === null || _this$api6$analytics === void 0 ? void 0 : _this$api6$analytics.actions,
pos,
node: this.node
})(state, dispatch);
this.updateExpandToggleIcon(this.node);
this.updateDisplayStyle(this.node);
}
});
_defineProperty(this, "moveToOutsideOfTitle", event => {
event.preventDefault();
const {
state,
dispatch
} = this.view;
const expandPos = this.getPos();
if (typeof expandPos !== 'number') {
return;
}
let pos = expandPos;
if (this.isCollapsed()) {
pos = expandPos + this.node.nodeSize;
}
const resolvedPos = state.doc.resolve(pos);
if (!resolvedPos) {
return;
}
if (this.isCollapsed() && resolvedPos.nodeAfter && ['expand', 'nestedExpand'].indexOf(resolvedPos.nodeAfter.type.name) > -1) {
return this.setRightGapCursor(event);
}
const sel = Selection.findFrom(resolvedPos, 1, true);
if (sel) {
// If the input has focus, ProseMirror doesn't
// Give PM focus back before changing our selection
this.view.focus();
dispatch(state.tr.setSelection(sel));
}
});
_defineProperty(this, "isCollapsed", () => {
return !expandedState.get(this.node);
});
_defineProperty(this, "setRightGapCursor", event => {
if (!this.input) {
return;
}
const pos = this.getPos();
if (typeof pos !== 'number') {
return;
}
const {
value,
selectionStart,
selectionEnd
} = this.input;
const selectionStartExists = selectionStart !== null && selectionStart !== undefined;
const selectionEndExists = selectionEnd !== null && selectionEnd !== undefined;
const selectionStartInsideTitle = selectionStartExists && selectionStart >= 0 && selectionStart <= value.length;
const selectionEndInsideTitle = selectionEndExists && selectionEnd >= 0 && selectionEnd <= value.length;
if (selectionStartInsideTitle && selectionEndInsideTitle) {
const {
state,
dispatch
} = this.view;
event.preventDefault();
this.view.focus();
dispatch(state.tr.setSelection(new GapCursorSelection(state.doc.resolve(this.node.nodeSize + pos), Side.RIGHT)));
}
});
_defineProperty(this, "moveToPreviousLine", event => {
if (!this.input) {
return;
}
const pos = this.getPos();
if (typeof pos !== 'number') {
return;
}
const {
selectionStart,
selectionEnd
} = this.input;
if (selectionStart === selectionEnd && selectionStart === 0) {
event.preventDefault();
const {
state,
dispatch
} = this.view;
const resolvedPos = state.doc.resolve(pos);
if (!resolvedPos) {
return;
}
if (resolvedPos.pos === 0) {
this.setLeftGapCursor(event);
return;
}
const sel = Selection.findFrom(resolvedPos, -1);
if (sel) {
this.view.focus();
dispatch(state.tr.setSelection(sel));
}
}
});
_defineProperty(this, "setLeftGapCursor", event => {
if (!this.input) {
return;
}
const pos = this.getPos();
if (typeof pos !== 'number') {
return;
}
const {
selectionStart,
selectionEnd
} = this.input;
if (selectionStart === selectionEnd && selectionStart === 0) {
event.preventDefault();
const {
state,
dispatch
} = this.view;
this.view.focus();
dispatch(state.tr.setSelection(new GapCursorSelection(state.doc.resolve(pos), Side.LEFT)));
}
});
_defineProperty(this, "handleArrowRightFromTitle", event => {
if (!this.input || !this.selectNearNode) {
return;
}
const pos = this.getPos();
if (typeof pos !== 'number') {
return;
}
const {
value,
selectionStart,
selectionEnd
} = this.input;
if (selectionStart === selectionEnd && selectionStart === value.length) {
event.preventDefault();
const {
state,
dispatch
} = this.view;
this.view.focus();
const tr = this.selectNearNode({
selectionRelativeToNode: RelativeSelectionPos.End,
selection: NodeSelection.create(state.doc, pos)
})(state);
if (dispatch) {
dispatch(tr);
}
}
});
_defineProperty(this, "handleArrowLeftFromTitle", event => {
if (!this.input || !this.selectNearNode) {
return;
}
const pos = this.getPos();
if (typeof pos !== 'number') {
return;
}
const {
selectionStart,
selectionEnd
} = this.input;
if (selectionStart === selectionEnd && selectionStart === 0) {
var _this$api7, _this$api7$selection;
event.preventDefault();
const {
state,
dispatch
} = this.view;
this.view.focus();
const selectionSharedState = ((_this$api7 = this.api) === null || _this$api7 === void 0 ? void 0 : (_this$api7$selection = _this$api7.selection) === null || _this$api7$selection === void 0 ? void 0 : _this$api7$selection.sharedState.currentState()) || {};
// selectionRelativeToNode is undefined when user clicked to select node, then hit left to get focus in title
// This is a special case where we want to bypass node selection and jump straight to gap cursor
if ((selectionSharedState === null || selectionSharedState === void 0 ? void 0 : selectionSharedState.selectionRelativeToNode) === undefined) {
const tr = this.selectNearNode({
selectionRelativeToNode: undefined,
selection: new GapCursorSelection(state.doc.resolve(pos), Side.LEFT)
})(state);
if (dispatch) {
dispatch(tr);
}
} else {
const tr = this.selectNearNode({
selectionRelativeToNode: RelativeSelectionPos.Start,
selection: NodeSelection.create(state.doc, pos)
})(state);
if (dispatch) {
dispatch(tr);
}
}
}
});
_defineProperty(this, "handleUndoFromTitle", event => {
const {
state,
dispatch
} = this.view;
undo(state, dispatch);
event.preventDefault();
return;
});
_defineProperty(this, "handleRedoFromTitle", event => {
const {
state,
dispatch
} = this.view;
redo(state, dispatch);
event.preventDefault();
return;
});
_defineProperty(this, "getContentEditable", node => {
const contentEditable = !isExpandCollapsed(node);
if (this.api && this.api.editorDisabled) {
var _this$api$editorDisab;
return !((_this$api$editorDisab = this.api.editorDisabled.sharedState.currentState()) !== null && _this$api$editorDisab !== void 0 && _this$api$editorDisab.editorDisabled) && contentEditable;
}
return contentEditable;
});
_defineProperty(this, "renderIcon", (icon, expanded) => {
if (!icon) {
return;
}
this.nodeViewPortalProviderAPI.render(() => /*#__PURE__*/React.createElement(ExpandButton, {
intl: this.intl,
allowInteractiveExpand: this.allowInteractiveExpand,
expanded: expanded
}), icon, this.renderKey);
});
this.selectNearNode = selectNearNode;
this.__livePage = __livePage;
this.cleanUpEditorDisabledOnChange = cleanUpEditorDisabledOnChange;
this.isExpanded = isExpanded;
this.intl = getIntl();
this.nodeViewPortalProviderAPI = nodeViewPortalProviderAPI;
this.allowInteractiveExpand = allowInteractiveExpand;
this.getPos = getPos;
this.view = view;
this.node = _node;
if (editorExperiment('platform_editor_block_menu', true, {
exposure: true
})) {
var _expandedState$get;
this.isExpanded.expanded = (_expandedState$get = expandedState.get(_node)) !== null && _expandedState$get !== void 0 ? _expandedState$get : false;
this.isExpanded.localId = _node.attrs.localId;
}
const {
dom: _dom,
contentDOM
} = DOMSerializer.renderSpec(document, toDOM(_node, this.__livePage, this.intl, api === null || api === void 0 ? void 0 : (_api$editorDisabled = api.editorDisabled) === null || _api$editorDisabled === void 0 ? void 0 : (_api$editorDisabled$s = _api$editorDisabled.sharedState.currentState()) === null || _api$editorDisabled$s === void 0 ? void 0 : _api$editorDisabled$s.editorDisabled));
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
this.dom = _dom;
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
this.contentDOM = contentDOM;
this.isMobile = isMobile;
this.api = api;
this.icon = this.dom.querySelector(`.${expandClassNames.icon}`);
this.input = this.dom.querySelector(`.${expandClassNames.titleInput}`);
this.titleContainer = this.dom.querySelector(`.${expandClassNames.titleContainer}`);
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
this.renderKey = uuid();
this.content = this.dom.querySelector(`.${expandClassNames.content}`);
if (!expandedState.has(this.node)) {
expandedState.set(this.node, false);
}
if (expValEquals('platform_editor_vc90_transition_expand_icon', 'isEnabled', true)) {
this.renderNativeIcon(this.node);
} else {
this.renderIcon(this.icon, !isExpandCollapsed(this.node));
}
if (!this.input || !this.titleContainer || !this.icon) {
return;
}
// Add event listeners
/* eslint-disable @repo/internal/dom-events/no-unsafe-event-listeners*/
this.dom.addEventListener('click', this.handleClick);
this.dom.addEventListener('input', this.handleInput);
this.input.addEventListener('keydown', this.handleTitleKeydown);
this.input.addEventListener('blur', this.handleBlur);
this.input.addEventListener('focus', this.handleInputFocus);
// If the user interacts in our title bar (either toggle or input)
// Prevent ProseMirror from getting a focus event (causes weird selection issues).
this.titleContainer.addEventListener('focus', this.handleFocus);
this.icon.addEventListener('keydown', this.handleIconKeyDown);
if ((_this$api8 = this.api) !== null && _this$api8 !== void 0 && _this$api8.editorDisabled) {
this.cleanUpEditorDisabledOnChange = this.api.editorDisabled.sharedState.onChange(sharedState => {
const editorDisabled = sharedState.nextSharedState.editorDisabled;
if (this.input) {
if (editorDisabled) {
this.input.setAttribute('readonly', 'true');
} else {
this.input.removeAttribute('readonly');
}
}
if (this.content) {
this.content.setAttribute('contenteditable', this.getContentEditable(this.node) ? 'true' : 'false');
}
});
}
}
stopEvent(event) {
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
const target = event.target;
return target === this.input || target === this.icon || !!closestElement(target, `.${expandClassNames.icon}`);
}
ignoreMutation(mutationRecord) {
// ME-1931: Mobile relies on composition which creates dom mutations. If we ignore them, prosemirror
// does not recognise the changes and reverts them.
if (this.isMobile && (mutationRecord.type === 'characterData' || mutationRecord.type === 'childList')) {
return false;
}
if (mutationRecord.type === 'selection') {
return false;
}
return true;
}
update(node, _decorations) {
if (this.node.type === node.type) {
var _expandedState$get2;
// During a collab session the title doesn't sync with other users
// since we're intentionally being less aggressive about re-rendering.
// We also apply a rAF to avoid abrupt continuous replacement of the title.
window.requestAnimationFrame(() => {
if (this.input && this.node.attrs.title !== this.input.value) {
this.input.value = this.node.attrs.title;
}
});
// This checks if the node has been replaced with a different version
// and updates the state of the new node to match the old one
// Eg. typing in a node changes it to a new node so it must be updated
// in the expandedState weak map
if (this.node !== node) {
const wasExpanded = expandedState.get(this.node);
if (wasExpanded) {
expandedState.set(node, wasExpanded);
}
}
this.node = node;
const currentExpanded = (_expandedState$get2 = expandedState.get(node)) !== null && _expandedState$get2 !== void 0 ? _expandedState$get2 : false;
const hasChanged = editorExperiment('platform_editor_block_menu', true, {
exposure: true
}) ? this.isExpanded.expanded !== currentExpanded && this.isExpanded.localId === node.attrs.localId : this.isExpanded.expanded !== currentExpanded;
if (hasChanged) {
this.updateExpandToggleIcon(node);
this.updateDisplayStyle(node);
}
return true;
}
return false;
}
updateExpandToggleIcon(node) {
const expanded = expandedState.get(node) ? expandedState.get(node) : false;
if (this.dom && expanded !== undefined) {
if (expValEquals('platform_editor_find_and_replace_improvements', 'isEnabled', true)) {
const classes = this.dom.className.split(' ');
// find & replace styles might be applied to the expand title and we need to keep them
const findReplaceDecorationsApplied = classes.filter(className => findReplaceExpandDecorations.includes(className)).join(' ');
this.dom.className = findReplaceDecorationsApplied ? buildExpandClassName(node.type.name, expanded) + ` ${findReplaceDecorationsApplied}` : buildExpandClassName(node.type.name, expanded);
} else {
this.dom.className = buildExpandClassName(node.type.name, expanded);
}
// Re-render the icon to update the aria-expanded attribute
if (expValEquals('platform_editor_vc90_transition_expand_icon', 'isEnabled', true)) {
this.renderNativeIcon(node);
} else {
var _expandedState$get3;
this.renderIcon(this.icon ? this.icon : null, (_expandedState$get3 = expandedState.get(node)) !== null && _expandedState$get3 !== void 0 ? _expandedState$get3 : false);
}
}
this.updateExpandBodyContentEditable();
this.isExpanded = editorExperiment('platform_editor_block_menu', true, {
exposure: true
}) ? {
expanded: expanded !== null && expanded !== void 0 ? expanded : false,
localId: node.attrs.localId
} : {
expanded: expanded !== null && expanded !== void 0 ? expanded : false
};
}
updateDisplayStyle(node) {
if (this.content) {
if (isExpandCollapsed(node)) {
this.content.classList.add(expandClassNames.contentCollapsed);
} else {
this.content.classList.remove(expandClassNames.contentCollapsed);
}
}
}
updateExpandBodyContentEditable() {
// Disallow interaction/selection inside expand body when collapsed.
if (this.content) {
this.content.setAttribute('contenteditable', this.getContentEditable(this.node) ? 'true' : 'false');
}
}
renderNativeIcon(node) {
if (!this.icon) {
return;
}
renderExpandButton(this.icon, {
expanded: !isExpandCollapsed(node),
allowInteractiveExpand: this.allowInteractiveExpand,
intl: this.intl
});
}
destroy() {
var _this$decorationClean2;
if (!this.dom || !this.input || !this.titleContainer || !this.icon) {
return;
}
this.dom.removeEventListener('click', this.handleClick);
this.dom.removeEventListener('input', this.handleInput);
this.input.removeEventListener('keydown', this.handleTitleKeydown);
this.input.removeEventListener('blur', this.handleBlur);
this.input.removeEventListener('focus', this.handleInputFocus);
this.titleContainer.removeEventListener('focus', this.handleFocus);
this.icon.removeEventListener('keydown', this.handleIconKeyDown);
(_this$decorationClean2 = this.decorationCleanup) === null || _this$decorationClean2 === void 0 ? void 0 : _this$decorationClean2.call(this);
if (this.cleanUpEditorDisabledOnChange) {
this.cleanUpEditorDisabledOnChange();
}
this.nodeViewPortalProviderAPI.remove(this.renderKey);
}
}
export default function ({
getIntl,
isMobile,
api,
nodeViewPortalProviderAPI,
allowInteractiveExpand = true,
__livePage
}) {
return (node, view, getPos) => {
var _api$selection, _api$selection$action;
return new ExpandNodeView(node, view, getPos, getIntl, isMobile, api === null || api === void 0 ? void 0 : (_api$selection = api.selection) === null || _api$selection === void 0 ? void 0 : (_api$selection$action = _api$selection.actions) === null || _api$selection$action === void 0 ? void 0 : _api$selection$action.selectNearNode, api, nodeViewPortalProviderAPI, allowInteractiveExpand, __livePage);
};
}