UNPKG

@atlaskit/editor-plugin-selection

Version:

Selection plugin for @atlaskit/editor-core

173 lines (171 loc) 8.77 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _safePlugin = require("@atlaskit/editor-common/safe-plugin"); var _selection = require("@atlaskit/editor-common/selection"); var _state = require("@atlaskit/editor-prosemirror/state"); var _utils = require("@atlaskit/editor-prosemirror/utils"); var _view2 = require("@atlaskit/editor-prosemirror/view"); var _cellSelection = require("@atlaskit/editor-tables/cell-selection"); var _experiments = require("@atlaskit/tmp-editor-statsig/experiments"); var _types = require("../types"); var _gapCursorPluginKey = require("./gap-cursor-plugin-key"); var _actions = require("./gap-cursor/actions"); var _direction = require("./gap-cursor/direction"); var _utils2 = require("./gap-cursor/utils"); var _placeGapCursor = require("./gap-cursor/utils/place-gap-cursor"); var plugin = new _safePlugin.SafePlugin({ key: _gapCursorPluginKey.gapCursorPluginKey, state: { init: function init() { return { selectionIsGapCursor: false, displayGapCursor: true, hideCursor: false }; }, apply: function apply(tr, pluginState, _oldState, newState) { var _meta$displayGapCurso, _selectionMeta$hideCu; var meta = tr.getMeta(_gapCursorPluginKey.gapCursorPluginKey); var selectionMeta = tr.getMeta(_types.selectionPluginKey); var selectionIsGapCursor = newState.selection instanceof _selection.GapCursorSelection; return { selectionIsGapCursor: selectionIsGapCursor, // only attempt to hide gap cursor if selection is gap cursor displayGapCursor: selectionIsGapCursor ? (_meta$displayGapCurso = meta === null || meta === void 0 ? void 0 : meta.displayGapCursor) !== null && _meta$displayGapCurso !== void 0 ? _meta$displayGapCurso : pluginState.displayGapCursor : true, // track hideCursor state from selection plugin hideCursor: (_selectionMeta$hideCu = selectionMeta === null || selectionMeta === void 0 ? void 0 : selectionMeta.hideCursor) !== null && _selectionMeta$hideCu !== void 0 ? _selectionMeta$hideCu : pluginState.hideCursor }; } }, view: function view(_view) { /** * If the selection is at the beginning of a document and is a NodeSelection, * convert to a GapCursor selection. This is to stop users accidentally replacing * the first node of a document by accident. */ if (_view.state.selection.anchor === 0 && _view.state.selection instanceof _state.NodeSelection) { // This is required otherwise the dispatch doesn't trigger in the correct place window.requestAnimationFrame(function () { _view.dispatch(_view.state.tr.setSelection(new _selection.GapCursorSelection(_view.state.doc.resolve(0), _selection.Side.LEFT))); }); } return { update: function update(view) { if ((0, _experiments.editorExperiment)('platform_synced_block', true)) { // Caret visibility now handled directly via CSS selector in gapCursorStyles.ts return; } var _gapCursorPluginKey$g = _gapCursorPluginKey.gapCursorPluginKey.getState(view.state), selectionIsGapCursor = _gapCursorPluginKey$g.selectionIsGapCursor; /** * Starting with prosemirror-view 1.19.4, cursor wrapper that previously was hiding cursor doesn't exist: * https://github.com/ProseMirror/prosemirror-view/commit/4a56bc7b7e61e96ef879d1dae1014ede0fc09e43 * * Because it was causing issues with RTL: https://github.com/ProseMirror/prosemirror/issues/948 * * This is the work around which uses `caret-color: transparent` in order to hide regular caret, * when gap cursor is visible. * * Browser support is pretty good: https://caniuse.com/#feat=css-caret-color */ view.dom.classList.toggle(_selection.hideCaretModifier, selectionIsGapCursor); } }; }, props: { decorations: function decorations(editorState) { var doc = editorState.doc, selection = editorState.selection; var _gapCursorPluginKey$g2 = _gapCursorPluginKey.gapCursorPluginKey.getState(editorState), displayGapCursor = _gapCursorPluginKey$g2.displayGapCursor, hideCursor = _gapCursorPluginKey$g2.hideCursor; if (selection instanceof _selection.GapCursorSelection && displayGapCursor && !hideCursor) { var $from = selection.$from, side = selection.side; // render decoration DOM node always to the left of the target node even if selection points to the right // otherwise positioning of the right gap cursor is a nightmare when the target node has a nodeView with vertical margins var position = selection.head; var isRightCursor = side === _selection.Side.RIGHT; if (isRightCursor && $from.nodeBefore) { var nodeBeforeStart = (0, _utils.findPositionOfNodeBefore)(selection); if (typeof nodeBeforeStart === 'number') { position = nodeBeforeStart; } } var node = isRightCursor ? $from.nodeBefore : $from.nodeAfter; var layoutMode = node && (0, _utils2.getLayoutModeFromTargetNode)(node); return _view2.DecorationSet.create(doc, [_view2.Decoration.widget(position, _placeGapCursor.toDOM, { key: "".concat(_selection.JSON_ID, "-").concat(side, "-").concat(layoutMode), // position === 0: if gap cursor at start of document, render it on the left side of the selection to enable pasting (otherwise Chrome doesn't pick up the paste event) side: layoutMode || position === 0 ? -1 : 0 })]); } return null; }, // render gap cursor only when its valid createSelectionBetween: function createSelectionBetween(view, $anchor, $head) { if (view && view.state && view.state.selection instanceof _cellSelection.CellSelection) { // Do not show GapCursor when there is a CellSection happening return null; } if ($anchor.pos === $head.pos && _selection.GapCursorSelection.valid($head)) { return new _selection.GapCursorSelection($head); } return null; }, handleClick: function handleClick(view, nodePos, event) { var _$pos$parent; var posAtCoords = view.posAtCoords({ left: event.clientX, top: event.clientY }); if (!posAtCoords || (0, _utils2.isIgnoredClick)(event.target instanceof HTMLElement ? event.target : null)) { return false; } var isInsideTheTarget = posAtCoords.pos === posAtCoords.inside; if (isInsideTheTarget) { return false; } var leftSideOffsetX = 20; var side = event.offsetX > leftSideOffsetX ? _selection.Side.RIGHT : _selection.Side.LEFT; var $pos = view.state.doc.resolve(nodePos); // In the new prosemirror-view posAtCoords is not returning a precise value for our media nodes if (((_$pos$parent = $pos.parent) === null || _$pos$parent === void 0 ? void 0 : _$pos$parent.type.name) === 'mediaSingle') { var $insidePos = view.state.doc.resolve(Math.max(posAtCoords.inside, 0)); // We don't have GapCursors problems when the node target is inside the root level if ($insidePos.depth <= 1) { return false; } var mediaGapCursor = !$pos.nodeBefore ? $pos.before() : $pos.after(); return (0, _selection.setGapCursorAtPos)(mediaGapCursor, side)(view.state, view.dispatch); } var docSize = view.state.doc.content.size; var nodeInside = posAtCoords.inside < 0 || posAtCoords.inside > docSize ? null : view.state.doc.nodeAt(posAtCoords.inside); if (nodeInside !== null && nodeInside !== void 0 && nodeInside.isAtom) { return false; } return (0, _selection.setGapCursorAtPos)(nodePos, side)(view.state, view.dispatch); }, handleDOMEvents: { /** * Android composition events aren't handled well by Prosemirror * We've added a couple of beforeinput hooks to help PM out when trying to delete * certain nodes. We can remove these when PM has better composition support. * @see https://github.com/ProseMirror/prosemirror/issues/543 */ // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any beforeinput: function beforeinput(view, event) { if (event.inputType === 'deleteContentBackward' && view.state.selection instanceof _selection.GapCursorSelection) { event.preventDefault(); return (0, _actions.deleteNode)(_direction.Direction.BACKWARD)(view.state, view.dispatch); } return false; } } } }); var _default = exports.default = plugin;