is it possible to prevent focusing void inline nodes and make them act like characters? i.e. only allow the cursor the be on either side of the node, not "on" it.
I tried to create a rule for this, but couldn't quite figure out how to check if a node has focus:
{
match: node => node.kind === 'inline' && node.isVoid,
validate: node => // how to check if the node has focus here?,
normalize: (transform, node) => // move cursor to either side
}
The selection and the nodes are two distinct data sets. The schema rules can't be used to "normalize" the selection.
Instead you could apply a transform in your onChange if the selection is in a void node:
class App extends React.Component {
state = {
state: initialState
}
onChange = (state) => {
const { startBlock, document } = state;
// The selection is in a void node.
if (startBlock.isVoid) {
const next = document.getNextBlock(startBlock.key);
// The transform can be improved to move to next OR prev block
// By comparing the selection with the one of this.state.state
// And testing that "next" exists
state = state.transform()
.collapseToStartOf(nextBlock)
.apply()
}
this.setState({ state })
}
render = () => {
return (
<Editor
state={this.state.state}
onChange={this.onChange}
/>
)
}
}
This example can be improved, and maybe @ianstormtaylor has a better/simpler solution :)
Note: it could be exported as a Slate plugin using onBeforeChange:
const noSelectionInVoid = {
onBeforeChange(state) {
const { startBlock, document } = state
// The selection is in a void node.
if (startBlock.isVoid) {
const next = document.getNextBlock(startBlock.key);
// The transform can be improved to move to next OR prev block
// By comparing the selection with the one of this.state.state
// And testing that "next" exists
state = state.transform()
.collapseToStartOf(nextBlock)
.apply()
}
return state
}
};
and you can pass this plugin to your <Editor plugins={[ noSelectionInVoid ]} />
@SamyPesse I see, think I'll go with the plugin approach then :)
one more thing: how would I get this.state.state for comparing previous cursor position in the plugin example?
I don't think it's possible to read the current state in onBeforeChange in a plugin
for anyone interested, managed to solve this eventually with a plugin like this:
class SkipVoidNodesPlugin {
direction = '';
onKeyDown(event, keyData, state) {
if (keyData.key === 'left') {
this.direction = 'Previous';
} else if (keyData.key === 'right') {
this.direction = 'Next';
} else {
this.direction = '';
}
}
onBeforeChange(state) {
if (this.direction !== '') {
const focusedNode = state.focusInline || state.focusText;
if (focusedNode.isVoid) {
return state.transform()[`collapseToEndOf${this.direction}Text`]().apply();
}
}
}
}
for those looking at this today, the bad news is that onBeforeChange, Transform and State have gone. the good news seems to be that the same trick works if you do it in onChange with Change, with something like this:
onChange = (change: Change) => {
let value = change.value;
if (this.direction !== '') {
const focusedNode = value.focusInline || value.focusText;
if (focusedNode.isVoid) {
change = change[`collapseToEndOf${ this.direction }Text`]();
value = change.value;
}
}
// ...
this.setState({
value,
});
}
Most helpful comment
for anyone interested, managed to solve this eventually with a plugin like this: