I don't know if this is a common issue or a mistake on our part but maybe someone has an idea: We're building an HTML editor with react and slate. The User can select a textbox and then change attributes. This works fine for simple buttons. However, when I open a dropdown (react-select) to for example change font size, the selected text is no longer marked. Slate keeps the selection so the changes take effect but it's a bad UX like that.
Imho this should be a slate feature to keep the text marked but maybe it's something I need to apply myself.
some snippets, don't know if they help:
The Editor component initializes the font style plugins and takes care of serialization.
class Editor extends React.Component {
constructor(props) {
super(props);
this.config = {
...mergePluginConfig(PLUGIN_CONFIG, props),
getEditor: () => this.editor,
getValue: () => this.state.value,
};
this.plugins = initializePlugins(this.config);
this.htmlSerializer = new HtmlSerializer({
rules: getSerializationRulesFromPlugins(this.plugins),
});
this.schema = getSchemaFromPlugins(this.plugins);
this.state = {
value: this.htmlSerializer.deserialize(props.value)
};
ref = editor => {
this.editor = editor;
};
render() {
return (
<div>
<Toolbar>
<div className="control">
{renderToolbarElementWithPlugins(this.plugins, 'font-size')}
</div>
<!--- more tools --->
<SlateEditor
autoFocus={true}
spellCheck={true}
placeholder={this.props.placeholder}
ref={this.ref}
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
plugins={this.plugins}
schema={this.schema}
/>
onChange = ({ value }) => {
const {startInline, endInline, document, selection, fragment} = value;
// holds the correct information
console.log(fragment.text);
// ...
this.setState({ value });
this.props.onChange(this.htmlSerializer.serialize(value));
};
This is the font-size plugin that is initialized with the others and will be displayed in the toolbar:
export default function initializeFontSizePlugin(options) {
// this takes care of detecting the current value and applying selected change to the value.
// it does not change selection
const plugin = createStyleBasedMarkPlugin(...);
const fontSizeOptions = options.fontSizeOptions || [];
const handleFontSizeChange = ({value}) => {
plugin.reapplyFontSize({value: rendererFontSize(value)});
};
return {
...plugin,
renderToolbarElement() {
const {isMixed, fontSize} = plugin.detectFontSize();
return <Select
isCreatable={true}
name='font-size'
value={isMixed ? undefined : displayFontSize(fontSize)}
onChange={handleFontSizeChange}
options={fontSizeOptions}
/>;
}
};
}
My current solution is to focus slate as soon as select opens and then tell select to be open but that feels hackish and has drawbacks (see below)
const handleFontSizeChange = ({value}) => {
plugin.reapplyFontSize({value: rendererFontSize(value)});
handleMenuClose();
};
let menuIsOpen = false;
let firstOpen = false;
const handleMenuOpen = (editor) => {
firstOpen = true;
if(!menuIsOpen) {
setTimeout(() => {
if (editor) {
editor.focus();
}
menuIsOpen = true;
}, 1);
}
}
const handleMenuClose = (editor) => {
if(!firstOpen) {
setTimeout(() => {
if(menuIsOpen) {
menuIsOpen = false;
if (editor) {
editor.focus();
}
}
}, 1);
} else {
firstOpen = false;
}
}
<Select
onMenuOpen={handleMenuOpen.bind(this)}
onMenuClose={handleMenuClose.bind(this)}
menuIsOpen={menuIsOpen}
I have to use the timeout to get outside the react lifecycle and I have to add an additional flag since losing focus on the select component will also close it. Like I said that has drawbacks: - a little flicker on the selected text during the focus switch - the dropdown box in select has the wrong coloring (not focused obviously) - switching to another dropdown (like alignment) won't close the other since that already has no focus:
Additional info:
We have to work with slate and slate-react
at version 0.47 as higher versions are not supported by slate-html-serializer
which we need. Maybe this has already been solved in a higher version?
So, I have a somewhat working version but I'd much more prefer a solution where slate takes care of the selection "natively" without me having to handle focus. It should be possible I think without the selected text flickering
and off colors.