@effindomv2/runtime 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE.md +6 -0
  2. package/dist/bridge.js +4 -0
  3. package/dist/bridge.js.map +7 -0
  4. package/dist/effindom.v2.manifest.json +68 -0
  5. package/dist/fonts/NotoColorEmoji.ttf +0 -0
  6. package/dist/fonts/NotoEmoji-Regular.ttf +0 -0
  7. package/dist/fonts/NotoSans-Bold.ttf +0 -0
  8. package/dist/fonts/NotoSans-BoldItalic.ttf +0 -0
  9. package/dist/fonts/NotoSans-Italic.ttf +0 -0
  10. package/dist/fonts/NotoSans-Regular.ttf +0 -0
  11. package/dist/fonts/NotoSansMono-Bold.ttf +0 -0
  12. package/dist/fonts/NotoSansMono-Regular.ttf +0 -0
  13. package/dist/fonts/NotoSansSymbols2-Regular.ttf +0 -0
  14. package/dist/harness.js +2 -0
  15. package/dist/harness.js.map +7 -0
  16. package/dist/index.html +53 -0
  17. package/dist/runtime/effindom-core-v2.wasm32-simd.JQXIaRaN0-JahfIVFiSLE49WzzCENvef_2EDEm09nJs.wasm +0 -0
  18. package/dist/runtime/effindom-core-v2.wasm32-simd.y7RzpkMARiFeRkpgiqKQsAfv4Hf17NYdpni-6aLNhMs.js.symbols +10079 -0
  19. package/dist/runtime/effindom-core-v2.wasm32-simd.yhT7DGUv4soEv4W91WVZl3T7T_ecKojk5_IcnwL79a0.js +1 -0
  20. package/dist/runtime/effindom-core-v2.wasm32.JSfMkp9ertJzSZxA-_xz3yacrJUhswxlwbqbJLRIuqw.wasm +0 -0
  21. package/dist/runtime/effindom-core-v2.wasm32.xNgsQv7dCwf8Uy-PfJSoRNyk9-q1OSogUwkk5g6ZBjk.js.symbols +10088 -0
  22. package/dist/runtime/effindom-core-v2.wasm32.yhT7DGUv4soEv4W91WVZl3T7T_ecKojk5_IcnwL79a0.js +1 -0
  23. package/dist/runtime/effindom-core-v2.wasm64-simd.GkByf-CPorNOs1CORny_8JjVk8Z3piiFq92r-uw1Syc.js +1 -0
  24. package/dist/runtime/effindom-core-v2.wasm64-simd.p4P98oRu2wEWxtRRW8RHr27JhGeWvWlziZXDM_z3Nc4.js.symbols +10286 -0
  25. package/dist/runtime/effindom-core-v2.wasm64-simd.y75FYXRwhQrpaDGYbZWrohGDv0AmjTb-EjXwOjBIgnM.wasm +0 -0
  26. package/dist/runtime/effindom-core-v2.wasm64.GkByf-CPorNOs1CORny_8JjVk8Z3piiFq92r-uw1Syc.js +1 -0
  27. package/dist/runtime/effindom-core-v2.wasm64.emhE1_CJs4_zXp8wiQS_5lYpUQ0OchmXgxksi0ykaBs.js.symbols +10298 -0
  28. package/dist/runtime/effindom-core-v2.wasm64.sO-Yu70cfN8Qs3a5iEp6cbFPaiOchqcMKUzryu4npNo.wasm +0 -0
  29. package/dist/runtime/effindom-ui-v2.wasm32-simd.0Mas1XD03eYvemryTioWaZOBuBA5ij7MFlTa8CgEZWs.wasm +0 -0
  30. package/dist/runtime/effindom-ui-v2.wasm32-simd.ThSDClMnSWdwf9d89JZfYor0G1Z6OxR4lOc75rNRuD4.js.symbols +1890 -0
  31. package/dist/runtime/effindom-ui-v2.wasm32-simd.wved0xEV4EKXVNBU3Sx7giD4faxD2YII9sQ2N_wCP4I.js +2 -0
  32. package/dist/runtime/effindom-ui-v2.wasm32.H7kYg99bT9ADGh0uUvj6H9Dk1L058nVFLv_4R79IXW8.js.symbols +1900 -0
  33. package/dist/runtime/effindom-ui-v2.wasm32.tp53X7nHfG_EUq29naDyElfnqhMw2D1Tr1T-BJAYO7w.wasm +0 -0
  34. package/dist/runtime/effindom-ui-v2.wasm32.wved0xEV4EKXVNBU3Sx7giD4faxD2YII9sQ2N_wCP4I.js +2 -0
  35. package/dist/runtime/effindom-ui-v2.wasm64-simd.86tk9Z3xIpgTOykET_8Nn9iUVJnp1AzOHW4fVQRGtQE.wasm +0 -0
  36. package/dist/runtime/effindom-ui-v2.wasm64-simd.RQaXil22Chu63-vxK9oOuX8wUY044kbo190oYIbBU4M.js.symbols +1918 -0
  37. package/dist/runtime/effindom-ui-v2.wasm64-simd.ZS1KEAg0XQex-VXkfgpBHE8MIoqPF8qpaf8nOjANb_U.js +2 -0
  38. package/dist/runtime/effindom-ui-v2.wasm64.YSwpMFbr-Q1SBe0Ze8mub1u1PqsvSz3QIYuA3eaUMME.js.symbols +1924 -0
  39. package/dist/runtime/effindom-ui-v2.wasm64.ZS1KEAg0XQex-VXkfgpBHE8MIoqPF8qpaf8nOjANb_U.js +2 -0
  40. package/dist/runtime/effindom-ui-v2.wasm64.ioQ9DuM6gR_EjlfRHdF8EvNPBcKCs0PQbbY9-cjTV6Y.wasm +0 -0
  41. package/dist/runtime/icudt_minimal.962CX1q0-Nbv-OqXPaub5piYTOLumUk-nEvemcvvnpw.dat +0 -0
  42. package/package.json +62 -0
  43. package/scripts/build.sh +279 -0
  44. package/scripts/build_assets.sh +51 -0
  45. package/scripts/font_assets.sh +52 -0
  46. package/scripts/generate_manifest.py +121 -0
  47. package/scripts/stage_package_assets.sh +42 -0
  48. package/src/bridge/commit-policy.ts +10 -0
  49. package/src/bridge/events/canvas-geometry.ts +78 -0
  50. package/src/bridge/events/key-router.ts +187 -0
  51. package/src/bridge/events/pointer-router.ts +619 -0
  52. package/src/bridge/events/semantic-hit-testing.ts +27 -0
  53. package/src/bridge/events.ts +54 -0
  54. package/src/bridge/find-dialog.ts +690 -0
  55. package/src/bridge/find-session.ts +158 -0
  56. package/src/bridge/font-catalog.ts +51 -0
  57. package/src/bridge/google-fonts.ts +63 -0
  58. package/src/bridge/incremental-font-packages.ts +216 -0
  59. package/src/bridge/init.ts +77 -0
  60. package/src/bridge/interaction/editor-model.ts +371 -0
  61. package/src/bridge/interaction/editor-mutations.ts +495 -0
  62. package/src/bridge/interaction/editor-session.ts +628 -0
  63. package/src/bridge/interaction/logs.ts +23 -0
  64. package/src/bridge/interaction/text-encoding.ts +51 -0
  65. package/src/bridge/interaction.ts +86 -0
  66. package/src/bridge/local-types.ts +105 -0
  67. package/src/bridge/platform.ts +68 -0
  68. package/src/bridge/pointer-move-coalescer.ts +41 -0
  69. package/src/bridge/pull-to-refresh.ts +124 -0
  70. package/src/bridge/render-loop.ts +268 -0
  71. package/src/bridge/runtime/asset-manager.ts +202 -0
  72. package/src/bridge/runtime/find-controller.ts +269 -0
  73. package/src/bridge/runtime/font-manager.ts +691 -0
  74. package/src/bridge/runtime/open-canvas-api.ts +72 -0
  75. package/src/bridge/runtime/semantic-controller.ts +133 -0
  76. package/src/bridge/runtime/text-documents.ts +234 -0
  77. package/src/bridge/runtime.ts +315 -0
  78. package/src/bridge/touch-gesture.ts +159 -0
  79. package/src/bridge/utils/assets.ts +572 -0
  80. package/src/bridge/utils/backends.ts +163 -0
  81. package/src/bridge/utils/encoding.ts +128 -0
  82. package/src/bridge/utils/fetch.ts +147 -0
  83. package/src/bridge/utils/heap.ts +118 -0
  84. package/src/bridge.ts +93 -0
  85. package/src/clipboard.ts +139 -0
  86. package/src/core-types.ts +595 -0
  87. package/src/find-on-page.ts +284 -0
  88. package/src/harness.ts +53 -0
  89. package/src/index.ts +40 -0
  90. package/src/open-canvas.ts +108 -0
  91. package/src/runtime-config.ts +96 -0
  92. package/src/semantic.ts +905 -0
