-
-
Notifications
You must be signed in to change notification settings - Fork 634
/
useContentEditableEffect.ts
105 lines (97 loc) 路 3.22 KB
/
useContentEditableEffect.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import { Path } from '@formily/path'
import { Engine, TreeNode } from '../models'
import { MouseDoubleClickEvent, MouseClickEvent } from '../events'
type GlobalState = {
activeElements: Map<HTMLInputElement, TreeNode>
requestTimer: any
}
function placeCaretAtEnd(el: HTMLInputElement) {
el.focus()
let range = document.createRange()
range.selectNodeContents(el)
range.collapse(false)
let sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
}
export const useContentEditableEffect = (engine: Engine) => {
const globalState: GlobalState = {
activeElements: new Map(),
requestTimer: null,
}
function onKeyDownHandler(event: KeyboardEvent) {
if (event.key === 'Enter') {
event.stopPropagation()
event.preventDefault()
}
}
function onInputHandler(event: InputEvent) {
const node = globalState.activeElements.get(this)
event.stopPropagation()
event.preventDefault()
if (node) {
const target = event.target as Element
clearTimeout(globalState.requestTimer)
globalState.requestTimer = setTimeout(() => {
Path.setIn(
node.props,
this.getAttribute(engine.props.contentEditableAttrName),
target?.textContent
)
setTimeout(() => {
placeCaretAtEnd(this)
}, 16)
}, 1000)
}
}
function findTargetNodeId(element: Element) {
if (!element) return
const nodeId = element.getAttribute(
engine.props.contentEditableNodeIdAttrName
)
if (nodeId) return nodeId
const parent = element.closest(`*[${engine.props.nodeIdAttrName}]`)
if (parent) return parent.getAttribute(engine.props.nodeIdAttrName)
}
engine.subscribeTo(MouseClickEvent, (event) => {
const target = event.data.target as Element
const editableElement = target?.closest?.(
`*[${engine.props.contentEditableAttrName}]`
)
if (
editableElement &&
editableElement.getAttribute('contenteditable') === 'true'
)
return
globalState.activeElements.forEach((node, element) => {
globalState.activeElements.delete(element)
element.setAttribute('contenteditable', 'false')
element.removeEventListener('input', onInputHandler)
})
})
engine.subscribeTo(MouseDoubleClickEvent, (event) => {
const target = event.data.target as Element
const editableElement = target?.closest?.(
`*[${engine.props.contentEditableAttrName}]`
) as HTMLInputElement
const workspace = engine.workbench.activeWorkspace
const tree = workspace.operation.tree
if (editableElement) {
const editable = editableElement.getAttribute('contenteditable')
if (editable === 'false' || !editable) {
const nodeId = findTargetNodeId(editableElement)
if (nodeId) {
const targetNode = tree.findById(nodeId)
if (targetNode) {
globalState.activeElements.set(editableElement, targetNode)
editableElement.setAttribute('contenteditable', 'true')
editableElement.focus()
editableElement.addEventListener('input', onInputHandler)
editableElement.addEventListener('keydown', onKeyDownHandler)
placeCaretAtEnd(editableElement)
}
}
}
}
})
}