UNPKG

@atlaskit/editor-plugin-selection

Version:

Selection plugin for @atlaskit/editor-core

186 lines (181 loc) 8.66 kB
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; }