Skip to content

Commit

Permalink
Remove auto focus on load (#205)
Browse files Browse the repository at this point in the history
* Add block map setup functions

* Add style map setup functions

* Use customStyleFn instead of customStyleMap property

* Remove style and block map states

* Rely on mouse to set focus cursor position

* Add isActive property to Toolbar

* Set active state to false to buttons when toolbar is not active

* Bump up package version

* Update Toolbar UTs
  • Loading branch information
niuware committed Nov 4, 2020
1 parent 9ef23a0 commit acf3eb9
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 46 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mui-rte",
"version": "1.25.0",
"version": "1.26.0",
"description": "Material-UI Rich Text Editor and Viewer",
"keywords": [
"material-ui",
Expand Down
117 changes: 73 additions & 44 deletions src/MUIRichTextEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { FunctionComponent, useEffect, useState, useRef,
forwardRef, useImperativeHandle, RefForwardingComponent, SyntheticEvent } from 'react'
forwardRef, useImperativeHandle, RefForwardingComponent } from 'react'
import Immutable from 'immutable'
import classNames from 'classnames'
import { createStyles, withStyles, WithStyles, Theme } from '@material-ui/core/styles'
Expand All @@ -8,7 +8,7 @@ import {
Editor, EditorState, convertFromRaw, RichUtils, AtomicBlockUtils,
CompositeDecorator, convertToRaw, DefaultDraftBlockRenderMap, DraftEditorCommand,
DraftHandleValue, DraftStyleMap, ContentBlock, DraftDecorator,
SelectionState, KeyBindingUtil, getDefaultKeyBinding, Modifier
SelectionState, KeyBindingUtil, getDefaultKeyBinding, Modifier, DraftBlockRenderMap
} from 'draft-js'
import Toolbar, { TToolbarControl, TCustomControl, TToolbarButtonSize } from './components/Toolbar'
import Link from './components/Link'
Expand Down Expand Up @@ -248,10 +248,6 @@ const MUIRichTextEditor: RefForwardingComponent<TMUIRichTextEditorRef, IMUIRichT
const [selectedIndex, setSelectedIndex] = useState<number>(0)
const [editorState, setEditorState] = useState(() => useEditorState(props))
const [focusMediaKey, setFocusMediaKey] = useState("")
const [customRenderers, setCustomRenderers] = useState<TCustomRenderers>({
style: undefined,
block: undefined
})

const editorRef = useRef<Editor>(null)
const editorId = props.id || "mui-rte"
Expand All @@ -262,6 +258,9 @@ const MUIRichTextEditor: RefForwardingComponent<TMUIRichTextEditorRef, IMUIRichT
const autocompletePositionRef = useRef<TPosition | undefined>(undefined)
const autocompleteLimit = props.autocomplete ? props.autocomplete.suggestLimit || 5 : 5
const isFirstFocus = useRef(true)
const customBlockMapRef = useRef<DraftBlockRenderMap | undefined>(undefined)
const customStyleMapRef = useRef<DraftStyleMap | undefined>(undefined)
const isFocusedWithMouse = useRef(false)
const selectionRef = useRef<TStateOffset>({
start: 0,
end: 0
Expand Down Expand Up @@ -290,31 +289,7 @@ const MUIRichTextEditor: RefForwardingComponent<TMUIRichTextEditorRef, IMUIRichT

useEffect(() => {
const editorState = useEditorState(props)
const customBlockMap: any = {}
const customStyleMap = JSON.parse(JSON.stringify(styleRenderMap))
if (props.customControls) {
props.customControls.forEach(control => {
if (control.type === "inline" && control.inlineStyle) {
customStyleMap[control.name.toUpperCase()] = control.inlineStyle
}
else if (control.type === "block" && control.blockWrapper) {
customBlockMap[control.name.toUpperCase()] = {
element: "div",
wrapper: control.blockWrapper
}
}
})
}
setCustomRenderers({
style: customStyleMap,
block: DefaultDraftBlockRenderMap.merge(blockRenderMap, Immutable.Map(customBlockMap))
})
const nextEditorState = EditorState.forceSelection(editorState, editorState.getSelection())
if (props.readOnly === true) {
setEditorState(nextEditorState)
} else {
setEditorState(EditorState.moveFocusToEnd(nextEditorState))
}
setEditorState(editorState)
toggleMouseUpListener(true)
return () => {
toggleMouseUpListener()
Expand Down Expand Up @@ -505,16 +480,10 @@ const MUIRichTextEditor: RefForwardingComponent<TMUIRichTextEditorRef, IMUIRichT
return isMaxLengthHandled(editorState, 1)
}

const isSyntheticEventTriggeredByTab = (event: SyntheticEvent): boolean => {
if (!event.hasOwnProperty("relatedTarget") || (event as any).relatedTarget == null) {
return false
}
return true
}

const handleEditorFocus = (event: SyntheticEvent) => {
const handleEditorFocus = () => {
handleFocus()
if (!isSyntheticEventTriggeredByTab(event)) {
if (isFocusedWithMouse.current === true) {
isFocusedWithMouse.current = false
return
}
const nextEditorState = EditorState.forceSelection(editorState, editorState.getSelection())
Expand Down Expand Up @@ -543,6 +512,7 @@ const MUIRichTextEditor: RefForwardingComponent<TMUIRichTextEditorRef, IMUIRichT
}

const handleBlur = () => {
isFocusedWithMouse.current = false
setFocus(false)
if (props.onBlur) {
props.onBlur()
Expand All @@ -556,8 +526,15 @@ const MUIRichTextEditor: RefForwardingComponent<TMUIRichTextEditorRef, IMUIRichT
}
}

const handleMouseDown = () => {
isFocusedWithMouse.current = true
}

const handleClearFormat = () => {
const withoutStyles = clearInlineStyles(editorState, customRenderers.style)
if (customStyleMapRef.current === undefined) {
return
}
const withoutStyles = clearInlineStyles(editorState, customStyleMapRef.current)
const selectionInfo = getSelectionInfo(editorState)
const newEditorState = EditorState.push(editorState, withoutStyles, 'change-inline-style')
setEditorState(RichUtils.toggleBlockType(newEditorState, selectionInfo.blockType))
Expand Down Expand Up @@ -903,6 +880,47 @@ const MUIRichTextEditor: RefForwardingComponent<TMUIRichTextEditorRef, IMUIRichT
handlePromptForMedia(false, newEditorState)
}

const getStyleMap = (): DraftStyleMap => {
if (customStyleMapRef.current === undefined) {
setupStyleMap()
}
return customStyleMapRef.current!
}

const setupStyleMap = () => {
const customStyleMap = JSON.parse(JSON.stringify(styleRenderMap))
if (props.customControls) {
props.customControls.forEach(control => {
if (control.type === "inline" && control.inlineStyle) {
customStyleMap[control.name.toUpperCase()] = control.inlineStyle
}
})
}
customStyleMapRef.current = customStyleMap
}

const getBlockMap = (): DraftBlockRenderMap => {
if (customBlockMapRef.current === undefined) {
setupBlockMap()
}
return customBlockMapRef.current!
}

const setupBlockMap = () => {
const customBlockMap: any = {}
if (props.customControls) {
props.customControls.forEach(control => {
if (control.type === "block" && control.blockWrapper) {
customBlockMap[control.name.toUpperCase()] = {
element: "div",
wrapper: control.blockWrapper
}
}
})
}
customBlockMapRef.current = DefaultDraftBlockRenderMap.merge(blockRenderMap, Immutable.Map(customBlockMap))
}

const blockRenderer = (contentBlock: ContentBlock) => {
const blockType = contentBlock.getType()
if (blockType === 'atomic') {
Expand Down Expand Up @@ -936,6 +954,15 @@ const MUIRichTextEditor: RefForwardingComponent<TMUIRichTextEditorRef, IMUIRichT
return null
}

const styleRenderer = (style: any, _block: ContentBlock): React.CSSProperties => {
const customStyleMap = getStyleMap()
const styleNames = style.toJS()
return styleNames.reduce((styles: any, styleName: string) => {
styles = customStyleMap[styleName]
return styles
}, {})
}

const insertAtomicBlock = (editorState: EditorState, type: string, data: any, options?: any) => {
const contentState = editorState.getCurrentContent()
const contentStateWithEntity = contentState.createEntity(type, 'IMMUTABLE', data)
Expand Down Expand Up @@ -1060,6 +1087,7 @@ const MUIRichTextEditor: RefForwardingComponent<TMUIRichTextEditorRef, IMUIRichT
controls={inlineToolbarControls}
customControls={customControls}
inlineMode={true}
isActive={true}
/>
</Paper>
: null}
Expand All @@ -1073,18 +1101,19 @@ const MUIRichTextEditor: RefForwardingComponent<TMUIRichTextEditorRef, IMUIRichT
className={classes.toolbar}
disabled={!editable}
size={props.toolbarButtonSize}
isActive={focus}
/>
: null}
{placeholder}
<div id={`${editorId}-editor`} className={classes.editor}>
<div id={`${editorId}-editor-container`} className={classNames(className, classes.editorContainer, {
[classes.editorReadOnly]: !editable,
[classes.error]: props.error
})} onBlur={handleBlur}>
})} onMouseDown={handleMouseDown} onBlur={handleBlur}>
<Editor
customStyleMap={customRenderers.style}
blockRenderMap={customRenderers.block}
blockRenderMap={getBlockMap()}
blockRendererFn={blockRenderer}
customStyleFn={styleRenderer}
editorState={editorState}
onChange={handleChange}
onFocus={handleEditorFocus}
Expand Down
6 changes: 5 additions & 1 deletion src/components/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type TToolbarProps = {
className?: string
disabled?: boolean
size?: TToolbarButtonSize
isActive: boolean
}

const STYLE_TYPES: TStyleType[] = [
Expand Down Expand Up @@ -233,7 +234,10 @@ const Toolbar: FunctionComponent<TToolbarProps> = (props) => {
}
let active = false
const action = props.onClick
if (style.type === "inline") {
if (!props.isActive) {
active = false
}
else if (style.type === "inline") {
active = editorState.getCurrentInlineStyle().has(style.style)
}
else if (style.type === "block") {
Expand Down
3 changes: 3 additions & 0 deletions test/Toolbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('<EditorControls />', () => {
id="mui-rte"
editorState={editorState}
onClick={() => {}}
isActive={true}
/>
)
const result = wrapper.find(EditorButton)
Expand All @@ -41,6 +42,7 @@ describe('<EditorControls />', () => {
editorState={editorState}
controls={controls}
onClick={() => {}}
isActive={true}
/>
)
const result = wrapper.find(EditorButton).map(item => {
Expand All @@ -56,6 +58,7 @@ describe('<EditorControls />', () => {
editorState={editorState}
controls={[]}
onClick={() => {}}
isActive={true}
/>
)
const result = wrapper.find(EditorButton)
Expand Down

0 comments on commit acf3eb9

Please sign in to comment.