diff --git a/types/draft-js/draft-js-tests.tsx b/types/draft-js/draft-js-tests.tsx index 1ac3c622a0..d4aabcbb7f 100644 --- a/types/draft-js/draft-js-tests.tsx +++ b/types/draft-js/draft-js-tests.tsx @@ -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 = { + 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(); diff --git a/types/draft-js/index.d.ts b/types/draft-js/index.d.ts index c0086319a3..ef1a02099f 100644 --- a/types/draft-js/index.d.ts +++ b/types/draft-js/index.d.ts @@ -1,6 +1,8 @@ // Type definitions for Draft.js v0.10.0 // Project: https://facebook.github.io/draft-js/ -// Definitions by: Dmitry Rogozhny , Eelco Lempsink +// Definitions by: Dmitry Rogozhny +// Eelco Lempsink +// Ryan Schwers // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.1 @@ -27,6 +29,8 @@ declare namespace Draft { type DraftBlockRenderMap = Immutable.Map; + 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; } }