A React framework for building text editors with immutable data models.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Utilities for key binding management, selection handling, and miscellaneous helper functions that support the Draft.js editor.
Utility functions for detecting keyboard modifiers and command keys across different platforms.
/**
* Key binding utilities for cross-platform keyboard handling
*/
const KeyBindingUtil: {
/**
* Check if event has Ctrl key (or Cmd on Mac)
* @param e - Keyboard event
* @returns True if Ctrl/Cmd is pressed
*/
isCtrlKeyCommand(e: SyntheticKeyboardEvent): boolean;
/**
* Check if event has Option key (Mac only)
* @param e - Keyboard event
* @returns True if Option is pressed on Mac
*/
isOptionKeyCommand(e: SyntheticKeyboardEvent): boolean;
/**
* Check if event has the platform's command modifier
* @param e - Keyboard event
* @returns True if command modifier is pressed
*/
hasCommandModifier(e: SyntheticKeyboardEvent): boolean;
};Usage Examples:
import { KeyBindingUtil } from "draft-js";
// Custom key binding function
function myKeyBindingFn(e) {
// Handle Ctrl+S / Cmd+S for save
if (e.keyCode === 83 && KeyBindingUtil.hasCommandModifier(e)) {
return 'save-content';
}
// Handle Ctrl+K / Cmd+K for link
if (e.keyCode === 75 && KeyBindingUtil.hasCommandModifier(e)) {
return 'add-link';
}
// Handle Alt+Arrow keys for word navigation
if (KeyBindingUtil.isOptionKeyCommand(e)) {
if (e.keyCode === 37) return 'move-word-left'; // Alt+Left
if (e.keyCode === 39) return 'move-word-right'; // Alt+Right
}
// Fall back to default key bindings
return getDefaultKeyBinding(e);
}
// Custom key command handler
function handleKeyCommand(command, editorState) {
switch (command) {
case 'save-content':
saveContent(editorState);
return 'handled';
case 'add-link':
addLink(editorState);
return 'handled';
case 'move-word-left':
// Custom word navigation logic
return 'handled';
default:
return RichUtils.handleKeyCommand(editorState, command);
}
}Default key binding function that maps keyboard events to Draft.js commands.
/**
* Default key binding function for Draft.js editor
* @param e - Keyboard event
* @returns Command string or null
*/
function getDefaultKeyBinding(e: SyntheticKeyboardEvent): ?string;Usage Examples:
import { getDefaultKeyBinding } from "draft-js";
// Extend default key bindings
function customKeyBindingFn(e) {
// Add custom bindings
if (e.keyCode === 9) { // Tab key
return 'indent';
}
if (e.keyCode === 13 && e.shiftKey) { // Shift+Enter
return 'soft-newline';
}
// Fall back to default bindings for standard commands
return getDefaultKeyBinding(e);
}
// Use in editor
<Editor
editorState={editorState}
onChange={setEditorState}
keyBindingFn={customKeyBindingFn}
handleKeyCommand={handleKeyCommand}
/>Utility function for generating unique keys for blocks and entities.
/**
* Generate unique key for blocks and entities
* @returns Random key string
*/
function genKey(): string;Usage Examples:
import { genKey, ContentBlock } from "draft-js";
// Create new content block with unique key
function createNewBlock(text, type = 'paragraph') {
return new ContentBlock({
key: genKey(),
type: type,
text: text,
characterList: List(Repeat(CharacterMetadata.EMPTY, text.length))
});
}
// Generate keys for custom entities
function createCustomEntity(type, mutability, data) {
const entityKey = genKey();
// Store entity with custom key...
return entityKey;
}Utility for getting the visible selection rectangle in the viewport.
/**
* Get visible selection rectangle
* @param window - Window object
* @returns ClientRect object or null if no visible selection
*/
function getVisibleSelectionRect(window: DOMWindow): ?ClientRect;Usage Examples:
import { getVisibleSelectionRect } from "draft-js";
// Position popup relative to selection
function showSelectionPopup() {
const selectionRect = getVisibleSelectionRect(window);
if (selectionRect) {
const popup = document.getElementById('selection-popup');
popup.style.display = 'block';
popup.style.top = `${selectionRect.top - 40}px`;
popup.style.left = `${selectionRect.left}px`;
}
}
// Hide popup when selection changes
function handleSelectionChange(editorState) {
const selection = editorState.getSelection();
if (selection.isCollapsed()) {
const popup = document.getElementById('selection-popup');
popup.style.display = 'none';
} else {
// Small delay to ensure DOM is updated
setTimeout(showSelectionPopup, 10);
}
}Utility for building BlockMap structures from arrays of ContentBlocks.
/**
* Utilities for building BlockMap structures
*/
const BlockMapBuilder: {
/**
* Create BlockMap from array of ContentBlocks
* @param blocks - Array of ContentBlock instances
* @returns OrderedMap of blocks keyed by block key
*/
createFromArray(blocks: Array<ContentBlock>): BlockMap;
};Usage Examples:
import { BlockMapBuilder, ContentBlock, CharacterMetadata } from "draft-js";
// Create blocks and build map
function createContentFromBlocks(blockData) {
const blocks = blockData.map(({ text, type = 'paragraph' }) => {
return new ContentBlock({
key: genKey(),
type: type,
text: text,
characterList: List(Repeat(CharacterMetadata.EMPTY, text.length))
});
});
return BlockMapBuilder.createFromArray(blocks);
}
// Use with ContentState
function createContentState(blockData) {
const blockMap = createContentFromBlocks(blockData);
return ContentState.createFromBlockArray(
blockMap.toArray(),
{} // entity map
);
}
// Example usage
const content = createContentState([
{ text: 'Welcome to Draft.js', type: 'header-one' },
{ text: 'This is a paragraph with some text.' },
{ text: 'This is another paragraph.' }
]);Additional utility functions for working with Draft.js content and editor state.
/**
* Advanced utility functions for Draft.js
*/
// Get all text content with formatting preserved
function getFormattedText(contentState: ContentState): Array<{
text: string,
style: DraftInlineStyle,
entityKey: ?string,
blockType: DraftBlockType
}> {
const result = [];
const blocks = contentState.getBlocksAsArray();
blocks.forEach(block => {
const text = block.getText();
const characterList = block.getCharacterList();
const blockType = block.getType();
for (let i = 0; i < text.length; i++) {
const char = text[i];
const metadata = characterList.get(i);
result.push({
text: char,
style: metadata.getStyle(),
entityKey: metadata.getEntity(),
blockType: blockType
});
}
});
return result;
}
// Find text occurrences with position info
function findText(contentState: ContentState, searchText: string): Array<{
blockKey: string,
start: number,
end: number,
text: string
}> {
const results = [];
const blocks = contentState.getBlocksAsArray();
blocks.forEach(block => {
const text = block.getText();
const blockKey = block.getKey();
let index = 0;
while ((index = text.indexOf(searchText, index)) !== -1) {
results.push({
blockKey,
start: index,
end: index + searchText.length,
text: searchText
});
index += searchText.length;
}
});
return results;
}
// Get word boundaries for selection
function getWordBoundaries(contentState: ContentState, blockKey: string, offset: number): {
start: number,
end: number
} {
const block = contentState.getBlockForKey(blockKey);
const text = block.getText();
// Find word start
let start = offset;
while (start > 0 && /\w/.test(text[start - 1])) {
start--;
}
// Find word end
let end = offset;
while (end < text.length && /\w/.test(text[end])) {
end++;
}
return { start, end };
}
// Select word at position
function selectWordAt(editorState: EditorState, blockKey: string, offset: number): EditorState {
const contentState = editorState.getCurrentContent();
const { start, end } = getWordBoundaries(contentState, blockKey, offset);
const selection = SelectionState.createEmpty(blockKey).merge({
anchorOffset: start,
focusOffset: end
});
return EditorState.forceSelection(editorState, selection);
}Complete Utility Example:
import React, { useState, useRef } from "react";
import {
Editor,
EditorState,
KeyBindingUtil,
getDefaultKeyBinding,
getVisibleSelectionRect,
RichUtils
} from "draft-js";
function AdvancedEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
const [showPopup, setShowPopup] = useState(false);
const editorRef = useRef(null);
// Custom key binding with utilities
const keyBindingFn = (e) => {
// Save with Ctrl+S / Cmd+S
if (e.keyCode === 83 && KeyBindingUtil.hasCommandModifier(e)) {
return 'save';
}
// Show popup with Ctrl+/ / Cmd+/
if (e.keyCode === 191 && KeyBindingUtil.hasCommandModifier(e)) {
return 'show-popup';
}
return getDefaultKeyBinding(e);
};
// Handle key commands
const handleKeyCommand = (command, editorState) => {
if (command === 'save') {
console.log('Saving content...');
return 'handled';
}
if (command === 'show-popup') {
const rect = getVisibleSelectionRect(window);
if (rect) {
setShowPopup(true);
}
return 'handled';
}
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
setEditorState(newState);
return 'handled';
}
return 'not-handled';
};
// Handle selection change
const handleChange = (newEditorState) => {
setEditorState(newEditorState);
// Hide popup when selection changes
const selection = newEditorState.getSelection();
if (selection.isCollapsed()) {
setShowPopup(false);
}
};
return (
<div style={{ position: 'relative' }}>
<Editor
ref={editorRef}
editorState={editorState}
onChange={handleChange}
keyBindingFn={keyBindingFn}
handleKeyCommand={handleKeyCommand}
placeholder="Type here... (Ctrl+S to save, Ctrl+/ for popup)"
/>
{showPopup && (
<div
style={{
position: 'absolute',
backgroundColor: 'black',
color: 'white',
padding: '8px',
borderRadius: '4px',
fontSize: '12px',
zIndex: 1000
}}
>
Selection popup!
</div>
)}
</div>
);
}// Keyboard event type
type SyntheticKeyboardEvent = Event & {
keyCode: number,
which: number,
key: string,
ctrlKey: boolean,
metaKey: boolean,
altKey: boolean,
shiftKey: boolean,
preventDefault: () => void,
stopPropagation: () => void
};
// DOM Window type
type DOMWindow = Window;
// Client rectangle
interface ClientRect {
bottom: number;
height: number;
left: number;
right: number;
top: number;
width: number;
}
// Key binding function type
type KeyBindingFunction = (e: SyntheticKeyboardEvent) => ?string;
// Block map type
type BlockMap = OrderedMap<string, ContentBlock>;
// Search result type
interface SearchResult {
blockKey: string;
start: number;
end: number;
text: string;
}
// Word boundary type
interface WordBoundary {
start: number;
end: number;
}Install with Tessl CLI
npx tessl i tessl/npm-draft-js