@atlaskit/editor-plugin-selection
Version:
Selection plugin for @atlaskit/editor-core
186 lines (181 loc) • 8.66 kB
JavaScript
import { expandedState, getNextNodeExpandPos } from '@atlaskit/editor-common/expand';
import { TextSelection } from '@atlaskit/editor-prosemirror/state';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
/*
* The way expand was built, no browser recognize selection on it.
* For instance, when a selection going to a "collapsed" expand
* the browser will try to send the cursor to inside the expand content (wrong),
* this behavior is caused because the expand content is never true hidden
* we just set the height to 1px.
*
* So, we need to capture a possible selection event
* when a collapsed expand is the next node in the common depth.
* If that is true, we create a new TextSelection and stop the event bubble
*/
var isCollapsedExpand = function isCollapsedExpand(node, _ref) {
var __livePage = _ref.__livePage;
var currentExpandedState;
if (__livePage && expValEquals('platform_editor_single_player_expand', 'isEnabled', true)) {
currentExpandedState = node ? !expandedState.get(node) : undefined;
} else if (__livePage) {
currentExpandedState = node === null || node === void 0 ? void 0 : node.attrs.__expanded;
} else {
currentExpandedState = !(node !== null && node !== void 0 && node.attrs.__expanded);
}
return Boolean(node && ['expand', 'nestedExpand'].includes(node.type.name) && currentExpandedState);
};
/**
* ED-18072 - Cannot shift + arrow past bodied extension if it is not empty
*/
var isBodiedExtension = function isBodiedExtension(node) {
return Boolean(node && ['bodiedExtension'].includes(node.type.name));
};
/**
* ED-19861 - [Regression] keyboard selections within action items are unpredicatable
* Table was added to the list of problematic nodes because the desired behaviour when Shift+Up from outside the
* table is to select the table node itself, rather than the table cell content. Previously this behaviour was handled
* in `packages/editor/editor-core/src/plugins/selection/pm-plugins/events/create-selection-between.ts` but there was
* a bug in `create-selection-between` which after fixing the bug that code was no longer handling table selection
* correctly, so to fix that table was added here.
*/
var isTable = function isTable(node) {
return Boolean(node && ['table'].includes(node.type.name));
};
var isProblematicNode = function isProblematicNode(node, _ref2) {
var __livePage = _ref2.__livePage;
return isCollapsedExpand(node, {
__livePage: __livePage
}) || isBodiedExtension(node) || isTable(node);
};
var findFixedProblematicNodePosition = function findFixedProblematicNodePosition(doc, $head, direction, _ref3) {
var __livePage = _ref3.__livePage;
if ($head.pos === 0 || $head.depth === 0) {
return null;
}
if (direction === 'up') {
var pos = $head.before();
var $posResolved = $head.doc.resolve(pos);
var maybeProblematicNode = $posResolved.nodeBefore;
if (maybeProblematicNode && isProblematicNode(maybeProblematicNode, {
__livePage: __livePage
})) {
var nodeSize = maybeProblematicNode.nodeSize;
var nodeStartPosition = pos - nodeSize;
// ($head.pos - 1) will correspond to (nodeStartPosition + nodeSize) when we are at the start of the text node
var isAtEndOfProblematicNode = $head.pos - 1 === nodeStartPosition + nodeSize;
if (isAtEndOfProblematicNode) {
var startPosNode = Math.max(nodeStartPosition, 0);
var $startPosNode = $head.doc.resolve(Math.min(startPosNode, $head.doc.content.size));
return $startPosNode;
}
}
}
if (direction === 'down') {
var _pos = $head.after();
var _maybeProblematicNode = doc.nodeAt(_pos);
if (_maybeProblematicNode && isProblematicNode(_maybeProblematicNode, {
__livePage: __livePage
}) && $head.pos + 1 === _pos) {
var _nodeSize = _maybeProblematicNode.nodeSize;
var nodePosition = _pos + _nodeSize;
var _startPosNode = Math.max(nodePosition, 0);
var _$startPosNode = $head.doc.resolve(Math.min(_startPosNode, $head.doc.content.size));
return _$startPosNode;
}
}
return null;
};
var isSelectionLineShortcutWhenCursorIsInsideInlineNode = function isSelectionLineShortcutWhenCursorIsInsideInlineNode(view, event) {
var _selection$$cursor$no, _selection$$cursor$no2;
if (!event.shiftKey || !event.metaKey) {
return false;
}
var selection = view.state.selection;
if (!(selection instanceof TextSelection)) {
return false;
}
if (!selection.$cursor) {
return false;
}
var isSelectingInlineNodeForward = event.key === 'ArrowRight' && Boolean((_selection$$cursor$no = selection.$cursor.nodeAfter) === null || _selection$$cursor$no === void 0 ? void 0 : _selection$$cursor$no.isInline);
var isSelectingInlineNodeBackward = event.key === 'ArrowLeft' && Boolean((_selection$$cursor$no2 = selection.$cursor.nodeBefore) === null || _selection$$cursor$no2 === void 0 ? void 0 : _selection$$cursor$no2.isInline);
return isSelectingInlineNodeForward || isSelectingInlineNodeBackward;
};
var isNavigatingVerticallyWhenCursorIsInsideInlineNode = function isNavigatingVerticallyWhenCursorIsInsideInlineNode(view, event) {
var _view$state, _selection$$cursor$no3, _selection$$cursor$no4;
if (event.shiftKey || event.metaKey) {
return false;
}
var selection = (_view$state = view.state) === null || _view$state === void 0 ? void 0 : _view$state.selection;
if (!(selection instanceof TextSelection)) {
return false;
}
if (!selection.$cursor) {
return false;
}
var isNavigatingInlineNodeDownward = event.key === 'ArrowDown' && Boolean((_selection$$cursor$no3 = selection.$cursor.nodeBefore) === null || _selection$$cursor$no3 === void 0 ? void 0 : _selection$$cursor$no3.isInline) && Boolean((_selection$$cursor$no4 = selection.$cursor.nodeAfter) === null || _selection$$cursor$no4 === void 0 ? void 0 : _selection$$cursor$no4.isInline);
if (isNavigatingInlineNodeDownward && getNextNodeExpandPos(view, selection) !== undefined && expValEqualsNoExposure('platform_editor_lovability_navigation_fixes', 'isEnabled', true)) {
return false;
}
return isNavigatingInlineNodeDownward;
};
export function createOnKeydown(_ref4) {
var _ref4$__livePage = _ref4.__livePage,
__livePage = _ref4$__livePage === void 0 ? false : _ref4$__livePage;
function onKeydown(view, event) {
/*
* This workaround is needed for some specific situations.
* - expand collapse
* - bodied extension
*/
if (!(event instanceof KeyboardEvent)) {
return false;
}
// Override the default behaviour to make sure that the selection always extends to
// the start of the document and not just the first inline position.
if (event.shiftKey && event.metaKey && event.key === 'ArrowUp') {
var selection = TextSelection.create(view.state.doc, view.state.selection.$anchor.pos, 0);
view.dispatch(view.state.tr.setSelection(selection));
event.preventDefault();
return true;
}
if (isSelectionLineShortcutWhenCursorIsInsideInlineNode(view, event)) {
return true;
}
if (isNavigatingVerticallyWhenCursorIsInsideInlineNode(view, event)) {
return true;
}
if (!event.shiftKey || event.ctrlKey || event.metaKey) {
return false;
}
if (!['ArrowUp', 'ArrowDown', 'ArrowRight', 'ArrowLeft', 'Home', 'End'].includes(event.key)) {
return false;
}
var _view$state2 = view.state,
doc = _view$state2.doc,
_view$state2$selectio = _view$state2.selection,
$head = _view$state2$selectio.$head,
$anchor = _view$state2$selectio.$anchor;
if (event.key === 'ArrowRight' && $head.nodeAfter || event.key === 'ArrowLeft' && $head.nodeBefore) {
return false;
}
var direction = ['ArrowLeft', 'ArrowUp', 'Home'].includes(event.key) ? 'up' : 'down';
var $fixedProblematicNodePosition = findFixedProblematicNodePosition(doc, $head, direction, {
__livePage: __livePage
});
if ($fixedProblematicNodePosition) {
// an offset is used here so that left arrow selects the first character before the node (consistent with arrow right)
var headOffset = event.key === 'ArrowLeft' ? -1 : 0;
var head = $fixedProblematicNodePosition.pos + headOffset;
var forcedTextSelection = TextSelection.create(view.state.doc, $anchor.pos, head);
var tr = view.state.tr;
tr.setSelection(forcedTextSelection);
view.dispatch(tr);
event.preventDefault();
return true;
}
return false;
}
return onKeydown;
}