import { EditorState , SelectionState , ContentState } from 'draft-js' ; import { EditorPlugin } from '@draft-js-plugins/editor' ; import insertNewLine from './modifiers/insertNewLine' ; import setSelection from './modifiers/setSelection' ; import setSelectionToBlock from './modifiers/setSelectionToBlock' ; import createDecorator from './createDecorator' ; import createBlockKeyStore , { BlockKeyStore , } from './utils/createBlockKeyStore' ; import blockInSelection from './utils/blockInSelection' ; import getBlockMapKeys from './utils/getBlockMapKeys' ; import removeBlock from './modifiers/removeBlock' ; import { defaultTheme , FocusPluginTheme } from './theme' ; const focusableBlockIsSelected = ( editorState , blockKeyStore ) : boolean => { const selection = editorState . getSelection ( ) ; if ( selection . getAnchorKey ( ) !== selection . getFocusKey ( ) ) { return false ; } const content = editorState . getCurrentContent ( ) ; const block = content . getBlockForKey ( selection . getAnchorKey ( ) ) ; return blockKeyStore . includes ( block . getKey ( ) ) ; } ; const deleteCommands = [ 'backspace' , 'backspace-word' , 'backspace-to-start-of-line' , 'delete' , 'delete-word' , 'delete-to-end-of-block' , ] ; export interface FocusEditorPluginConfig { theme ? : FocusPluginTheme ; } type FocusEditorPlugin = EditorPlugin & { decorator : ReturnType < typeof createDecorator > ; } ; function forceSelection ( editorState ) { return EditorState . set ( editorState , { selection : editorState . getSelection ( ) , forceSelection : true , nativelyRenderedContent : null , inlineStyleOverride : null , } ) ; } export default ( config = { } ) : FocusEditorPlugin => { const blockKeyStore = createBlockKeyStore ( ) ; const theme = config . theme ? config . theme : defaultTheme ; let lastSelection ; let lastContentState ; return { handleReturn : ( event , editorState , { setEditorState } ) => { if ( focusableBlockIsSelected ( editorState , blockKeyStore ) ) { setEditorState ( insertNewLine ( editorState ) ) ; return 'handled' ; } return 'not-handled' ; } , handleKeyCommand : ( command , editorState , eventTimeStamp , { setEditorState } ) => { if ( deleteCommands . includes ( command ) && focusableBlockIsSelected ( editorState , blockKeyStore ) ) { const key = editorState . getSelection ( ) . getStartKey ( ) ; const newEditorState = removeBlock ( editorState , key ) ; if ( newEditorState !== editorState ) { setEditorState ( newEditorState ) ; return 'handled' ; } } return 'not-handled' ; } , onChange : ( editorState ) => { const contentState = editorState . getCurrentContent ( ) ; if ( ! contentState . equals ( lastContentState ! ) ) { lastContentState = contentState ; return editorState ; } lastContentState = contentState ; const selection = editorState . getSelection ( ) ; if ( lastSelection && selection . equals ( lastSelection ) ) { lastSelection = editorState . getSelection ( ) ; return editorState ; } const focusableBlockKeys = blockKeyStore . getAll ( ) ; if ( lastSelection ) { const lastBlockMapKeys = getBlockMapKeys ( contentState , lastSelection . getStartKey ( ) , lastSelection . getEndKey ( ) ) ; if ( lastBlockMapKeys . some ( ( key ) => focusableBlockKeys . includes ( key ! ) ) ) { lastSelection = selection ; return forceSelection ( editorState ) ; } } const currentBlockMapKeys = getBlockMapKeys ( contentState , selection . getStartKey ( ) , selection . getEndKey ( ) ) ; if ( currentBlockMapKeys . some ( ( key ) => focusableBlockKeys . includes ( key ! ) ) ) { lastSelection = selection ; return forceSelection ( editorState ) ; } return editorState ; } , keyBindingFn ( evt , { getEditorState , setEditorState } ) { const editorState = getEditorState ( ) ; if ( focusableBlockIsSelected ( editorState , blockKeyStore ) ) { if ( evt . keyCode === 37 ) { setSelection ( getEditorState , setEditorState , 'up' , evt ) ; } if ( evt . keyCode === 39 ) { setSelection ( getEditorState , setEditorState , 'down' , evt ) ; } if ( evt . keyCode === 38 ) { setSelection ( getEditorState , setEditorState , 'up' , evt ) ; } if ( evt . keyCode === 40 ) { setSelection ( getEditorState , setEditorState , 'down' , evt ) ; return undefined ; } } if ( evt . shiftKey ) { return undefined ; } if ( evt . keyCode === 37 ) { const selection = editorState . getSelection ( ) ; const selectionKey = selection . getAnchorKey ( ) ; const beforeBlock = editorState . getCurrentContent ( ) . getBlockBefore ( selectionKey ) ; if ( beforeBlock && selection . getAnchorOffset ( ) === 0 && blockKeyStore . includes ( beforeBlock . getKey ( ) ) ) { setSelection ( getEditorState , setEditorState , 'up' , evt ) ; } } if ( evt . keyCode === 39 ) { const selection = editorState . getSelection ( ) ; const selectionKey = selection . getFocusKey ( ) ; const currentBlock = editorState . getCurrentContent ( ) . getBlockForKey ( selectionKey ) ; const afterBlock = editorState . getCurrentContent ( ) . getBlockAfter ( selectionKey ) ; const notAtomicAndLastPost = currentBlock . getType ( ) !== 'atomic' && currentBlock . getLength ( ) === selection . getFocusOffset ( ) ; if ( afterBlock && notAtomicAndLastPost && blockKeyStore . includes ( afterBlock . getKey ( ) ) ) { setSelection ( getEditorState , setEditorState , 'down' , evt ) ; } } if ( evt . keyCode === 38 ) { const selectionKey = editorState . getSelection ( ) . getAnchorKey ( ) ; const beforeBlock = editorState . getCurrentContent ( ) . getBlockBefore ( selectionKey ) ; if ( beforeBlock && blockKeyStore . includes ( beforeBlock . getKey ( ) ) ) { setSelection ( getEditorState , setEditorState , 'up' , evt ) ; } } if ( evt . keyCode === 40 ) { const selectionKey = editorState . getSelection ( ) . getAnchorKey ( ) ; const afterBlock = editorState . getCurrentContent ( ) . getBlockAfter ( selectionKey ) ; if ( afterBlock && blockKeyStore . includes ( afterBlock . getKey ( ) ) ) { setSelection ( getEditorState , setEditorState , 'down' , evt ) ; } } return undefined ; } , blockRendererFn : ( contentBlock , { getEditorState , setEditorState } ) => { if ( contentBlock . getType ( ) !== 'atomic' ) { return undefined ; } const editorState = getEditorState ( ) ; const isFocused = editorState . getSelection ( ) . getHasFocus ( ) && blockInSelection ( editorState , contentBlock . getKey ( ) ) ; return { props : { isFocused , isCollapsedSelection : editorState . getSelection ( ) . isCollapsed ( ) , setFocusToBlock : ( ) => { setSelectionToBlock ( getEditorState , setEditorState , contentBlock ) ; } , } , } ; } , decorator : createDecorator ( { theme , blockKeyStore } ) , } ; } ;