I’ve been learning/evaluating the framework the past couple of days and I have re-implement the Tooltip example that’s available in the docs.
The approach I took was to allow the plugin class to control and render a css-component (in this case implemented with glamorous). I’m sure the code can be improved, anyways, here it is.
SelectionSizeTooltipPlugin.js:
import React from 'react';
import { render as renderReact } from 'react-dom';
import { Plugin } from 'prosemirror-state';
import { SelectionSizeTooltip } from './components/SelectionSizeTooltip';
import { areStatesEqual } from '../../utils';
class SelectionSizeTooltipPlugin {
constructor(view) {
this.tooltip = document.createElement('div');
view.dom.parentNode.appendChild(this.tooltip);
this.update(view, null);
}
render(view) {
const { from, to, empty } = view.state.selection;
const start = view.coordsAtPos(from);
const end = view.coordsAtPos(to);
const box = this.tooltip.offsetParent.getBoundingClientRect();
const left = Math.max((start.left + end.left) / 2, start.left + 3);
const leftSpacing = `${left - box.left}px`;
const bottomSpacing = `${box.bottom - start.top}px`;
const numberOfCharactersInSelection = to - from;
return (
<SelectionSizeTooltip
display={empty ? 'none' : undefined}
left={leftSpacing}
bottom={bottomSpacing}
>
{numberOfCharactersInSelection}
</SelectionSizeTooltip>
);
}
update(view, lastState) {
const { state } = view;
if (areStatesEqual(state, lastState)) return;
const tooltipComponent = this.render(view);
renderReact(tooltipComponent, this.tooltip);
}
destroy() {
this.tooltip.remove();
}
}
const selectionSizeTooltipPlugin = new Plugin({
view(editorView) {
return new SelectionSizeTooltipPlugin(editorView);
},
});
export { selectionSizeTooltipPlugin };
SelectionSizeTooltip.js:
/* eslint-disable no-useless-return */
import PropTypes from 'prop-types';
import glamorous from 'glamorous';
const SelectionSizeTooltip = glamorous('div', { propsAreCssOverrides: true })({
position: 'absolute',
pointerEvents: 'none',
zIndex: 20,
background: 'white',
border: '1px solid silver',
borderRadius: '2px',
padding: '2px 10px',
marginBottom: '7px',
transform: 'translateX(-50%)',
'&::before': {
content: '',
height: 0,
width: 0,
position: 'absolute',
left: '50%',
marginLeft: '-5px',
bottom: '-6px',
border: '5px solid transparent',
borderBottomWidth: 0,
borderTopColor: 'silver',
},
'&::after': {
content: '',
height: 0,
width: 0,
position: 'absolute',
left: '50%',
marginLeft: '-5px',
bottom: '-4.5px',
border: '5px solid transparent',
borderBottomWidth: 0,
borderTopColor: 'white',
},
});
SelectionSizeTooltip.propTypes = {
display: PropTypes.string,
children: PropTypes.number.isRequired,
left: PropTypes.string.isRequired,
bottom: PropTypes.string.isRequired,
};
SelectionSizeTooltip.defaultProps = {
display: undefined,
};
export { SelectionSizeTooltip };
I’d much appreciate if someone had input on how to improve the cycle of rendering/updating. It would be nice to replace createElement, this.update(view, null) and/or reactDOM.render with something more declarative.
Also interested in hearing if someone managed to implement a plugin where the plugin class is a react component or if it makes more sense to have something in between like in my approach.
Hopefully this is useful to someone!