mirror of
https://github.com/gosticks/DefinitelyTyped.git
synced 2026-08-11 12:30:18 +00:00
[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:
@@ -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();
|
||||
|
||||
Vendored
+8
-8
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user