[Draft-js] Support custom keyBindingFN strings

The current typings don't work for custom keyBindingFn
return values. keyBindingFn implementations may return
any string or null. TypeScript's handling of string unions is
preventing the fn type definition overloading from working
as intended. This change removes overloading and moves
the union to a type, and adds nullability to the type definition.
This commit is contained in:
Ryan Schwers
2017-05-16 15:10:22 -07:00
parent 3b3fb05596
commit d28f34da92
2 changed files with 108 additions and 10 deletions
+100 -2
View File
@@ -2,7 +2,29 @@ import * as React from "react";
import * as ReactDOM from "react-dom";
import {Map} from "immutable";
import {Editor, EditorState, RichUtils, DefaultDraftBlockRenderMap, ContentBlock} from 'draft-js';
import {
ContentBlock,
DefaultDraftBlockRenderMap,
Editor,
EditorState,
Modifier,
RichUtils,
SelectionState,
getDefaultKeyBinding,
} from 'draft-js';
const SPLIT_HEADER_BLOCK = 'split-header-block';
export type KeyName =
'ENTER';
export type KeyCode = number;
export const KEYCODES: Record<KeyName, KeyCode> = {
ENTER: 13,
};
type SyntheticKeyboardEvent = React.KeyboardEvent<{}>;
class RichEditorExample extends React.Component<{}, { editorState: EditorState }> {
constructor() {
@@ -13,13 +35,40 @@ class RichEditorExample extends React.Component<{}, { editorState: EditorState }
onChange: (editorState: EditorState) => void = (editorState: EditorState) => this.setState({ editorState });
keyBindingFn(e: SyntheticKeyboardEvent): string {
if (e.keyCode === KEYCODES.ENTER) {
const { editorState } = this.state;
const contentState = editorState.getCurrentContent();
const selectionState = editorState.getSelection();
// only split headers into header and unstyled if we press 'Enter'
// at the end of a header (without text selected)
if (selectionState.isCollapsed()) {
const endKey = selectionState.getEndKey();
const endOffset = selectionState.getEndOffset();
const endBlock = contentState.getBlockForKey(endKey);
if (isHeaderBlock(endBlock) && endOffset === endBlock.getText().length) {
return SPLIT_HEADER_BLOCK;
}
}
}
return getDefaultKeyBinding(e);
}
handleKeyCommand = (command: string) => {
if (command === SPLIT_HEADER_BLOCK) {
this.onChange(this.splitHeaderToNewBlock());
return 'handled';
}
const {editorState} = this.state;
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
this.onChange(newState);
return "handled";
}
}
return "not-handled";
}
@@ -32,6 +81,40 @@ class RichEditorExample extends React.Component<{}, { editorState: EditorState }
this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, inlineStyle));
}
splitHeaderToNewBlock(): EditorState {
const { editorState } = this.state;
const selection = editorState.getSelection();
// Add a new block after the cursor
const contentWithBlock = Modifier.splitBlock(
editorState.getCurrentContent(),
selection,
);
// Change the new block type to be normal 'unstyled' text,
const newBlock = contentWithBlock.getBlockAfter(selection.getEndKey());
const contentWithUnstyledBlock = Modifier.setBlockType(
contentWithBlock,
SelectionState.createEmpty(newBlock.getKey()),
'unstyled',
);
// push the new state with 'insert-characters' to preserve the undo/redo stack
const stateWithNewline = EditorState.push(
editorState,
contentWithUnstyledBlock,
'insert-characters'
);
// manually move the cursor to the next line (as expected)
const nextState = EditorState.forceSelection(
stateWithNewline,
SelectionState.createEmpty(newBlock.getKey()),
);
return nextState;
}
render(): React.ReactElement<{}> {
// If the user changes block type before entering any text, we can
// either style the placeholder or hide it. Let's just hide it now.
@@ -58,6 +141,7 @@ class RichEditorExample extends React.Component<{}, { editorState: EditorState }
blockStyleFn={getBlockStyle}
customStyleMap={styleMap}
editorState={this.state.editorState}
keyBindingFn={this.keyBindingFn}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
placeholder="Tell a story..."
@@ -125,6 +209,20 @@ const BLOCK_TYPES = [
{ label: 'Code Block', style: 'code-block' },
];
const isHeaderBlock = (block: ContentBlock): boolean => {
switch (block.getType()) {
case 'header-one':
case 'header-two':
case 'header-three':
case 'header-four':
case 'header-five':
case 'header-six': {
return true;
}
default: return false;
}
}
const BlockStyleControls = (props: {editorState: EditorState, onToggle: (blockType: string) => void}) => {
const {editorState} = props;
const selection = editorState.getSelection();
+8 -8
View File
@@ -1,6 +1,8 @@
// Type definitions for Draft.js v0.10.0
// Project: https://facebook.github.io/draft-js/
// Definitions by: Dmitry Rogozhny <https://github.com/dmitryrogozhny>, Eelco Lempsink <https://github.com/eelco>
// Definitions by: Dmitry Rogozhny <https://github.com/dmitryrogozhny>
// Eelco Lempsink <https://github.com/eelco>
// Ryan Schwers <https://github.com/schwers>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.1
@@ -27,6 +29,8 @@ declare namespace Draft {
type DraftBlockRenderMap = Immutable.Map<DraftBlockType, DraftBlockRenderConfig>;
type EditorCommand = DraftEditorCommand | string;
/**
* `DraftEditor` is the root editor component. It composes a `contentEditable`
* div, and provides a wide variety of useful function props for managing the
@@ -78,8 +82,7 @@ declare namespace Draft {
// A function that accepts a synthetic key event and returns
// the matching DraftEditorCommand constant, or null if no command should
// be invoked.
keyBindingFn?(e: SyntheticKeyboardEvent): DraftEditorCommand;
keyBindingFn?(e: SyntheticKeyboardEvent): string;
keyBindingFn?(e: SyntheticKeyboardEvent): EditorCommand | null;
// Set whether the `DraftEditor` component should be editable. Useful for
// temporarily disabling edit behavior or allowing `DraftEditor` rendering
@@ -118,9 +121,7 @@ declare namespace Draft {
// Map a key command string provided by your key binding function to a
// specified behavior.
handleKeyCommand?(command: DraftEditorCommand): DraftHandleValue,
handleKeyCommand?(command: string): DraftHandleValue,
handleKeyCommand?(command: EditorCommand): DraftHandleValue,
// Handle intended text insertion before the insertion occurs. This may be
// useful in cases where the user has entered characters that you would like
@@ -201,8 +202,7 @@ declare namespace Draft {
/**
* Retrieve a bound key command for the given event.
*/
function getDefaultKeyBinding(e: SyntheticKeyboardEvent): DraftEditorCommand;
function getDefaultKeyBinding(e: SyntheticKeyboardEvent): string;
function getDefaultKeyBinding(e: SyntheticKeyboardEvent): DraftEditorCommand | null;
}
}