@atlaskit/editor-plugin-selection
Version:
Selection plugin for @atlaskit/editor-core
173 lines (171 loc) • 8.77 kB
JavaScript
"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;