@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
105 lines (100 loc) • 3.68 kB
JavaScript
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
import { CellSelection } from '@atlaskit/editor-tables/cell-selection';
import { findCellClosestToPos } from '@atlaskit/editor-tables/utils';
import { TableCssClassName as ClassName } from '../../types';
const EMPTY_STATE = {
cellPos: -1,
decorationSet: DecorationSet.empty
};
export const activeCellHighlightPluginKey = new PluginKey('tableActiveCellHighlight');
/**
* Returns the position of the cell containing the cursor, or -1 if the cursor
* is not in a table cell or the selection is a CellSelection.
*/
const getActiveCellPos = state => {
if (state.selection instanceof CellSelection) {
return -1;
}
try {
const cell = findCellClosestToPos(state.selection.$from);
return cell ? cell.pos : -1;
} catch {
return -1;
}
};
/** Builds the decoration state highlighting the cell at `cellPos`. */
const buildState = (cellPos, state) => {
if (cellPos === -1) {
return EMPTY_STATE;
}
try {
const cell = state.doc.nodeAt(cellPos);
if (!cell) {
return EMPTY_STATE;
}
const decoration = Decoration.node(cellPos, cellPos + cell.nodeSize, {
class: ClassName.ACTIVE_CURSOR_CELL
});
return {
cellPos,
decorationSet: DecorationSet.create(state.doc, [decoration])
};
} catch {
return EMPTY_STATE;
}
};
const hasHadInteraction = api => {
var _api$interaction$shar;
// The interaction plugin is optional - when absent, default to treating the editor as
// interacted.
if (!(api !== null && api !== void 0 && api.interaction)) {
return true;
}
return ((_api$interaction$shar = api.interaction.sharedState.currentState()) === null || _api$interaction$shar === void 0 ? void 0 : _api$interaction$shar.interactionState) !== 'hasNotHadInteraction';
};
/** Stays hidden until the first interaction. */
const createStateField = api => ({
init: (_config, state) => {
const interacted = hasHadInteraction(api);
return {
...(interacted ? buildState(getActiveCellPos(state), state) : EMPTY_STATE),
hasHadInteraction: interacted
};
},
apply: (tr, prev, _oldState, newState) => {
const interacted = hasHadInteraction(api);
// Keep the highlight hidden until the first interaction has happened.
if (!interacted) {
return prev;
}
// `true` only on the single transaction that crosses from "no interaction"
// to "interacted" — the meta-only transaction that reveals the highlight.
const isFirstInteraction = !prev.hasHadInteraction;
if (!tr.docChanged && !tr.selectionSet && !isFirstInteraction) {
// Doc, selection and interaction are all unchanged — nothing to do.
return prev;
}
const nextCellPos = getActiveCellPos(newState);
if (nextCellPos === prev.cellPos && !isFirstInteraction) {
return prev;
}
return {
...buildState(nextCellPos, newState),
hasHadInteraction: true
};
}
});
export const createPlugin = api => {
return new SafePlugin({
key: activeCellHighlightPluginKey,
state: createStateField(api),
props: {
decorations: state => {
var _activeCellHighlightP, _activeCellHighlightP2;
return (_activeCellHighlightP = (_activeCellHighlightP2 = activeCellHighlightPluginKey.getState(state)) === null || _activeCellHighlightP2 === void 0 ? void 0 : _activeCellHighlightP2.decorationSet) !== null && _activeCellHighlightP !== void 0 ? _activeCellHighlightP : DecorationSet.empty;
}
}
});
};