-
Notifications
You must be signed in to change notification settings - Fork 10
/
native_selection_demo.html
153 lines (132 loc) · 7.2 KB
/
native_selection_demo.html
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
<style>
span:focus { outline: none; }
</style>
<div id="decoration" style="position:absolute"></div>
<br><br>
<div id=editView style="padding:5px; font-size:40px;white-space:pre;"> 😂😃😄一二三 hello world すし美味しいです</div>
<script>
var editContext = new EditContext();
editView.editContext = editContext;
editContext.updateText(0, 0, editView.textContent);
let compositionNode = null;
let compositionStartOffset = null
editContext.addEventListener("compositionstart", e => {
console.log(`editContext::compositionstart: e.data=[${e.data}]`);
let s = document.getSelection();
let range = s.getRangeAt(0);
compositionNode = range.startContainer;
compositionStartOffset = range.startOffset;
});
function printEditContextBuffer() {
buffer = editContext.text;
selectionStart = editContext.selectionStart;
selectionEnd = editContext.selectionEnd;
return buffer.slice(0,selectionStart) + '{' + buffer.slice(selectionStart, selectionEnd) + '}' + buffer.slice(selectionEnd);
}
editContext.addEventListener("textupdate", e => {
// The standardized values are e.text, e.selectionStart, and e.selectionEnd.
// The Chromium implementation was recently switched [1] to these values, so for
// now keep handling the old values as that change rolls out.
// [1] https://chromium-review.googlesource.com/c/chromium/src/+/4370629
let text = e.updateText || e.text || '';
let selectionStart = e.newSelectionStart || e.selectionStart;
let selectionEnd = e.newSelectionEnd || e.selectionEnd;
console.log(`editContext::textupdate:[${text}], composition range (${e.updateRangeStart}, ${e.updateRangeEnd}), selection (${selectionStart}, ${selectionEnd})`);
console.log('editContext.text[' + printEditContextBuffer() + ']');
let s = document.getSelection();
console.log(`anchor (${s.anchorNode}, ${s.anchorOffset}), textContent [${s.anchorNode.textContent}], focus (${s.focusNode}, ${s.focusOffset})`);
// English typing
if (!compositionNode) {
let textNode = s.anchorNode;
let string = textNode.textContent;
// update the text Node
textNode.textContent = string.substring(0, e.updateRangeStart) + text + string.substring(e.updateRangeEnd);
// update the caret
s.collapse(s.anchorNode, selectionStart);
console.log(`After textupdate: textContent[${s.anchorNode.textContent}]`);
// Composition
} else {
let compositionLength = e.updateRangeEnd - e.updateRangeStart;
console.log(`compositionNode[${compositionNode}], compositionStartOffset[${compositionStartOffset}], textContent[${compositionNode.textContent}]`);
let string = compositionNode.textContent;
// update the text Node
compositionNode.textContent = string.substring(0, compositionStartOffset) + text + string.substring(compositionStartOffset + compositionLength);
// update the caret
let selectionLocalOffset = selectionStart - e.updateRangeStart;
let selectionGlobalOffset = compositionStartOffset + selectionLocalOffset;
s.collapse(compositionNode, selectionGlobalOffset);
// Update bounds for IME.
let controlBound = editView.getBoundingClientRect();
controlBound = scaleDOMRect(controlBound, window.devicePixelRatio);
let selectionBound = s.getRangeAt(0).getBoundingClientRect();
selectionBound = scaleDOMRect(selectionBound, window.devicePixelRatio);
editContext.updateSelectionBounds(selectionBound);
editContext.updateControlBounds(controlBound);
// Update decoration
let range = document.createRange();
range.setStart(compositionNode, compositionStartOffset);
const compositionEndOffset = compositionStartOffset + text.length;
range.setEnd(compositionNode, compositionEndOffset);
const clientRect = range.getBoundingClientRect();
decoration.style.left = `${clientRect.x}px`;
decoration.style.top = `${clientRect.y}px`;
decoration.style.width = `${clientRect.width}px`;
decoration.style.height = `${clientRect.height}px`;
}
});
editContext.addEventListener("textformatupdate", e => {
console.log(`editContext::textformatupdate: e.underlineStyle=[${e.underlineStyle}]`);
// always use dotted here for convenience since CSS doesn't supprot 'Squiggle' that's returned by Japanese IME.
decoration.style.borderBottom = "3px dotted";
});
editContext.addEventListener("compositionend", e => {
console.log(`editContext::compositionend: e.data=[${e.data}]`);
decoration.style.width = `0px`;
compositionNode = null;
});
document.addEventListener("selectionchange", e => {
let s = document.getSelection();
console.log(`selectionchange: activeElement id(${document.activeElement.id}), [anchorNode: ${s.anchorNode}: ${s.anchorOffset}], focusNode: ${s.focusNode}: ${s.focusOffset}]`);
if (document.activeElement != editView)
return;
// Update editContext using start/end info from the native selection. (here we assume anchor/focus node are the same)
start = Math.min(s.anchorOffset, s.focusOffset);
end = Math.max(s.anchorOffset, s.focusOffset);
editContext.updateSelection(start, end);
console.log('editContext.text[' + printEditContextBuffer() + ']');
});
function computeCharacterBound(offset) {
let range = document.createRange();
range.setStart(compositionNode, offset);
range.setEnd(compositionNode, offset + 1); // 1 character
return range.getBoundingClientRect();
}
editContext.addEventListener("characterboundsupdate", e => {
console.log(`editcontext.text:[${editContext.text}]`);
console.log(`characterboundsupdate: composition range (${e.rangeStart}, ${e.rangeEnd})`);
let rangeStart = e.rangeStart;
let rangeEnd = e.rangeEnd;
// Calculate bounds for each character in the range.
if (rangeEnd > rangeStart) {
let charBounds = [];
for (offset = rangeStart; offset < rangeEnd; offset++) {
let bound = computeCharacterBound(offset);
bound = scaleDOMRect(bound, window.devicePixelRatio);
charBounds.push(bound);
}
editContext.updateCharacterBounds(rangeStart, charBounds);
console.log("charcterBoundsRangeStart:");
console.log(editContext.characterBoundsRangeStart);
console.log("charcterBounds:");
console.log(editContext.characterBounds());
}
});
// To handle zoom levels
function scaleDOMRect(rect, scale) {
rect.x = rect.x * scale;
rect.y = rect.y * scale;
rect.width = rect.width * scale;
rect.height = rect.height * scale;
return rect;
}
</script>