Skip to content

Commit 8de6be8

Browse files
committed
chore(core): improve content-editable experience
1 parent 27c0881 commit 8de6be8

File tree

1 file changed

+43
-5
lines changed

1 file changed

+43
-5
lines changed

packages/core/src/effects/useContentEditableEffect.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ import { MouseDoubleClickEvent, MouseClickEvent } from '../events'
55
type GlobalState = {
66
activeElements: Map<HTMLInputElement, TreeNode>
77
requestTimer: any
8+
isComposition: boolean
89
}
910

10-
function placeCaretAtEnd(el: HTMLInputElement) {
11+
function placeCaretAtEnd(el: HTMLInputElement, isCollapse: boolean) {
1112
const currentSelection = window.getSelection()
1213
if (currentSelection.containsNode(el)) return
1314
el.focus()
1415
const range = document.createRange()
1516
range.selectNodeContents(el)
16-
range.collapse(false)
17+
if (!isCollapse) {
18+
range.collapse(false)
19+
}
1720
const sel = window.getSelection()
1821
sel.removeAllRanges()
1922
sel.addRange(range)
@@ -23,6 +26,7 @@ export const useContentEditableEffect = (engine: Engine) => {
2326
const globalState: GlobalState = {
2427
activeElements: new Map(),
2528
requestTimer: null,
29+
isComposition: false,
2630
}
2731

2832
function onKeyDownHandler(event: KeyboardEvent) {
@@ -40,18 +44,35 @@ export const useContentEditableEffect = (engine: Engine) => {
4044
const target = event.target as Element
4145
clearTimeout(globalState.requestTimer)
4246
globalState.requestTimer = setTimeout(() => {
47+
if (globalState.isComposition) return
4348
Path.setIn(
4449
node.props,
4550
this.getAttribute(engine.props.contentEditableAttrName),
4651
target?.textContent
4752
)
4853
setTimeout(() => {
49-
placeCaretAtEnd(this)
54+
placeCaretAtEnd(this, window.getSelection().isCollapsed)
5055
}, 16)
51-
}, 300)
56+
}, 1000)
57+
}
58+
}
59+
60+
function onCompositionHandler(event: CompositionEvent) {
61+
if (event.type === 'compositionend') {
62+
globalState.isComposition = false
63+
onInputHandler(event as any)
64+
} else {
65+
clearTimeout(globalState.requestTimer)
66+
globalState.isComposition = true
5267
}
5368
}
5469

70+
function onPastHandler(event: ClipboardEvent) {
71+
event.preventDefault()
72+
const text = event.clipboardData.getData('text')
73+
this.textContent = text
74+
}
75+
5576
function findTargetNodeId(element: Element) {
5677
if (!element) return
5778
const nodeId = element.getAttribute(
@@ -76,6 +97,10 @@ export const useContentEditableEffect = (engine: Engine) => {
7697
globalState.activeElements.delete(element)
7798
element.setAttribute('contenteditable', 'false')
7899
element.removeEventListener('input', onInputHandler)
100+
element.removeEventListener('compositionstart', onCompositionHandler)
101+
element.removeEventListener('compositionupdate', onCompositionHandler)
102+
element.removeEventListener('compositionend', onCompositionHandler)
103+
element.removeEventListener('past', onPastHandler)
79104
})
80105
})
81106

@@ -97,8 +122,21 @@ export const useContentEditableEffect = (engine: Engine) => {
97122
editableElement.setAttribute('contenteditable', 'true')
98123
editableElement.focus()
99124
editableElement.addEventListener('input', onInputHandler)
125+
editableElement.addEventListener(
126+
'compositionstart',
127+
onCompositionHandler
128+
)
129+
editableElement.addEventListener(
130+
'compositionupdate',
131+
onCompositionHandler
132+
)
133+
editableElement.addEventListener(
134+
'compositionend',
135+
onCompositionHandler
136+
)
100137
editableElement.addEventListener('keydown', onKeyDownHandler)
101-
placeCaretAtEnd(editableElement)
138+
editableElement.addEventListener('paste', onPastHandler)
139+
placeCaretAtEnd(editableElement, false)
102140
}
103141
}
104142
}

0 commit comments

Comments
 (0)