@@ -0,0 +1,371 @@
1
+ import type { TextChangeLog } from '../../core-types';
2
+ import {
3
+ advanceCodeUnitIndex,
4
+ codeUnitIndexToUtf8ByteOffset,
5
+ utf8ByteLengthForCodePoint,
6
+ utf8ByteLength,
7
+ } from './text-encoding';
8
+
9
+ export type HiddenTextEditor = HTMLInputElement | HTMLTextAreaElement;
10
+
11
+ export interface ReplacementEdit {
12
+ readonly start: number;
13
+ readonly end: number;
14
+ readonly insertedText: string;
15
+ }
16
+
17
+ export interface HiddenEditorWindow {
18
+ readonly text: string;
19
+ readonly docStart: number;
20
+ readonly docEnd: number;
21
+ readonly textStart: number;
22
+ readonly textEnd: number;
23
+ }
24
+
25
+ export interface PendingLocalReplacementEcho {
26
+ readonly handle: string;
27
+ readonly start: number;
28
+ readonly end: number;
29
+ readonly text: string;
30
+ }
31
+
32
+ export interface PendingLocalSelectionEcho {
33
+ readonly handle: string;
34
+ readonly start: number;
35
+ readonly end: number;
36
+ }
37
+
38
+ export interface PendingTextMutationBatch {
39
+ readonly handle: string;
40
+ readonly docStart: number;
41
+ readonly baseWindowText: string;
42
+ readonly currentWindowText: string;
43
+ readonly caret: number;
44
+ readonly interactionTime: bigint;
45
+ readonly kind: 'typing' | 'paste';
46
+ readonly mutationCount: number;
47
+ }
48
+
49
+ export interface PendingPasteInput {
50
+ readonly handle: string;
51
+ readonly docStart: number;
52
+ readonly selectionStart: number;
53
+ readonly selectionEnd: number;
54
+ readonly text: string;
55
+ }
56
+
57
+ interface TextClampRange {
58
+ readonly start: number;
59
+ readonly end: number;
60
+ }
61
+
62
+ const HIDDEN_EDITOR_WINDOW_OVERSCAN = 2048;
63
+ const HIDDEN_EDITOR_WINDOW_MIN_LENGTH = 4096;
64
+ const HIDDEN_EDITOR_WINDOW_REUSE_MARGIN = 512;
65
+ const TEXT_CHANGE_LOG_PREVIEW_LIMIT = 256;
66
+ const TEXTBOX_HARD_CLAMP_MAX_CODEPOINTS = 10000;
67
+
68
+ function isLineBreakCodeUnit(text: string, index: number): boolean {
69
+ if (index < 0 || index >= text.length) {
70
+ return false;
71
+ }
72
+ const codeUnit = text.charCodeAt(index);
73
+ return codeUnit === 0x0a || codeUnit === 0x0d;
74
+ }
75
+
76
+ function collectTextboxHardLineClampRanges(text: string): readonly TextClampRange[] {
77
+ const ranges: TextClampRange[] = [];
78
+ let index = 0;
79
+ while (index < text.length) {
80
+ let lineCapEnd = index;
81
+ let lineEnd = index;
82
+ let codePointCount = 0;
83
+ while (lineEnd < text.length && !isLineBreakCodeUnit(text, lineEnd)) {
84
+ const next = advanceCodeUnitIndex(text, lineEnd);
85
+ if (codePointCount < TEXTBOX_HARD_CLAMP_MAX_CODEPOINTS) {
86
+ lineCapEnd = next;
87
+ }
88
+ codePointCount += 1;
89
+ lineEnd = next;
90
+ }
91
+ if (codePointCount > TEXTBOX_HARD_CLAMP_MAX_CODEPOINTS) {
92
+ ranges.push({ start: lineCapEnd, end: lineEnd });
93
+ }
94
+ if (lineEnd >= text.length) {
95
+ break;
96
+ }
97
+ if (text.charCodeAt(lineEnd) === 0x0d && lineEnd + 1 < text.length && text.charCodeAt(lineEnd + 1) === 0x0a) {
98
+ index = lineEnd + 2;
99
+ } else {
100
+ index = lineEnd + 1;
101
+ }
102
+ }
103
+ return ranges;
104
+ }
105
+
106
+ function mapClampedTextIndex(index: number, ranges: readonly TextClampRange[]): number {
107
+ const clampedIndex = Math.max(0, index);
108
+ let removedBefore = 0;
109
+ for (const range of ranges) {
110
+ if (clampedIndex <= range.start) {
111
+ break;
112
+ }
113
+ if (clampedIndex < range.end) {
114
+ return range.start - removedBefore;
115
+ }
116
+ removedBefore += range.end - range.start;
117
+ }
118
+ return clampedIndex - removedBefore;
119
+ }
120
+
121
+ function clampTextboxHardLines(text: string): {
122
+ readonly text: string;
123
+ readonly changed: boolean;
124
+ mapIndex(index: number): number;
125
+ } {
126
+ const ranges = collectTextboxHardLineClampRanges(text);
127
+ if (ranges.length === 0) {
128
+ return {
129
+ text,
130
+ changed: false,
131
+ mapIndex: (index: number) => Math.max(0, Math.min(index, text.length)),
132
+ };
133
+ }
134
+ let result = '';
135
+ let cursor = 0;
136
+ for (const range of ranges) {
137
+ result += text.slice(cursor, range.start);
138
+ cursor = range.end;
139
+ }
140
+ result += text.slice(cursor);
141
+ return {
142
+ text: result,
143
+ changed: true,
144
+ mapIndex: (index: number) => mapClampedTextIndex(index, ranges),
145
+ };
146
+ }
147
+
148
+ export function utf8ByteOffsetToCodeUnitIndex(text: string, byteOffset: number, textByteLength?: number): number {
149
+ const byteLimit = textByteLength ?? utf8ByteLength(text);
150
+ const target = Math.max(0, Math.min(byteOffset, byteLimit));
151
+ let currentByteOffset = 0;
152
+ let currentIndex = 0;
153
+ while (currentIndex < text.length) {
154
+ const codePoint = text.codePointAt(currentIndex) ?? 0;
155
+ const nextByteOffset = currentByteOffset + utf8ByteLengthForCodePoint(codePoint);
156
+ if (nextByteOffset > target) {
157
+ break;
158
+ }
159
+ currentByteOffset = nextByteOffset;
160
+ currentIndex = advanceCodeUnitIndex(text, currentIndex);
161
+ if (currentByteOffset === target) {
162
+ break;
163
+ }
164
+ }
165
+ return currentIndex;
166
+ }
167
+
168
+ export function computeReplacementEdit(previousText: string, nextText: string): ReplacementEdit | null {
169
+ if (previousText === nextText) {
170
+ return null;
171
+ }
172
+ const sharedPrefixLimit = Math.min(previousText.length, nextText.length);
173
+ let prefix = 0;
174
+ while (prefix < sharedPrefixLimit && previousText.charCodeAt(prefix) === nextText.charCodeAt(prefix)) {
175
+ prefix += 1;
176
+ }
177
+
178
+ let suffix = 0;
179
+ while (
180
+ suffix < (previousText.length - prefix) &&
181
+ suffix < (nextText.length - prefix) &&
182
+ previousText.charCodeAt(previousText.length - suffix - 1) === nextText.charCodeAt(nextText.length - suffix - 1)
183
+ ) {
184
+ suffix += 1;
185
+ }
186
+
187
+ return {
188
+ start: prefix,
189
+ end: previousText.length - suffix,
190
+ insertedText: nextText.slice(prefix, nextText.length - suffix),
191
+ };
192
+ }
193
+
194
+ export function applyUtf8ByteReplacementEdit(text: string, start: number, end: number, insertedText: string): string {
195
+ const clampedStart = utf8ByteOffsetToCodeUnitIndex(text, Math.max(0, start));
196
+ const clampedEnd = utf8ByteOffsetToCodeUnitIndex(text, Math.max(clampedStart, end));
197
+ return `${text.slice(0, clampedStart)}${insertedText}${text.slice(clampedEnd)}`;
198
+ }
199
+
200
+ export function mapPendingBatchCurrentIndexToBaseIndex(batch: PendingTextMutationBatch, index: number): number {
201
+ const clampedIndex = Math.max(0, Math.min(index, batch.currentWindowText.length));
202
+ const replacement = computeReplacementEdit(batch.baseWindowText, batch.currentWindowText);
203
+ if (replacement === null) {
204
+ return Math.max(0, Math.min(clampedIndex, batch.baseWindowText.length));
205
+ }
206
+ const insertedStart = replacement.start;
207
+ const insertedEnd = replacement.start + replacement.insertedText.length;
208
+ if (clampedIndex <= insertedStart) {
209
+ return clampedIndex;
210
+ }
211
+ if (clampedIndex <= insertedEnd) {
212
+ return replacement.start;
213
+ }
214
+ const delta = replacement.insertedText.length - (replacement.end - replacement.start);
215
+ return Math.max(0, Math.min(clampedIndex - delta, batch.baseWindowText.length));
216
+ }
217
+
218
+ export function buildClampedTextboxEdit(
219
+ fullPreviousText: string,
220
+ absoluteStart: number,
221
+ absoluteEnd: number,
222
+ insertedText: string,
223
+ absoluteCaret: number,
224
+ ): {
225
+ readonly fullNextText: string;
226
+ readonly replacement: ReplacementEdit | null;
227
+ readonly caretByte: number;
228
+ readonly clampChanged: boolean;
229
+ } {
230
+ const replaceStart = utf8ByteOffsetToCodeUnitIndex(fullPreviousText, absoluteStart);
231
+ const replaceEnd = utf8ByteOffsetToCodeUnitIndex(fullPreviousText, absoluteEnd);
232
+ const intendedFullNextText =
233
+ `${fullPreviousText.slice(0, replaceStart)}${insertedText}${fullPreviousText.slice(replaceEnd)}`;
234
+ const intendedCaret = utf8ByteOffsetToCodeUnitIndex(intendedFullNextText, absoluteCaret);
235
+ const clamped = clampTextboxHardLines(intendedFullNextText);
236
+ const finalCaret = clamped.mapIndex(intendedCaret);
237
+ return {
238
+ fullNextText: clamped.text,
239
+ replacement: computeReplacementEdit(fullPreviousText, clamped.text),
240
+ caretByte: codeUnitIndexToUtf8ByteOffset(clamped.text, finalCaret),
241
+ clampChanged: clamped.changed,
242
+ };
243
+ }
244
+
245
+ export function summarizeTextChange(handle: string, text: string): TextChangeLog {
246
+ if (text.length <= TEXT_CHANGE_LOG_PREVIEW_LIMIT) {
247
+ return { handle, text };
248
+ }
249
+ return {
250
+ handle,
251
+ text: `${text.slice(0, TEXT_CHANGE_LOG_PREVIEW_LIMIT)}…`,
252
+ textLength: text.length,
253
+ truncated: true,
254
+ };
255
+ }
256
+
257
+ export function buildHiddenEditorWindow(
258
+ text: string,
259
+ start: number,
260
+ end: number,
261
+ textByteLength: number,
262
+ previousWindow?: HiddenEditorWindow,
263
+ ): HiddenEditorWindow {
264
+ const normalizedStart = Math.max(0, Math.min(start, text.length));
265
+ const normalizedEnd = Math.max(normalizedStart, Math.min(end, text.length));
266
+ const selectionSpan = normalizedEnd - normalizedStart;
267
+ const targetLength = Math.max(
268
+ HIDDEN_EDITOR_WINDOW_MIN_LENGTH,
269
+ selectionSpan + (HIDDEN_EDITOR_WINDOW_OVERSCAN * 2),
270
+ );
271
+ if (text.length <= targetLength) {
272
+ return {
273
+ text,
274
+ docStart: 0,
275
+ docEnd: textByteLength,
276
+ textStart: 0,
277
+ textEnd: text.length,
278
+ };
279
+ }
280
+
281
+ if (previousWindow !== undefined) {
282
+ const previousLength = previousWindow.textEnd - previousWindow.textStart;
283
+ const clampedPreviousStart = Math.max(0, Math.min(previousWindow.textStart, text.length));
284
+ const clampedPreviousEnd = Math.max(clampedPreviousStart, Math.min(previousWindow.textEnd, text.length));
285
+ if (previousLength > 0 &&
286
+ clampedPreviousEnd > clampedPreviousStart &&
287
+ clampedPreviousEnd - clampedPreviousStart >= targetLength) {
288
+ const reuseMargin = Math.min(
289
+ HIDDEN_EDITOR_WINDOW_REUSE_MARGIN,
290
+ Math.max(0, Math.floor((clampedPreviousEnd - clampedPreviousStart) / 4)),
291
+ );
292
+ const reusableStart = clampedPreviousStart + reuseMargin;
293
+ const reusableEnd = clampedPreviousEnd - reuseMargin;
294
+ if (normalizedStart >= reusableStart && normalizedEnd <= reusableEnd) {
295
+ return {
296
+ text: text.slice(clampedPreviousStart, clampedPreviousEnd),
297
+ docStart: previousWindow.docStart,
298
+ docEnd: previousWindow.docEnd,
299
+ textStart: clampedPreviousStart,
300
+ textEnd: clampedPreviousEnd,
301
+ };
302
+ }
303
+ }
304
+ }
305
+
306
+ let docStart = Math.max(0, normalizedStart - HIDDEN_EDITOR_WINDOW_OVERSCAN);
307
+ let docEnd = Math.min(text.length, normalizedEnd + HIDDEN_EDITOR_WINDOW_OVERSCAN);
308
+ const currentLength = docEnd - docStart;
309
+ if (currentLength < targetLength) {
310
+ let remaining = targetLength - currentLength;
311
+ const extendBefore = Math.min(docStart, Math.floor(remaining / 2));
312
+ docStart -= extendBefore;
313
+ remaining -= extendBefore;
314
+ const extendAfter = Math.min(text.length - docEnd, remaining);
315
+ docEnd += extendAfter;
316
+ remaining -= extendAfter;
317
+ if (remaining > 0) {
318
+ const extraBefore = Math.min(docStart, remaining);
319
+ docStart -= extraBefore;
320
+ remaining -= extraBefore;
321
+ if (remaining > 0) {
322
+ docEnd = Math.min(text.length, docEnd + remaining);
323
+ }
324
+ }
325
+ }
326
+
327
+ return {
328
+ text: text.slice(docStart, docEnd),
329
+ docStart: codeUnitIndexToUtf8ByteOffset(text, docStart),
330
+ docEnd: codeUnitIndexToUtf8ByteOffset(text, docEnd),
331
+ textStart: docStart,
332
+ textEnd: docEnd,
333
+ };
334
+ }
335
+
336
+ export function createHiddenTextEditor(multiline: boolean): HiddenTextEditor {
337
+ const editor = multiline ? document.createElement('textarea') : document.createElement('input');
338
+ if (editor instanceof HTMLInputElement) {
339
+ editor.type = 'text';
340
+ } else {
341
+ editor.rows = 1;
342
+ editor.wrap = 'off';
343
+ editor.style.resize = 'none';
344
+ editor.style.whiteSpace = 'pre';
345
+ }
346
+ editor.autocapitalize = 'off';
347
+ editor.autocomplete = 'off';
348
+ editor.autocorrect = false;
349
+ editor.spellcheck = false;
350
+ editor.tabIndex = -1;
351
+ editor.dataset.effindomHiddenEditor = 'true';
352
+ editor.setAttribute('aria-hidden', 'true');
353
+ editor.style.position = 'fixed';
354
+ editor.style.left = '-9999px';
355
+ editor.style.top = '0';
356
+ editor.style.width = '1px';
357
+ editor.style.height = '1px';
358
+ editor.style.opacity = '0';
359
+ editor.style.pointerEvents = 'none';
360
+ editor.style.font = '16px "Noto Sans Symbols 2", "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", monospace';
361
+ editor.style.overflow = 'hidden';
362
+ document.body.appendChild(editor);
363
+ return editor;
364
+ }
365
+
366
+ export function clearHiddenTextEditor(editor: HiddenTextEditor): void {
367
+ if (editor.value.length !== 0) {
368
+ editor.value = '';
369
+ }
370
+ editor.setSelectionRange(0, 0, 'none');
371
+ }